Commit 3b4e81bd authored by Stéphane Eranian's avatar Stéphane Eranian Committed by David Mosberger

[PATCH] ia64: switch to perfmon2

This patch contains a major rewrite of the perfmon subsystem to
bring it to version 2.0. This version is NOT compatible with the
existing perfmon-1.x version which was in 2.5 and still is in 2.4
kernels. This new codebase brings a lot of new features including
the ability to attach to already running tasks, the ability the
follow clone2, the ability to write your own sampling buffer format
via kernel modules. It is also much more robust than its 1.x
counter-part. This version supports the Itanium, McKinley and
Madison PMUs. This is beta quality code and extensions to the
interface are planned.
parent 5a5bed27
...@@ -5,8 +5,8 @@ ...@@ -5,8 +5,8 @@
extra-y := head.o init_task.o extra-y := head.o init_task.o
obj-y := acpi.o entry.o efi.o efi_stub.o gate.o ia64_ksyms.o irq.o irq_ia64.o irq_lsapic.o \ obj-y := acpi.o entry.o efi.o efi_stub.o gate.o ia64_ksyms.o irq.o irq_ia64.o irq_lsapic.o \
ivt.o machvec.o pal.o perfmon.o process.o ptrace.o sal.o semaphore.o setup.o signal.o \ ivt.o machvec.o pal.o perfmon.o perfmon_default_smpl.o process.o ptrace.o sal.o \
sys_ia64.o time.o traps.o unaligned.o unwind.o semaphore.o setup.o signal.o sys_ia64.o time.o traps.o unaligned.o unwind.o
obj-$(CONFIG_EFI_VARS) += efivars.o obj-$(CONFIG_EFI_VARS) += efivars.o
obj-$(CONFIG_FSYS) += fsys.o obj-$(CONFIG_FSYS) += fsys.o
......
...@@ -376,7 +376,8 @@ GLOBAL_ENTRY(fsys_bubble_down) ...@@ -376,7 +376,8 @@ GLOBAL_ENTRY(fsys_bubble_down)
* - r29: psr * - r29: psr
*/ */
# define PSR_PRESERVED_BITS (IA64_PSR_UP | IA64_PSR_MFL | IA64_PSR_MFH | IA64_PSR_PK \ # define PSR_PRESERVED_BITS (IA64_PSR_UP | IA64_PSR_MFL | IA64_PSR_MFH | IA64_PSR_PK \
| IA64_PSR_DT | IA64_PSR_PP | IA64_PSR_RT | IA64_PSR_IC) | IA64_PSR_DT | IA64_PSR_PP | IA64_PSR_SP | IA64_PSR_RT \
| IA64_PSR_IC)
/* /*
* Reading psr.l gives us only bits 0-31, psr.it, and psr.mc. The rest we have * Reading psr.l gives us only bits 0-31, psr.it, and psr.mc. The rest we have
* to synthesize. * to synthesize.
......
...@@ -162,8 +162,11 @@ EXPORT_SYMBOL(machvec_noop); ...@@ -162,8 +162,11 @@ EXPORT_SYMBOL(machvec_noop);
EXPORT_SYMBOL(zero_page_memmap_ptr); EXPORT_SYMBOL(zero_page_memmap_ptr);
#ifdef CONFIG_PERFMON #ifdef CONFIG_PERFMON
#include <asm/perfmon.h> #include <asm/perfmon.h>
EXPORT_SYMBOL(pfm_install_alternate_syswide_subsystem); EXPORT_SYMBOL(pfm_register_buffer_fmt);
EXPORT_SYMBOL(pfm_remove_alternate_syswide_subsystem); EXPORT_SYMBOL(pfm_unregister_buffer_fmt);
EXPORT_SYMBOL(pfm_mod_fast_read_pmds);
EXPORT_SYMBOL(pfm_mod_read_pmds);
EXPORT_SYMBOL(pfm_mod_write_pmcs);
#endif #endif
#ifdef CONFIG_NUMA #ifdef CONFIG_NUMA
......
This source diff could not be displayed because it is too large. You can view the blob instead.
/*
* Copyright (C) 2002-2003 Hewlett-Packard Co
* Stephane Eranian <eranian@hpl.hp.com>
*
* This file implements the default sampling buffer format
* for the Linux/ia64 perfmon-2 subsystem.
*/
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/module.h>
#include <linux/config.h>
#include <linux/init.h>
#include <asm/delay.h>
#include <linux/smp.h>
#include <asm/perfmon.h>
#include <asm/perfmon_default_smpl.h>
MODULE_AUTHOR("Stephane Eranian <eranian@hpl.hp.com>");
MODULE_DESCRIPTION("perfmon default sampling format");
MODULE_LICENSE("GPL");
MODULE_PARM(debug, "i");
MODULE_PARM_DESC(debug, "debug");
MODULE_PARM(debug_ovfl, "i");
MODULE_PARM_DESC(debug_ovfl, "debug ovfl");
#define DEFAULT_DEBUG 1
#ifdef DEFAULT_DEBUG
#define DPRINT(a) \
do { \
if (unlikely(debug >0)) { printk("%s.%d: CPU%d ", __FUNCTION__, __LINE__, smp_processor_id()); printk a; } \
} while (0)
#define DPRINT_ovfl(a) \
do { \
if (unlikely(debug_ovfl >0)) { printk("%s.%d: CPU%d ", __FUNCTION__, __LINE__, smp_processor_id()); printk a; } \
} while (0)
#else
#define DPRINT(a)
#define DPRINT_ovfl(a)
#endif
static int debug, debug_ovfl;
static int
default_validate(struct task_struct *task, unsigned int flags, int cpu, void *data)
{
pfm_default_smpl_arg_t *arg = (pfm_default_smpl_arg_t*)data;
int ret = 0;
if (data == NULL) {
DPRINT(("[%d] no argument passed\n", task->pid));
return -EINVAL;
}
DPRINT(("[%d] validate flags=0x%x CPU%d\n", task->pid, flags, cpu));
/*
* must hold at least the buffer header + one minimally sized entry
*/
if (arg->buf_size < PFM_DEFAULT_SMPL_MIN_BUF_SIZE) return -EINVAL;
DPRINT(("buf_size=%lu\n", arg->buf_size));
return ret;
}
static int
default_get_size(struct task_struct *task, unsigned int flags, int cpu, void *data, unsigned long *size)
{
pfm_default_smpl_arg_t *arg = (pfm_default_smpl_arg_t *)data;
/*
* size has been validated in default_validate
*/
*size = arg->buf_size;
return 0;
}
static int
default_init(struct task_struct *task, void *buf, unsigned int flags, int cpu, void *data)
{
pfm_default_smpl_hdr_t *hdr;
pfm_default_smpl_arg_t *arg = (pfm_default_smpl_arg_t *)data;
hdr = (pfm_default_smpl_hdr_t *)buf;
hdr->hdr_version = PFM_DEFAULT_SMPL_VERSION;
hdr->hdr_buf_size = arg->buf_size;
hdr->hdr_cur_pos = (void *)((unsigned long)buf)+sizeof(*hdr);
hdr->hdr_last_pos = (void *)((unsigned long)buf)+arg->buf_size;
hdr->hdr_overflows = 0UL;
hdr->hdr_count = 0UL;
DPRINT(("[%d] buffer=%p buf_size=%lu hdr_size=%lu hdr_version=%u\n",
task->pid,
buf,
hdr->hdr_buf_size,
sizeof(*hdr),
hdr->hdr_version));
return 0;
}
static int
default_handler(struct task_struct *task, void *buf, pfm_ovfl_arg_t *arg, struct pt_regs *regs)
{
pfm_default_smpl_hdr_t *hdr;
pfm_default_smpl_entry_t *ent;
void *cur, *last;
unsigned long *e;
unsigned long ovfl_mask;
unsigned long ovfl_notify;
unsigned long stamp;
unsigned int npmds, i;
/*
* some time stamp
*/
stamp = ia64_get_itc();
if (unlikely(buf == NULL || arg == NULL|| regs == NULL || task == NULL)) {
DPRINT(("[%d] invalid arguments buf=%p arg=%p\n", task->pid, buf, arg));
return -EINVAL;
}
hdr = (pfm_default_smpl_hdr_t *)buf;
cur = hdr->hdr_cur_pos;
last = hdr->hdr_last_pos;
ovfl_mask = arg->ovfl_pmds[0];
ovfl_notify = arg->ovfl_notify[0];
/*
* check for space against largest possibly entry.
* We may waste space at the end of the buffer.
*/
if ((last - cur) < PFM_DEFAULT_MAX_ENTRY_SIZE) goto full;
npmds = hweight64(arg->smpl_pmds[0]);
ent = (pfm_default_smpl_entry_t *)cur;
prefetch(arg->smpl_pmds_values);
/* position for first pmd */
e = (unsigned long *)(ent+1);
hdr->hdr_count++;
DPRINT_ovfl(("[%d] count=%lu cur=%p last=%p free_bytes=%lu ovfl_pmds=0x%lx ovfl_notify=0x%lx npmds=%u\n",
task->pid,
hdr->hdr_count,
cur, last,
last-cur,
ovfl_mask,
ovfl_notify, npmds));
/*
* current = task running at the time of the overflow.
*
* per-task mode:
* - this is ususally 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->cpu = smp_processor_id();
ent->last_reset_val = arg->pmd_last_reset; //pmd[0].reg_last_reset_val;
/*
* where did the fault happen (includes slot number)
*/
ent->ip = regs->cr_iip | ((regs->cr_ipsr >> 41) & 0x3);
/*
* which registers overflowed
*/
ent->ovfl_pmds = ovfl_mask;
ent->tstamp = stamp;
ent->set = arg->active_set;
ent->reserved1 = 0;
/*
* selectively store PMDs in increasing index number
*/
if (npmds) {
unsigned long *val = arg->smpl_pmds_values;
for(i=0; i < npmds; i++) {
*e++ = *val++;
}
}
/*
* update position for next entry
*/
hdr->hdr_cur_pos = cur + sizeof(*ent) + (npmds << 3);
/*
* keep same ovfl_pmds, ovfl_notify
*/
arg->ovfl_ctrl.notify_user = 0;
arg->ovfl_ctrl.block = 0;
arg->ovfl_ctrl.stop_monitoring = 0;
arg->ovfl_ctrl.reset_pmds = 1;
return 0;
full:
DPRINT_ovfl(("sampling buffer full free=%lu, count=%lu, ovfl_notify=0x%lx\n", last-cur, hdr->hdr_count, ovfl_notify));
/*
* increment number of buffer overflow.
* important to detect duplicate set of samples.
*/
hdr->hdr_overflows++;
/*
* if no notification is needed, then we just reset the buffer index.
*/
if (ovfl_notify == 0UL) {
hdr->hdr_count = 0UL;
arg->ovfl_ctrl.notify_user = 0;
arg->ovfl_ctrl.block = 0;
arg->ovfl_ctrl.stop_monitoring = 0;
arg->ovfl_ctrl.reset_pmds = 1;
} else {
/* keep same ovfl_pmds, ovfl_notify */
arg->ovfl_ctrl.notify_user = 1;
arg->ovfl_ctrl.block = 1;
arg->ovfl_ctrl.stop_monitoring = 1;
arg->ovfl_ctrl.reset_pmds = 0;
}
return 0;
}
static int
default_restart(struct task_struct *task, pfm_ovfl_ctrl_t *ctrl, void *buf, struct pt_regs *regs)
{
pfm_default_smpl_hdr_t *hdr;
hdr = (pfm_default_smpl_hdr_t *)buf;
hdr->hdr_count = 0UL;
hdr->hdr_cur_pos = (void *)((unsigned long)buf)+sizeof(*hdr);
ctrl->stop_monitoring = 0;
ctrl->reset_pmds = PFM_PMD_LONG_RESET;
return 0;
}
static int
default_exit(struct task_struct *task, void *buf, struct pt_regs *regs)
{
DPRINT(("[%d] exit(%p)\n", task->pid, buf));
return 0;
}
static pfm_buffer_fmt_t default_fmt={
.fmt_name = "default_format",
.fmt_uuid = PFM_DEFAULT_SMPL_UUID,
.fmt_arg_size = sizeof(pfm_default_smpl_arg_t),
.fmt_validate = default_validate,
.fmt_getsize = default_get_size,
.fmt_init = default_init,
.fmt_handler = default_handler,
.fmt_restart = default_restart,
.fmt_exit = default_exit,
};
static int __init
pfm_default_smpl_init_module(void)
{
int ret;
ret = pfm_register_buffer_fmt(&default_fmt);
if (ret == 0) {
printk("perfmon_default_smpl: %s v%u.%u registered\n",
default_fmt.fmt_name,
PFM_DEFAULT_SMPL_VERSION_MAJ,
PFM_DEFAULT_SMPL_VERSION_MIN);
} else {
printk("perfmon_default_smpl: %s cannot register ret=%d\n",
default_fmt.fmt_name,
ret);
}
return ret;
}
static void __exit
pfm_default_smpl_cleanup_module(void)
{
int ret;
ret = pfm_unregister_buffer_fmt(default_fmt.fmt_uuid);
printk("perfmon_default_smpl: unregister %s=%d\n", default_fmt.fmt_name, ret);
}
module_init(pfm_default_smpl_init_module);
module_exit(pfm_default_smpl_cleanup_module);
/* /*
* This file contains the architected PMU register description tables * This file contains the generic PMU register description tables
* and pmc checker used by perfmon.c. * and pmc checker used by perfmon.c.
* *
* Copyright (C) 2002 Hewlett Packard Co * Copyright (C) 2002-2003 Hewlett Packard Co
* Stephane Eranian <eranian@hpl.hp.com> * Stephane Eranian <eranian@hpl.hp.com>
*/ */
#define RDEP(x) (1UL<<(x)) #define RDEP(x) (1UL<<(x))
#if defined(CONFIG_ITANIUM) || defined (CONFIG_MCKINLEY) #if defined(CONFIG_ITANIUM) || defined (CONFIG_MCKINLEY)
...@@ -39,10 +41,13 @@ static pfm_reg_desc_t pmd_gen_desc[PMU_MAX_PMDS]={ ...@@ -39,10 +41,13 @@ static pfm_reg_desc_t pmd_gen_desc[PMU_MAX_PMDS]={
* impl_pmcs, impl_pmds are computed at runtime to minimize errors! * impl_pmcs, impl_pmds are computed at runtime to minimize errors!
*/ */
static pmu_config_t pmu_conf={ static pmu_config_t pmu_conf={
.disabled = 1, .pmu_name = "Generic",
.pmu_family = 0xff, /* any */
.enabled = 0,
.ovfl_val = (1UL << 32) - 1, .ovfl_val = (1UL << 32) - 1,
.num_ibrs = 8, .num_ibrs = 0, /* does not use */
.num_dbrs = 8, .num_dbrs = 0, /* does not use */
.pmd_desc = pfm_gen_pmd_desc, .pmd_desc = pfm_gen_pmd_desc,
.pmc_desc = pfm_gen_pmc_desc .pmc_desc = pfm_gen_pmc_desc
}; };
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* This file contains the Itanium PMU register description tables * This file contains the Itanium PMU register description tables
* and pmc checker used by perfmon.c. * and pmc checker used by perfmon.c.
* *
* Copyright (C) 2002 Hewlett Packard Co * Copyright (C) 2002-2003 Hewlett Packard Co
* Stephane Eranian <eranian@hpl.hp.com> * Stephane Eranian <eranian@hpl.hp.com>
*/ */
...@@ -12,8 +12,8 @@ ...@@ -12,8 +12,8 @@
#error "This file is only valid when CONFIG_ITANIUM is defined" #error "This file is only valid when CONFIG_ITANIUM is defined"
#endif #endif
static int pfm_ita_pmc_check(struct task_struct *task, unsigned int cnum, unsigned long *val, struct pt_regs *regs); static int pfm_ita_pmc_check(struct task_struct *task, pfm_context_t *ctx, unsigned int cnum, unsigned long *val, struct pt_regs *regs);
static int pfm_write_ibr_dbr(int mode, struct task_struct *task, void *arg, int count, struct pt_regs *regs); static int pfm_write_ibr_dbr(int mode, pfm_context_t *ctx, void *arg, int count, struct pt_regs *regs);
static pfm_reg_desc_t pfm_ita_pmc_desc[PMU_MAX_PMCS]={ static pfm_reg_desc_t pfm_ita_pmc_desc[PMU_MAX_PMCS]={
/* pmc0 */ { PFM_REG_CONTROL , 0, 0x1UL, -1UL, NULL, NULL, {0UL,0UL, 0UL, 0UL}, {0UL,0UL, 0UL, 0UL}}, /* pmc0 */ { PFM_REG_CONTROL , 0, 0x1UL, -1UL, NULL, NULL, {0UL,0UL, 0UL, 0UL}, {0UL,0UL, 0UL, 0UL}},
...@@ -59,19 +59,20 @@ static pfm_reg_desc_t pfm_ita_pmd_desc[PMU_MAX_PMDS]={ ...@@ -59,19 +59,20 @@ static pfm_reg_desc_t pfm_ita_pmd_desc[PMU_MAX_PMDS]={
* impl_pmcs, impl_pmds are computed at runtime to minimize errors! * impl_pmcs, impl_pmds are computed at runtime to minimize errors!
*/ */
static pmu_config_t pmu_conf={ static pmu_config_t pmu_conf={
.disabled = 1, .pmu_name = "Itanium",
.pmu_family = 0x7,
.enabled = 0,
.ovfl_val = (1UL << 32) - 1, .ovfl_val = (1UL << 32) - 1,
.pmd_desc = pfm_ita_pmd_desc,
.pmc_desc = pfm_ita_pmc_desc,
.num_ibrs = 8, .num_ibrs = 8,
.num_dbrs = 8, .num_dbrs = 8,
.pmd_desc = pfm_ita_pmd_desc, .use_rr_dbregs = 1 /* debug register are use for range retrictions */
.pmc_desc = pfm_ita_pmc_desc
}; };
static int static int
pfm_ita_pmc_check(struct task_struct *task, unsigned int cnum, unsigned long *val, struct pt_regs *regs) pfm_ita_pmc_check(struct task_struct *task, pfm_context_t *ctx, unsigned int cnum, unsigned long *val, struct pt_regs *regs)
{ {
pfm_context_t *ctx = task->thread.pfm_context;
int ret; int ret;
/* /*
...@@ -81,13 +82,13 @@ pfm_ita_pmc_check(struct task_struct *task, unsigned int cnum, unsigned long *va ...@@ -81,13 +82,13 @@ pfm_ita_pmc_check(struct task_struct *task, unsigned int cnum, unsigned long *va
if (cnum == 13 && ((*val & 0x1) == 0UL) && ctx->ctx_fl_using_dbreg == 0) { if (cnum == 13 && ((*val & 0x1) == 0UL) && ctx->ctx_fl_using_dbreg == 0) {
/* don't mix debug with perfmon */ /* don't mix debug with perfmon */
if ((task->thread.flags & IA64_THREAD_DBG_VALID) != 0) return -EINVAL; if (task && (task->thread.flags & IA64_THREAD_DBG_VALID) != 0) return -EINVAL;
/* /*
* a count of 0 will mark the debug registers as in use and also * a count of 0 will mark the debug registers as in use and also
* ensure that they are properly cleared. * ensure that they are properly cleared.
*/ */
ret = pfm_write_ibr_dbr(1, task, NULL, 0, regs); ret = pfm_write_ibr_dbr(1, ctx, NULL, 0, regs);
if (ret) return ret; if (ret) return ret;
} }
...@@ -98,13 +99,13 @@ pfm_ita_pmc_check(struct task_struct *task, unsigned int cnum, unsigned long *va ...@@ -98,13 +99,13 @@ pfm_ita_pmc_check(struct task_struct *task, unsigned int cnum, unsigned long *va
if (cnum == 11 && ((*val >> 28)& 0x1) == 0 && ctx->ctx_fl_using_dbreg == 0) { if (cnum == 11 && ((*val >> 28)& 0x1) == 0 && ctx->ctx_fl_using_dbreg == 0) {
/* don't mix debug with perfmon */ /* don't mix debug with perfmon */
if ((task->thread.flags & IA64_THREAD_DBG_VALID) != 0) return -EINVAL; if (task && (task->thread.flags & IA64_THREAD_DBG_VALID) != 0) return -EINVAL;
/* /*
* a count of 0 will mark the debug registers as in use and also * a count of 0 will mark the debug registers as in use and also
* ensure that they are properly cleared. * ensure that they are properly cleared.
*/ */
ret = pfm_write_ibr_dbr(0, task, NULL, 0, regs); ret = pfm_write_ibr_dbr(0, ctx, NULL, 0, regs);
if (ret) return ret; if (ret) return ret;
} }
return 0; return 0;
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* This file contains the McKinley PMU register description tables * This file contains the McKinley PMU register description tables
* and pmc checker used by perfmon.c. * and pmc checker used by perfmon.c.
* *
* Copyright (C) 2002 Hewlett Packard Co * Copyright (C) 2002-2003 Hewlett Packard Co
* Stephane Eranian <eranian@hpl.hp.com> * Stephane Eranian <eranian@hpl.hp.com>
*/ */
...@@ -12,9 +12,8 @@ ...@@ -12,9 +12,8 @@
#error "This file is only valid when CONFIG_MCKINLEY is defined" #error "This file is only valid when CONFIG_MCKINLEY is defined"
#endif #endif
static int pfm_mck_reserved(struct task_struct *task, unsigned int cnum, unsigned long *val, struct pt_regs *regs); static int pfm_mck_pmc_check(struct task_struct *task, pfm_context_t *ctx, unsigned int cnum, unsigned long *val, struct pt_regs *regs);
static int pfm_mck_pmc_check(struct task_struct *task, unsigned int cnum, unsigned long *val, struct pt_regs *regs); static int pfm_write_ibr_dbr(int mode, pfm_context_t *ctx, void *arg, int count, struct pt_regs *regs);
static int pfm_write_ibr_dbr(int mode, struct task_struct *task, void *arg, int count, struct pt_regs *regs);
static pfm_reg_desc_t pfm_mck_pmc_desc[PMU_MAX_PMCS]={ static pfm_reg_desc_t pfm_mck_pmc_desc[PMU_MAX_PMCS]={
/* pmc0 */ { PFM_REG_CONTROL , 0, 0x1UL, -1UL, NULL, NULL, {0UL,0UL, 0UL, 0UL}, {0UL,0UL, 0UL, 0UL}}, /* pmc0 */ { PFM_REG_CONTROL , 0, 0x1UL, -1UL, NULL, NULL, {0UL,0UL, 0UL, 0UL}, {0UL,0UL, 0UL, 0UL}},
...@@ -22,17 +21,17 @@ static pfm_reg_desc_t pfm_mck_pmc_desc[PMU_MAX_PMCS]={ ...@@ -22,17 +21,17 @@ static pfm_reg_desc_t pfm_mck_pmc_desc[PMU_MAX_PMCS]={
/* pmc2 */ { PFM_REG_CONTROL , 0, 0x0UL, -1UL, NULL, NULL, {0UL,0UL, 0UL, 0UL}, {0UL,0UL, 0UL, 0UL}}, /* pmc2 */ { PFM_REG_CONTROL , 0, 0x0UL, -1UL, NULL, NULL, {0UL,0UL, 0UL, 0UL}, {0UL,0UL, 0UL, 0UL}},
/* pmc3 */ { PFM_REG_CONTROL , 0, 0x0UL, -1UL, NULL, NULL, {0UL,0UL, 0UL, 0UL}, {0UL,0UL, 0UL, 0UL}}, /* pmc3 */ { PFM_REG_CONTROL , 0, 0x0UL, -1UL, NULL, NULL, {0UL,0UL, 0UL, 0UL}, {0UL,0UL, 0UL, 0UL}},
/* pmc4 */ { PFM_REG_COUNTING, 6, 0x0000000000800000UL, 0xfffff7fUL, NULL, pfm_mck_pmc_check, {RDEP(4),0UL, 0UL, 0UL}, {0UL,0UL, 0UL, 0UL}}, /* pmc4 */ { PFM_REG_COUNTING, 6, 0x0000000000800000UL, 0xfffff7fUL, NULL, pfm_mck_pmc_check, {RDEP(4),0UL, 0UL, 0UL}, {0UL,0UL, 0UL, 0UL}},
/* pmc5 */ { PFM_REG_COUNTING, 6, 0x0UL, 0xfffff7fUL, NULL, pfm_mck_reserved, {RDEP(5),0UL, 0UL, 0UL}, {0UL,0UL, 0UL, 0UL}}, /* pmc5 */ { PFM_REG_COUNTING, 6, 0x0UL, 0xfffff7fUL, NULL, pfm_mck_pmc_check, {RDEP(5),0UL, 0UL, 0UL}, {0UL,0UL, 0UL, 0UL}},
/* pmc6 */ { PFM_REG_COUNTING, 6, 0x0UL, 0xfffff7fUL, NULL, pfm_mck_reserved, {RDEP(6),0UL, 0UL, 0UL}, {0UL,0UL, 0UL, 0UL}}, /* pmc6 */ { PFM_REG_COUNTING, 6, 0x0UL, 0xfffff7fUL, NULL, pfm_mck_pmc_check, {RDEP(6),0UL, 0UL, 0UL}, {0UL,0UL, 0UL, 0UL}},
/* pmc7 */ { PFM_REG_COUNTING, 6, 0x0UL, 0xfffff7fUL, NULL, pfm_mck_reserved, {RDEP(7),0UL, 0UL, 0UL}, {0UL,0UL, 0UL, 0UL}}, /* pmc7 */ { PFM_REG_COUNTING, 6, 0x0UL, 0xfffff7fUL, NULL, pfm_mck_pmc_check, {RDEP(7),0UL, 0UL, 0UL}, {0UL,0UL, 0UL, 0UL}},
/* pmc8 */ { PFM_REG_CONFIG , 0, 0xffffffff3fffffffUL, 0xffffffff3fffffffUL, NULL, pfm_mck_pmc_check, {0UL,0UL, 0UL, 0UL}, {0UL,0UL, 0UL, 0UL}}, /* pmc8 */ { PFM_REG_CONFIG , 0, 0xffffffff3fffffffUL, 0xffffffff3ffffffbUL, NULL, pfm_mck_pmc_check, {0UL,0UL, 0UL, 0UL}, {0UL,0UL, 0UL, 0UL}},
/* pmc9 */ { PFM_REG_CONFIG , 0, 0xffffffff3ffffffcUL, 0xffffffff3fffffffUL, NULL, pfm_mck_pmc_check, {0UL,0UL, 0UL, 0UL}, {0UL,0UL, 0UL, 0UL}}, /* pmc9 */ { PFM_REG_CONFIG , 0, 0xffffffff3ffffffcUL, 0xffffffff3ffffffbUL, NULL, pfm_mck_pmc_check, {0UL,0UL, 0UL, 0UL}, {0UL,0UL, 0UL, 0UL}},
/* pmc10 */ { PFM_REG_MONITOR , 4, 0x0UL, 0xffffUL, NULL, pfm_mck_reserved, {RDEP(0)|RDEP(1),0UL, 0UL, 0UL}, {0UL,0UL, 0UL, 0UL}}, /* pmc10 */ { PFM_REG_MONITOR , 4, 0x0UL, 0xffffUL, NULL, pfm_mck_pmc_check, {RDEP(0)|RDEP(1),0UL, 0UL, 0UL}, {0UL,0UL, 0UL, 0UL}},
/* pmc11 */ { PFM_REG_MONITOR , 6, 0x0UL, 0x30f01cf, NULL, pfm_mck_reserved, {RDEP(2)|RDEP(3)|RDEP(17),0UL, 0UL, 0UL}, {0UL,0UL, 0UL, 0UL}}, /* pmc11 */ { PFM_REG_MONITOR , 6, 0x0UL, 0x30f01cf, NULL, pfm_mck_pmc_check, {RDEP(2)|RDEP(3)|RDEP(17),0UL, 0UL, 0UL}, {0UL,0UL, 0UL, 0UL}},
/* pmc12 */ { PFM_REG_MONITOR , 6, 0x0UL, 0xffffUL, NULL, pfm_mck_reserved, {RDEP(8)|RDEP(9)|RDEP(10)|RDEP(11)|RDEP(12)|RDEP(13)|RDEP(14)|RDEP(15)|RDEP(16),0UL, 0UL, 0UL}, {0UL,0UL, 0UL, 0UL}}, /* pmc12 */ { PFM_REG_MONITOR , 6, 0x0UL, 0xffffUL, NULL, pfm_mck_pmc_check, {RDEP(8)|RDEP(9)|RDEP(10)|RDEP(11)|RDEP(12)|RDEP(13)|RDEP(14)|RDEP(15)|RDEP(16),0UL, 0UL, 0UL}, {0UL,0UL, 0UL, 0UL}},
/* pmc13 */ { PFM_REG_CONFIG , 0, 0x00002078fefefefeUL, 0x1e00018181818UL, NULL, pfm_mck_pmc_check, {0UL,0UL, 0UL, 0UL}, {0UL,0UL, 0UL, 0UL}}, /* pmc13 */ { PFM_REG_CONFIG , 0, 0x00002078fefefefeUL, 0x1e00018181818UL, NULL, pfm_mck_pmc_check, {0UL,0UL, 0UL, 0UL}, {0UL,0UL, 0UL, 0UL}},
/* pmc14 */ { PFM_REG_CONFIG , 0, 0x0db60db60db60db6UL, 0x2492UL, NULL, pfm_mck_pmc_check, {0UL,0UL, 0UL, 0UL}, {0UL,0UL, 0UL, 0UL}}, /* pmc14 */ { PFM_REG_CONFIG , 0, 0x0db60db60db60db6UL, 0x2492UL, NULL, pfm_mck_pmc_check, {0UL,0UL, 0UL, 0UL}, {0UL,0UL, 0UL, 0UL}},
/* pmc15 */ { PFM_REG_CONFIG , 0, 0x00000000fffffff0UL, 0xfUL, NULL, pfm_mck_reserved, {0UL,0UL, 0UL, 0UL}, {0UL,0UL, 0UL, 0UL}}, /* pmc15 */ { PFM_REG_CONFIG , 0, 0x00000000fffffff0UL, 0xfUL, NULL, pfm_mck_pmc_check, {0UL,0UL, 0UL, 0UL}, {0UL,0UL, 0UL, 0UL}},
{ PFM_REG_END , 0, 0x0UL, -1UL, NULL, NULL, {0,}, {0,}}, /* end marker */ { PFM_REG_END , 0, 0x0UL, -1UL, NULL, NULL, {0,}, {0,}}, /* end marker */
}; };
...@@ -62,20 +61,22 @@ static pfm_reg_desc_t pfm_mck_pmd_desc[PMU_MAX_PMDS]={ ...@@ -62,20 +61,22 @@ static pfm_reg_desc_t pfm_mck_pmd_desc[PMU_MAX_PMDS]={
* impl_pmcs, impl_pmds are computed at runtime to minimize errors! * impl_pmcs, impl_pmds are computed at runtime to minimize errors!
*/ */
static pmu_config_t pmu_conf={ static pmu_config_t pmu_conf={
.disabled = 1, .pmu_name = "Itanium 2",
.pmu_family = 0x1f,
.enabled = 0,
.ovfl_val = (1UL << 47) - 1, .ovfl_val = (1UL << 47) - 1,
.pmd_desc = pfm_mck_pmd_desc,
.pmc_desc = pfm_mck_pmc_desc,
.num_ibrs = 8, .num_ibrs = 8,
.num_dbrs = 8, .num_dbrs = 8,
.pmd_desc = pfm_mck_pmd_desc, .use_rr_dbregs = 1 /* debug register are use for range retrictions */
.pmc_desc = pfm_mck_pmc_desc
}; };
/* /*
* PMC reserved fields must have their power-up values preserved * PMC reserved fields must have their power-up values preserved
*/ */
static int static int
pfm_mck_reserved(struct task_struct *task, unsigned int cnum, unsigned long *val, struct pt_regs *regs) pfm_mck_reserved(unsigned int cnum, unsigned long *val, struct pt_regs *regs)
{ {
unsigned long tmp1, tmp2, ival = *val; unsigned long tmp1, tmp2, ival = *val;
...@@ -87,21 +88,20 @@ pfm_mck_reserved(struct task_struct *task, unsigned int cnum, unsigned long *val ...@@ -87,21 +88,20 @@ pfm_mck_reserved(struct task_struct *task, unsigned int cnum, unsigned long *val
*val = tmp1 | tmp2; *val = tmp1 | tmp2;
DBprintk(("pmc[%d]=0x%lx, mask=0x%lx, reset=0x%lx, val=0x%lx\n", DPRINT(("pmc[%d]=0x%lx, mask=0x%lx, reset=0x%lx, val=0x%lx\n",
cnum, ival, PMC_RSVD_MASK(cnum), PMC_DFL_VAL(cnum), *val)); cnum, ival, PMC_RSVD_MASK(cnum), PMC_DFL_VAL(cnum), *val));
return 0; return 0;
} }
static int static int
pfm_mck_pmc_check(struct task_struct *task, unsigned int cnum, unsigned long *val, struct pt_regs *regs) pfm_mck_pmc_check(struct task_struct *task, pfm_context_t *ctx, unsigned int cnum, unsigned long *val, struct pt_regs *regs)
{ {
struct thread_struct *th = &task->thread;
pfm_context_t *ctx = task->thread.pfm_context;
int ret = 0, check_case1 = 0; int ret = 0, check_case1 = 0;
struct thread_struct *th = &task->thread;
unsigned long val8 = 0, val14 = 0, val13 = 0; unsigned long val8 = 0, val14 = 0, val13 = 0;
/* first preserve the reserved fields */ /* first preserve the reserved fields */
pfm_mck_reserved(task, cnum, val, regs); pfm_mck_reserved(cnum, val, regs);
/* /*
* we must clear the debug registers if any pmc13.ena_dbrpX bit is enabled * we must clear the debug registers if any pmc13.ena_dbrpX bit is enabled
...@@ -110,13 +110,13 @@ pfm_mck_pmc_check(struct task_struct *task, unsigned int cnum, unsigned long *va ...@@ -110,13 +110,13 @@ pfm_mck_pmc_check(struct task_struct *task, unsigned int cnum, unsigned long *va
if (cnum == 13 && (*val & (0xfUL << 45)) && ctx->ctx_fl_using_dbreg == 0) { if (cnum == 13 && (*val & (0xfUL << 45)) && ctx->ctx_fl_using_dbreg == 0) {
/* don't mix debug with perfmon */ /* don't mix debug with perfmon */
if ((task->thread.flags & IA64_THREAD_DBG_VALID) != 0) return -EINVAL; if (task && (task->thread.flags & IA64_THREAD_DBG_VALID) != 0) return -EINVAL;
/* /*
* a count of 0 will mark the debug registers as in use and also * a count of 0 will mark the debug registers as in use and also
* ensure that they are properly cleared. * ensure that they are properly cleared.
*/ */
ret = pfm_write_ibr_dbr(1, task, NULL, 0, regs); ret = pfm_write_ibr_dbr(1, ctx, NULL, 0, regs);
if (ret) return ret; if (ret) return ret;
} }
/* /*
...@@ -126,13 +126,13 @@ pfm_mck_pmc_check(struct task_struct *task, unsigned int cnum, unsigned long *va ...@@ -126,13 +126,13 @@ pfm_mck_pmc_check(struct task_struct *task, unsigned int cnum, unsigned long *va
if (cnum == 14 && ((*val & 0x2222) != 0x2222) && ctx->ctx_fl_using_dbreg == 0) { if (cnum == 14 && ((*val & 0x2222) != 0x2222) && ctx->ctx_fl_using_dbreg == 0) {
/* don't mix debug with perfmon */ /* don't mix debug with perfmon */
if ((task->thread.flags & IA64_THREAD_DBG_VALID) != 0) return -EINVAL; if (task && (task->thread.flags & IA64_THREAD_DBG_VALID) != 0) return -EINVAL;
/* /*
* a count of 0 will mark the debug registers as in use and also * a count of 0 will mark the debug registers as in use and also
* ensure that they are properly cleared. * ensure that they are properly cleared.
*/ */
ret = pfm_write_ibr_dbr(0, task, NULL, 0, regs); ret = pfm_write_ibr_dbr(0, ctx, NULL, 0, regs);
if (ret) return ret; if (ret) return ret;
} }
...@@ -141,17 +141,17 @@ pfm_mck_pmc_check(struct task_struct *task, unsigned int cnum, unsigned long *va ...@@ -141,17 +141,17 @@ pfm_mck_pmc_check(struct task_struct *task, unsigned int cnum, unsigned long *va
case 4: *val |= 1UL << 23; /* force power enable bit */ case 4: *val |= 1UL << 23; /* force power enable bit */
break; break;
case 8: val8 = *val; case 8: val8 = *val;
val13 = th->pmc[13]; val13 = th->pmcs[13];
val14 = th->pmc[14]; val14 = th->pmcs[14];
check_case1 = 1; check_case1 = 1;
break; break;
case 13: val8 = th->pmc[8]; case 13: val8 = th->pmcs[8];
val13 = *val; val13 = *val;
val14 = th->pmc[14]; val14 = th->pmcs[14];
check_case1 = 1; check_case1 = 1;
break; break;
case 14: val8 = th->pmc[13]; case 14: val8 = th->pmcs[13];
val13 = th->pmc[13]; val13 = th->pmcs[13];
val14 = *val; val14 = *val;
check_case1 = 1; check_case1 = 1;
break; break;
...@@ -165,7 +165,7 @@ pfm_mck_pmc_check(struct task_struct *task, unsigned int cnum, unsigned long *va ...@@ -165,7 +165,7 @@ pfm_mck_pmc_check(struct task_struct *task, unsigned int cnum, unsigned long *va
&& ((((val14>>1) & 0x3) == 0x2 || ((val14>>1) & 0x3) == 0x0) && ((((val14>>1) & 0x3) == 0x2 || ((val14>>1) & 0x3) == 0x0)
||(((val14>>4) & 0x3) == 0x2 || ((val14>>4) & 0x3) == 0x0)); ||(((val14>>4) & 0x3) == 0x2 || ((val14>>4) & 0x3) == 0x0));
if (ret) printk(KERN_DEBUG "perfmon: failure check_case1\n"); if (ret) printk("perfmon: failure check_case1\n");
} }
return ret ? -EINVAL : 0; return ret ? -EINVAL : 0;
......
...@@ -148,8 +148,8 @@ do_notify_resume_user (sigset_t *oldset, struct sigscratch *scr, long in_syscall ...@@ -148,8 +148,8 @@ do_notify_resume_user (sigset_t *oldset, struct sigscratch *scr, long in_syscall
#endif #endif
#ifdef CONFIG_PERFMON #ifdef CONFIG_PERFMON
if (current->thread.pfm_ovfl_block_reset) if (current->thread.pfm_needs_checking)
pfm_ovfl_block_reset(); pfm_handle_work();
#endif #endif
/* deal with pending signal delivery */ /* deal with pending signal delivery */
...@@ -387,16 +387,8 @@ copy_thread (int nr, unsigned long clone_flags, ...@@ -387,16 +387,8 @@ copy_thread (int nr, unsigned long clone_flags,
#endif #endif
#ifdef CONFIG_PERFMON #ifdef CONFIG_PERFMON
/*
* reset notifiers and owner check (may not have a perfmon context)
*/
atomic_set(&p->thread.pfm_notifiers_check, 0);
atomic_set(&p->thread.pfm_owners_check, 0);
/* clear list of sampling buffer to free for new task */
p->thread.pfm_smpl_buf_list = NULL;
if (current->thread.pfm_context) if (current->thread.pfm_context)
retval = pfm_inherit(p, child_ptregs); pfm_inherit(p, child_ptregs);
#endif #endif
return retval; return retval;
} }
...@@ -609,34 +601,6 @@ flush_thread (void) ...@@ -609,34 +601,6 @@ flush_thread (void)
ia64_drop_fpu(current); ia64_drop_fpu(current);
} }
#ifdef CONFIG_PERFMON
/*
* by the time we get here, the task is detached from the tasklist. This is important
* because it means that no other tasks can ever find it as a notified task, therfore there
* is no race condition between this code and let's say a pfm_context_create().
* Conversely, the pfm_cleanup_notifiers() cannot try to access a task's pfm context if this
* other task is in the middle of its own pfm_context_exit() because it would already be out of
* the task list. Note that this case is very unlikely between a direct child and its parents
* (if it is the notified process) because of the way the exit is notified via SIGCHLD.
*/
void
release_thread (struct task_struct *task)
{
if (task->thread.pfm_context)
pfm_context_exit(task);
if (atomic_read(&task->thread.pfm_notifiers_check) > 0)
pfm_cleanup_notifiers(task);
if (atomic_read(&task->thread.pfm_owners_check) > 0)
pfm_cleanup_owners(task);
if (task->thread.pfm_smpl_buf_list)
pfm_cleanup_smpl_buf(task);
}
#endif
/* /*
* Clean up state associated with current thread. This is called when * Clean up state associated with current thread. This is called when
* the thread calls exit(). * the thread calls exit().
...@@ -648,7 +612,7 @@ exit_thread (void) ...@@ -648,7 +612,7 @@ exit_thread (void)
#ifdef CONFIG_PERFMON #ifdef CONFIG_PERFMON
/* if needed, stop monitoring and flush state to perfmon context */ /* if needed, stop monitoring and flush state to perfmon context */
if (current->thread.pfm_context) if (current->thread.pfm_context)
pfm_flush_regs(current); pfm_exit_thread(current);
/* free debug register resources */ /* free debug register resources */
if (current->thread.flags & IA64_THREAD_DBG_VALID) if (current->thread.flags & IA64_THREAD_DBG_VALID)
......
...@@ -189,24 +189,15 @@ copy_siginfo_to_user (siginfo_t *to, siginfo_t *from) ...@@ -189,24 +189,15 @@ copy_siginfo_to_user (siginfo_t *to, siginfo_t *from)
err |= __put_user(from->si_addr, &to->si_addr); err |= __put_user(from->si_addr, &to->si_addr);
err |= __put_user(from->si_imm, &to->si_imm); err |= __put_user(from->si_imm, &to->si_imm);
break; break;
case __SI_CHLD >> 16:
err |= __put_user(from->si_utime, &to->si_utime);
err |= __put_user(from->si_stime, &to->si_stime);
err |= __put_user(from->si_status, &to->si_status);
case __SI_PROF >> 16:
err |= __put_user(from->si_uid, &to->si_uid);
err |= __put_user(from->si_pid, &to->si_pid);
if (from->si_code == PROF_OVFL) {
err |= __put_user(from->si_pfm_ovfl[0], &to->si_pfm_ovfl[0]);
err |= __put_user(from->si_pfm_ovfl[1], &to->si_pfm_ovfl[1]);
err |= __put_user(from->si_pfm_ovfl[2], &to->si_pfm_ovfl[2]);
err |= __put_user(from->si_pfm_ovfl[3], &to->si_pfm_ovfl[3]);
}
case __SI_TIMER >> 16: case __SI_TIMER >> 16:
err |= __put_user(from->si_tid, &to->si_tid); err |= __put_user(from->si_tid, &to->si_tid);
err |= __put_user(from->si_overrun, &to->si_overrun); err |= __put_user(from->si_overrun, &to->si_overrun);
err |= __put_user(from->si_value, &to->si_value); err |= __put_user(from->si_value, &to->si_value);
break; break;
case __SI_CHLD >> 16:
err |= __put_user(from->si_utime, &to->si_utime);
err |= __put_user(from->si_stime, &to->si_stime);
err |= __put_user(from->si_status, &to->si_status);
default: default:
err |= __put_user(from->si_uid, &to->si_uid); err |= __put_user(from->si_uid, &to->si_uid);
err |= __put_user(from->si_pid, &to->si_pid); err |= __put_user(from->si_pid, &to->si_pid);
...@@ -243,10 +234,6 @@ copy_siginfo_from_user (siginfo_t *to, siginfo_t *from) ...@@ -243,10 +234,6 @@ copy_siginfo_from_user (siginfo_t *to, siginfo_t *from)
to->si_code |= __SI_POLL; to->si_code |= __SI_POLL;
break; break;
case SIGPROF:
to->si_code |= __SI_PROF;
break;
default: default:
break; break;
} }
......
...@@ -72,7 +72,7 @@ ...@@ -72,7 +72,7 @@
#define IA64_PSR_BITS_TO_CLEAR (IA64_PSR_MFL | IA64_PSR_MFH | IA64_PSR_DB | IA64_PSR_LP | \ #define IA64_PSR_BITS_TO_CLEAR (IA64_PSR_MFL | IA64_PSR_MFH | IA64_PSR_DB | IA64_PSR_LP | \
IA64_PSR_TB | IA64_PSR_ID | IA64_PSR_DA | IA64_PSR_DD | \ IA64_PSR_TB | IA64_PSR_ID | IA64_PSR_DA | IA64_PSR_DD | \
IA64_PSR_SS | IA64_PSR_ED | IA64_PSR_IA) IA64_PSR_SS | IA64_PSR_ED | IA64_PSR_IA)
#define IA64_PSR_BITS_TO_SET (IA64_PSR_DFH) #define IA64_PSR_BITS_TO_SET (IA64_PSR_DFH | IA64_PSR_SP)
#define IA64_PSR_BE (__IA64_UL(1) << IA64_PSR_BE_BIT) #define IA64_PSR_BE (__IA64_UL(1) << IA64_PSR_BE_BIT)
#define IA64_PSR_UP (__IA64_UL(1) << IA64_PSR_UP_BIT) #define IA64_PSR_UP (__IA64_UL(1) << IA64_PSR_UP_BIT)
......
...@@ -14,20 +14,21 @@ ...@@ -14,20 +14,21 @@
#define PFM_READ_PMDS 0x03 #define PFM_READ_PMDS 0x03
#define PFM_STOP 0x04 #define PFM_STOP 0x04
#define PFM_START 0x05 #define PFM_START 0x05
#define PFM_ENABLE 0x06 #define PFM_ENABLE 0x06 /* obsolete */
#define PFM_DISABLE 0x07 #define PFM_DISABLE 0x07 /* obsolete */
#define PFM_CREATE_CONTEXT 0x08 #define PFM_CREATE_CONTEXT 0x08
#define PFM_DESTROY_CONTEXT 0x09 #define PFM_DESTROY_CONTEXT 0x09 /* obsolete use close() */
#define PFM_RESTART 0x0a #define PFM_RESTART 0x0a
#define PFM_PROTECT_CONTEXT 0x0b #define PFM_PROTECT_CONTEXT 0x0b /* obsolete */
#define PFM_GET_FEATURES 0x0c #define PFM_GET_FEATURES 0x0c
#define PFM_DEBUG 0x0d #define PFM_DEBUG 0x0d
#define PFM_UNPROTECT_CONTEXT 0x0e #define PFM_UNPROTECT_CONTEXT 0x0e /* obsolete */
#define PFM_GET_PMC_RESET_VAL 0x0f #define PFM_GET_PMC_RESET_VAL 0x0f
#define PFM_LOAD_CONTEXT 0x10
#define PFM_UNLOAD_CONTEXT 0x11
/* /*
* CPU model specific commands (may not be supported on all models) * PMU model specific commands (may not be supported on all PMU models)
*/ */
#define PFM_WRITE_IBRS 0x20 #define PFM_WRITE_IBRS 0x20
#define PFM_WRITE_DBRS 0x21 #define PFM_WRITE_DBRS 0x21
...@@ -35,19 +36,21 @@ ...@@ -35,19 +36,21 @@
/* /*
* context flags * context flags
*/ */
#define PFM_FL_INHERIT_NONE 0x00 /* never inherit a context across fork (default) */ #define PFM_FL_NOTIFY_BLOCK 0x01 /* block task on user level notifications */
#define PFM_FL_INHERIT_ONCE 0x01 /* clone pfm_context only once across fork() */ #define PFM_FL_SYSTEM_WIDE 0x02 /* create a system wide context */
#define PFM_FL_INHERIT_ALL 0x02 /* always clone pfm_context across fork() */ #define PFM_FL_UNSECURE 0x04 /* allow unsecure monitoring for non self-monitoring task */
#define PFM_FL_NOTIFY_BLOCK 0x04 /* block task on user level notifications */ #define PFM_FL_OVFL_NO_MSG 0x80 /* do not post overflow/end messages for notification */
#define PFM_FL_SYSTEM_WIDE 0x08 /* create a system wide context */
#define PFM_FL_EXCL_IDLE 0x20 /* exclude idle task from system wide session */ /*
#define PFM_FL_UNSECURE 0x40 /* allow unsecure monitoring for non self-monitoring task */ * event set flags
*/
#define PFM_SETFL_EXCL_IDLE 0x01 /* exclude idle task (syswide only) XXX: DO NOT USE YET */
/* /*
* PMC flags * PMC flags
*/ */
#define PFM_REGFL_OVFL_NOTIFY 0x1 /* send notification on overflow */ #define PFM_REGFL_OVFL_NOTIFY 0x1 /* send notification on overflow */
#define PFM_REGFL_RANDOM 0x2 /* randomize sampling periods */ #define PFM_REGFL_RANDOM 0x2 /* randomize sampling interval */
/* /*
* PMD/PMC/IBR/DBR return flags (ignored on input) * PMD/PMC/IBR/DBR return flags (ignored on input)
...@@ -55,26 +58,24 @@ ...@@ -55,26 +58,24 @@
* Those flags are used on output and must be checked in case EAGAIN is returned * Those flags are used on output and must be checked in case EAGAIN is returned
* by any of the calls using a pfarg_reg_t or pfarg_dbreg_t structure. * by any of the calls using a pfarg_reg_t or pfarg_dbreg_t structure.
*/ */
#define PFM_REG_RETFL_NOTAVAIL (1U<<31) /* set if register is implemented but not available */ #define PFM_REG_RETFL_NOTAVAIL (1UL<<31) /* set if register is implemented but not available */
#define PFM_REG_RETFL_EINVAL (1U<<30) /* set if register entry is invalid */ #define PFM_REG_RETFL_EINVAL (1UL<<30) /* set if register entry is invalid */
#define PFM_REG_RETFL_MASK (PFM_REG_RETFL_NOTAVAIL|PFM_REG_RETFL_EINVAL) #define PFM_REG_RETFL_MASK (PFM_REG_RETFL_NOTAVAIL|PFM_REG_RETFL_EINVAL)
#define PFM_REG_HAS_ERROR(flag) (((flag) & PFM_REG_RETFL_MASK) != 0) #define PFM_REG_HAS_ERROR(flag) (((flag) & PFM_REG_RETFL_MASK) != 0)
typedef unsigned char pfm_uuid_t[16]; /* custom sampling buffer identifier type */
/* /*
* Request structure used to define a context * Request structure used to define a context
*/ */
typedef struct { typedef struct {
unsigned long ctx_smpl_entries; /* how many entries in sampling buffer */ pfm_uuid_t ctx_smpl_buf_id; /* which buffer format to use (if needed) */
unsigned long ctx_smpl_regs[4]; /* which pmds to record on overflow */ unsigned long ctx_flags; /* noblock/block */
unsigned int ctx_nextra_sets; /* number of extra event sets (you always get 1) */
pid_t ctx_notify_pid; /* which process to notify on overflow */ int ctx_fd; /* return arg: unique identification for context */
int ctx_flags; /* noblock/block, inherit flags */ void *ctx_smpl_vaddr; /* return arg: virtual address of sampling buffer, is used */
void *ctx_smpl_vaddr; /* returns address of BTB buffer */ unsigned long ctx_reserved[11]; /* for future use */
unsigned long ctx_cpu_mask; /* on which CPU to enable perfmon (systemwide) */
unsigned long reserved[8]; /* for future use */
} pfarg_context_t; } pfarg_context_t;
/* /*
...@@ -82,126 +83,172 @@ typedef struct { ...@@ -82,126 +83,172 @@ typedef struct {
*/ */
typedef struct { typedef struct {
unsigned int reg_num; /* which register */ unsigned int reg_num; /* which register */
unsigned int reg_flags; /* PMC: notify/don't notify. PMD/PMC: return flags */ unsigned int reg_set; /* event set for this register */
unsigned long reg_value; /* configuration (PMC) or initial value (PMD) */
unsigned long reg_long_reset; /* reset after sampling buffer overflow (large) */ unsigned long reg_value; /* initial pmc/pmd value */
unsigned long reg_short_reset;/* reset after counter overflow (small) */ unsigned long reg_flags; /* input: pmc/pmd flags, return: reg error */
unsigned long reg_long_reset; /* reset after buffer overflow notification */
unsigned long reg_short_reset; /* reset after counter overflow */
unsigned long reg_reset_pmds[4]; /* which other counters to reset on overflow */ unsigned long reg_reset_pmds[4]; /* which other counters to reset on overflow */
unsigned long reg_random_seed; /* seed value when randomization is used */ unsigned long reg_random_seed; /* seed value when randomization is used */
unsigned long reg_random_mask; /* bitmask used to limit random value */ unsigned long reg_random_mask; /* bitmask used to limit random value */
unsigned long reg_last_reset_value;/* last value used to reset the PMD (PFM_READ_PMDS) */ unsigned long reg_last_reset_val;/* return: PMD last reset value */
unsigned long reg_smpl_pmds[4]; /* which pmds are accessed when PMC overflows */
unsigned long reg_smpl_eventid; /* opaque sampling event identifier */
unsigned long reserved[13]; /* for future use */ unsigned long reserved[3]; /* for future use */
} pfarg_reg_t; } pfarg_reg_t;
typedef struct { typedef struct {
unsigned int dbreg_num; /* which register */ unsigned int dbreg_num; /* which debug register */
unsigned int dbreg_flags; /* dbregs return flags */ unsigned int dbreg_set; /* event set for this register */
unsigned long dbreg_value; /* configuration (PMC) or initial value (PMD) */ unsigned long dbreg_value; /* value for debug register */
unsigned long reserved[6]; unsigned long dbreg_flags; /* return: dbreg error */
unsigned long dbreg_reserved[1]; /* for future use */
} pfarg_dbreg_t; } pfarg_dbreg_t;
typedef struct { typedef struct {
unsigned int ft_version; /* perfmon: major [16-31], minor [0-15] */ unsigned int ft_version; /* perfmon: major [16-31], minor [0-15] */
unsigned int ft_smpl_version;/* sampling format: major [16-31], minor [0-15] */ unsigned int ft_reserved; /* reserved for future use */
unsigned long reserved[4]; /* for future use */ unsigned long reserved[4]; /* for future use */
} pfarg_features_t; } pfarg_features_t;
/*
* This header is at the beginning of the sampling buffer returned to the user.
* It is exported as Read-Only at this point. It is directly followed by the
* first record.
*/
typedef struct { typedef struct {
unsigned int hdr_version; /* contains perfmon version (smpl format diffs) */ pid_t load_pid; /* process to load the context into */
unsigned int reserved; unsigned int load_set; /* first event set to load */
unsigned long hdr_entry_size; /* size of one entry in bytes */ unsigned long load_reserved[2]; /* for future use */
unsigned long hdr_count; /* how many valid entries */ } pfarg_load_t;
unsigned long hdr_pmds[4]; /* which pmds are recorded */
} perfmon_smpl_hdr_t; typedef struct {
int msg_type; /* generic message header */
int msg_ctx_fd; /* generic message header */
unsigned long msg_tstamp; /* for perf tuning */
unsigned int msg_active_set; /* active set at the time of overflow */
unsigned long msg_ovfl_pmds[4]; /* which PMDs overflowed */
} pfm_ovfl_msg_t;
typedef struct {
int msg_type; /* generic message header */
int msg_ctx_fd; /* generic message header */
unsigned long msg_tstamp; /* for perf tuning */
} pfm_end_msg_t;
typedef struct {
int msg_type; /* type of the message */
int msg_ctx_fd; /* unique identifier for the context */
unsigned long msg_tstamp; /* for perf tuning */
} pfm_gen_msg_t;
#define PFM_MSG_OVFL 1 /* an overflow happened */
#define PFM_MSG_END 2 /* task to which context was attached ended */
typedef union {
pfm_ovfl_msg_t pfm_ovfl_msg;
pfm_end_msg_t pfm_end_msg;
pfm_gen_msg_t pfm_gen_msg;
} pfm_msg_t;
/* /*
* Define the version numbers for both perfmon as a whole and the sampling buffer format. * Define the version numbers for both perfmon as a whole and the sampling buffer format.
*/ */
#define PFM_VERSION_MAJ 1U #define PFM_VERSION_MAJ 2U
#define PFM_VERSION_MIN 4U #define PFM_VERSION_MIN 0U
#define PFM_SMPL_HDR_VERSION_MAJ 2U
#define PFM_SMPL_HDR_VERSION_MIN 0U
#define PFM_VERSION (((PFM_VERSION_MAJ&0xffff)<<16)|(PFM_VERSION_MIN & 0xffff)) #define PFM_VERSION (((PFM_VERSION_MAJ&0xffff)<<16)|(PFM_VERSION_MIN & 0xffff))
#define PFM_SMPL_VERSION_MAJ 1U
#define PFM_SMPL_VERSION_MIN 0U
#define PFM_SMPL_VERSION (((PFM_SMPL_VERSION_MAJ&0xffff)<<16)|(PFM_SMPL_VERSION_MIN & 0xffff))
#define PFM_VERSION_MAJOR(x) (((x)>>16) & 0xffff) #define PFM_VERSION_MAJOR(x) (((x)>>16) & 0xffff)
#define PFM_VERSION_MINOR(x) ((x) & 0xffff) #define PFM_VERSION_MINOR(x) ((x) & 0xffff)
/* /*
* Entry header in the sampling buffer. The header is directly followed * miscellaneous architected definitions
* with the PMDs saved in increasing index order: PMD4, PMD5, .... How
* many PMDs are present is determined by the user program during
* context creation.
*
* XXX: in this version of the entry, only up to 64 registers can be
* recorded. This should be enough for quite some time. Always check
* sampling format before parsing entries!
*
* In the case where multiple counters overflow at the same time, the
* last_reset_value member indicates the initial value of the PMD with
* the smallest index. For instance, if PMD2 and PMD5 have overflowed,
* the last_reset_value member contains the initial value of PMD2.
*/ */
typedef struct { #define PMU_FIRST_COUNTER 4 /* first counting monitor (PMC/PMD) */
int pid; /* identification of process */ #define PMU_MAX_PMCS 256 /* maximum architected number of PMC registers */
int cpu; /* which cpu was used */ #define PMU_MAX_PMDS 256 /* maximum architected number of PMD registers */
unsigned long last_reset_value; /* initial value of counter that overflowed */
unsigned long stamp; /* timestamp */
unsigned long ip; /* where did the overflow interrupt happened */
unsigned long regs; /* bitmask of which registers overflowed */
unsigned long reserved; /* unused */
} perfmon_smpl_entry_t;
extern long perfmonctl(pid_t pid, int cmd, void *arg, int narg);
#ifdef __KERNEL__ #ifdef __KERNEL__
typedef struct { extern long perfmonctl(int fd, int cmd, void *arg, int narg);
void (*handler)(int irq, void *arg, struct pt_regs *regs);
} pfm_intr_handler_desc_t;
extern void pfm_save_regs (struct task_struct *); extern void pfm_save_regs (struct task_struct *);
extern void pfm_load_regs (struct task_struct *); extern void pfm_load_regs (struct task_struct *);
extern int pfm_inherit (struct task_struct *, struct pt_regs *); extern void pfm_exit_thread(struct task_struct *);
extern void pfm_context_exit (struct task_struct *);
extern void pfm_flush_regs (struct task_struct *);
extern void pfm_cleanup_notifiers (struct task_struct *);
extern void pfm_cleanup_owners (struct task_struct *);
extern int pfm_use_debug_registers(struct task_struct *); extern int pfm_use_debug_registers(struct task_struct *);
extern int pfm_release_debug_registers(struct task_struct *); extern int pfm_release_debug_registers(struct task_struct *);
extern int pfm_cleanup_smpl_buf(struct task_struct *);
extern void pfm_syst_wide_update_task(struct task_struct *, unsigned long info, int is_ctxswin); extern void pfm_syst_wide_update_task(struct task_struct *, unsigned long info, int is_ctxswin);
extern void pfm_ovfl_block_reset(void); extern void pfm_inherit(struct task_struct *task, struct pt_regs *regs);
extern void pfm_init_percpu(void); extern void pfm_init_percpu(void);
extern void pfm_handle_work(void);
/* /*
* hooks to allow VTune/Prospect to cooperate with perfmon. * Reset PMD register flags
* (reserved for system wide monitoring modules only)
*/ */
extern int pfm_install_alternate_syswide_subsystem(pfm_intr_handler_desc_t *h); #define PFM_PMD_NO_RESET 0
extern int pfm_remove_alternate_syswide_subsystem(pfm_intr_handler_desc_t *h); #define PFM_PMD_LONG_RESET 1
#define PFM_PMD_SHORT_RESET 2
typedef struct {
unsigned int notify_user:1; /* notify user program of overflow */
unsigned int reset_pmds :2; /* PFM_PMD_NO_RESET, PFM_PMD_LONG_RESET, PFM_PMD_SHORT_RESET */
unsigned int block:1; /* block monitored task on kernel exit */
unsigned int stop_monitoring:1; /* will mask monitoring via PMCx.plm */
unsigned int reserved:26; /* for future use */
} pfm_ovfl_ctrl_t;
typedef struct {
unsigned long ovfl_pmds[4]; /* bitmask of overflowed pmds */
unsigned long ovfl_notify[4]; /* bitmask of overflow pmds which asked for notification */
unsigned long pmd_value; /* current 64-bit value of 1st pmd which overflowed */
unsigned long pmd_last_reset; /* last reset value of 1st pmd which overflowed */
unsigned long pmd_eventid; /* eventid associated with 1st pmd which overflowed */
unsigned int active_set; /* event set active at the time of the overflow */
unsigned int reserved1;
unsigned long smpl_pmds[4];
unsigned long smpl_pmds_values[PMU_MAX_PMDS];
pfm_ovfl_ctrl_t ovfl_ctrl; /* return: perfmon controls to set by handler */
} pfm_ovfl_arg_t;
typedef struct _pfm_buffer_fmt_t {
char *fmt_name;
pfm_uuid_t fmt_uuid;
size_t fmt_arg_size;
unsigned long fmt_flags;
int (*fmt_validate)(struct task_struct *task, unsigned int flags, int cpu, void *arg);
int (*fmt_getsize)(struct task_struct *task, unsigned int flags, int cpu, void *arg, unsigned long *size);
int (*fmt_init)(struct task_struct *task, void *buf, unsigned int flags, int cpu, void *arg);
int (*fmt_handler)(struct task_struct *task, void *buf, pfm_ovfl_arg_t *arg, struct pt_regs *regs);
int (*fmt_restart)(struct task_struct *task, pfm_ovfl_ctrl_t *ctrl, void *buf, struct pt_regs *regs);
int (*fmt_restart_active)(struct task_struct *task, pfm_ovfl_ctrl_t *ctrl, void *buf, struct pt_regs *regs);
int (*fmt_exit)(struct task_struct *task, void *buf, struct pt_regs *regs);
struct _pfm_buffer_fmt_t *fmt_next;
struct _pfm_buffer_fmt_t *fmt_prev;
} pfm_buffer_fmt_t;
extern int pfm_register_buffer_fmt(pfm_buffer_fmt_t *fmt);
extern int pfm_unregister_buffer_fmt(pfm_uuid_t uuid);
/*
* perfmon interface exported to modules
*/
extern long pfm_mod_fast_read_pmds(struct task_struct *, unsigned long mask[4], unsigned long *addr, struct pt_regs *regs);
extern long pfm_mod_read_pmds(struct task_struct *, pfarg_reg_t *req, unsigned int nreq, struct pt_regs *regs);
extern long pfm_mod_write_pmcs(struct task_struct *, pfarg_reg_t *req, unsigned int nreq, struct pt_regs *regs);
/* /*
* describe the content of the local_cpu_date->pfm_syst_info field * describe the content of the local_cpu_date->pfm_syst_info field
*/ */
#define PFM_CPUINFO_SYST_WIDE 0x1 /* if set a system wide session exist */ #define PFM_CPUINFO_SYST_WIDE 0x1 /* if set a system wide session exists */
#define PFM_CPUINFO_DCR_PP 0x2 /* if set the system wide session has started */ #define PFM_CPUINFO_DCR_PP 0x2 /* if set the system wide session has started */
#define PFM_CPUINFO_EXCL_IDLE 0x4 /* the system wide session excludes the idle task */ #define PFM_CPUINFO_EXCL_IDLE 0x4 /* the system wide session excludes the idle task */
#define PFM_CPUINFO_CLEAR(v) __get_cpu_var(pfm_syst_info) &= ~(v)
#define PFM_CPUINFO_SET(v) __get_cpu_var(pfm_syst_info) |= (v)
#endif /* __KERNEL__ */ #endif /* __KERNEL__ */
#endif /* _ASM_IA64_PERFMON_H */ #endif /* _ASM_IA64_PERFMON_H */
/*
* Copyright (C) 2002-2003 Hewlett-Packard Co
* Stephane Eranian <eranian@hpl.hp.com>
*
* This file implements the default sampling buffer format
* for Linux/ia64 perfmon subsystem.
*/
#ifndef __PERFMON_DEFAULT_SMPL_H__
#define __PERFMON_DEFAULT_SMPL_H__ 1
#define PFM_DEFAULT_SMPL_UUID { \
0x4d, 0x72, 0xbe, 0xc0, 0x06, 0x64, 0x41, 0x43, 0x82, 0xb4, 0xd3, 0xfd, 0x27, 0x24, 0x3c, 0x97}
/*
* format specific parameters (passed at context creation)
*/
typedef struct {
unsigned long buf_size; /* size of the buffer in bytes */
unsigned long reserved[3]; /* for future use */
} pfm_default_smpl_arg_t;
/*
* combined context+format specific structure. Can be passed
* to PFM_CONTEXT_CREATE
*/
typedef struct {
pfarg_context_t ctx_arg;
pfm_default_smpl_arg_t buf_arg;
} pfm_default_smpl_ctx_arg_t;
/*
* This header is at the beginning of the sampling buffer returned to the user.
* It is directly followed by the first record.
*/
typedef struct {
unsigned long hdr_count; /* how many valid entries */
void *hdr_cur_pos; /* current position in the buffer */
void *hdr_last_pos; /* first byte beyond buffer */
unsigned long hdr_overflows; /* how many times the buffer overflowed */
unsigned long hdr_buf_size; /* how many bytes in the buffer */
unsigned int hdr_version; /* contains perfmon version (smpl format diffs) */
unsigned int hdr_reserved1; /* for future use */
unsigned long hdr_reserved[10]; /* for future use */
} pfm_default_smpl_hdr_t;
/*
* Entry header in the sampling buffer. The header is directly followed
* with the PMDs saved in increasing index order: PMD4, PMD5, .... How
* many PMDs are present depends on how the session was programmed.
*
* XXX: in this version of the entry, only up to 64 registers can be
* recorded. This should be enough for quite some time. Always check
* sampling format before parsing entries!
*
* In the case where multiple counters overflow at the same time, the
* last_reset_value member indicates the initial value of the
* overflowed PMD with the smallest index. For instance, if PMD2 and
* PMD5 have overflowed, the last_reset_value member contains the
* initial value of PMD2.
*/
typedef struct {
int pid; /* current process at PMU interrupt point */
int cpu; /* cpu on which the overfow occured */
unsigned long last_reset_val; /* initial value of 1st overflowed PMD */
unsigned long ip; /* where did the overflow interrupt happened */
unsigned long ovfl_pmds; /* which PMDS registers overflowed (64 max) */
unsigned long tstamp; /* ar.itc on the CPU that took the overflow */
unsigned int set; /* event set active when overflow ocurred */
unsigned int reserved1; /* for future use */
} pfm_default_smpl_entry_t;
#define PFM_DEFAULT_MAX_PMDS 64 /* how many pmds supported by data structures (sizeof(unsigned long) */
#define PFM_DEFAULT_MAX_ENTRY_SIZE (sizeof(pfm_default_smpl_entry_t)+(sizeof(unsigned long)*PFM_DEFAULT_MAX_PMDS))
#define PFM_DEFAULT_SMPL_MIN_BUF_SIZE (sizeof(pfm_default_smpl_hdr_t)+PFM_DEFAULT_MAX_ENTRY_SIZE)
#define PFM_DEFAULT_SMPL_VERSION_MAJ 2U
#define PFM_DEFAULT_SMPL_VERSION_MIN 0U
#define PFM_DEFAULT_SMPL_VERSION (((PFM_DEFAULT_SMPL_VERSION_MAJ&0xffff)<<16)|(PFM_DEFAULT_SMPL_VERSION_MIN & 0xffff))
#endif /* __PERFMON_DEFAULT_SMPL_H__ */
...@@ -258,20 +258,14 @@ struct thread_struct { ...@@ -258,20 +258,14 @@ struct thread_struct {
# define INIT_THREAD_IA32 # define INIT_THREAD_IA32
#endif /* CONFIG_IA32_SUPPORT */ #endif /* CONFIG_IA32_SUPPORT */
#ifdef CONFIG_PERFMON #ifdef CONFIG_PERFMON
__u64 pmc[IA64_NUM_PMC_REGS]; __u64 pmcs[IA64_NUM_PMC_REGS];
__u64 pmd[IA64_NUM_PMD_REGS]; __u64 pmds[IA64_NUM_PMD_REGS];
unsigned long pfm_ovfl_block_reset;/* non-zero if we need to block or reset regs on ovfl */
void *pfm_context; /* pointer to detailed PMU context */ void *pfm_context; /* pointer to detailed PMU context */
atomic_t pfm_notifiers_check; /* when >0, will cleanup ctx_notify_task in tasklist */ unsigned long pfm_needs_checking; /* when >0, pending perfmon work on kernel exit */
atomic_t pfm_owners_check; /* when >0, will cleanup ctx_owner in tasklist */ # define INIT_THREAD_PM .pmcs = {0UL, }, \
void *pfm_smpl_buf_list; /* list of sampling buffers to vfree */ .pmds = {0UL, }, \
# define INIT_THREAD_PM .pmc = {0, }, \
.pmd = {0, }, \
.pfm_ovfl_block_reset = 0, \
.pfm_context = NULL, \ .pfm_context = NULL, \
.pfm_notifiers_check = { 0 }, \ .pfm_needs_checking = 0UL,
.pfm_owners_check = { 0 }, \
.pfm_smpl_buf_list = NULL,
#else #else
# define INIT_THREAD_PM # define INIT_THREAD_PM
#endif #endif
...@@ -326,11 +320,7 @@ struct task_struct; ...@@ -326,11 +320,7 @@ struct task_struct;
* parent of DEAD_TASK has collected the exist status of the task via * parent of DEAD_TASK has collected the exist status of the task via
* wait(). * wait().
*/ */
#ifdef CONFIG_PERFMON #define release_thread(dead_task)
extern void release_thread (struct task_struct *task);
#else
# define release_thread(dead_task)
#endif
/* Prepare to copy thread state - unlazy all lazy status */ /* Prepare to copy thread state - unlazy all lazy status */
#define prepare_to_copy(tsk) do { } while (0) #define prepare_to_copy(tsk) do { } while (0)
......
...@@ -69,13 +69,6 @@ typedef struct siginfo { ...@@ -69,13 +69,6 @@ typedef struct siginfo {
long _band; /* POLL_IN, POLL_OUT, POLL_MSG (XPG requires a "long") */ long _band; /* POLL_IN, POLL_OUT, POLL_MSG (XPG requires a "long") */
int _fd; int _fd;
} _sigpoll; } _sigpoll;
/* SIGPROF */
struct {
pid_t _pid; /* which child */
uid_t _uid; /* sender's uid */
unsigned long _pfm_ovfl_counters[4]; /* which PMU counter overflowed */
} _sigprof;
} _sifields; } _sifields;
} siginfo_t; } siginfo_t;
...@@ -94,14 +87,6 @@ typedef struct siginfo { ...@@ -94,14 +87,6 @@ typedef struct siginfo {
#define __ISR_VALID_BIT 0 #define __ISR_VALID_BIT 0
#define __ISR_VALID (1 << __ISR_VALID_BIT) #define __ISR_VALID (1 << __ISR_VALID_BIT)
/*
* si_code values
* Positive values for kernel-generated signals.
*/
#ifdef __KERNEL__
#define __SI_PROF (6 << 16)
#endif
/* /*
* SIGILL si_codes * SIGILL si_codes
*/ */
...@@ -137,11 +122,6 @@ typedef struct siginfo { ...@@ -137,11 +122,6 @@ typedef struct siginfo {
#undef NSIGTRAP #undef NSIGTRAP
#define NSIGTRAP 4 #define NSIGTRAP 4
/*
* SIGPROF si_codes
*/
#define PROF_OVFL (__SI_PROF|1) /* some counters overflowed */
#ifdef __KERNEL__ #ifdef __KERNEL__
#include <linux/string.h> #include <linux/string.h>
...@@ -151,8 +131,8 @@ copy_siginfo (siginfo_t *to, siginfo_t *from) ...@@ -151,8 +131,8 @@ copy_siginfo (siginfo_t *to, siginfo_t *from)
if (from->si_code < 0) if (from->si_code < 0)
memcpy(to, from, sizeof(siginfo_t)); memcpy(to, from, sizeof(siginfo_t));
else else
/* _sigprof is currently the largest know union member */ /* _sigchld is currently the largest know union member */
memcpy(to, from, 4*sizeof(int) + sizeof(from->_sifields._sigprof)); memcpy(to, from, 4*sizeof(int) + sizeof(from->_sifields._sigchld));
} }
extern int copy_siginfo_from_user(siginfo_t *to, siginfo_t *from); extern int copy_siginfo_from_user(siginfo_t *to, siginfo_t *from);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment