mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-05 08:07:10 +07:00
88a486132d
The 24x7 counters in Powerpc allow monitoring a large number of counters simultaneously. They also allow reading several counters in a single HCALL so we can get a more consistent snapshot of the system. Use the PMU's transaction interface to monitor and read several event counters at once. The idea is that users can group several 24x7 events into a single group of events. We use the following logic to submit the group of events to the PMU and read the values: pmu->start_txn() // Initialize before first event for each event in group pmu->read(event); // Queue each event to be read pmu->commit_txn() // Read/update all queuedcounters The ->commit_txn() also updates the event counts in the respective perf_event objects. The perf subsystem can then directly get the event counts from the perf_event and can avoid submitting a new ->read() request to the PMU. Thanks to input from Peter Zijlstra. Signed-off-by: Sukadev Bhattiprolu <sukadev@linux.vnet.ibm.com> Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org> Acked-by: Michael Ellerman <mpe@ellerman.id.au> Cc: Arnaldo Carvalho de Melo <acme@kernel.org> Cc: Arnaldo Carvalho de Melo <acme@redhat.com> Cc: Jiri Olsa <jolsa@redhat.com> Cc: Linus Torvalds <torvalds@linux-foundation.org> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Stephane Eranian <eranian@google.com> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: Vince Weaver <vincent.weaver@maine.edu> Link: http://lkml.kernel.org/r/1441336073-22750-10-git-send-email-sukadev@linux.vnet.ibm.com Signed-off-by: Ingo Molnar <mingo@kernel.org>
1473 lines
36 KiB
C
1473 lines
36 KiB
C
/*
|
|
* Hypervisor supplied "24x7" performance counter support
|
|
*
|
|
* Author: Cody P Schafer <cody@linux.vnet.ibm.com>
|
|
* Copyright 2014 IBM Corporation.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version
|
|
* 2 of the License, or (at your option) any later version.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "hv-24x7: " fmt
|
|
|
|
#include <linux/perf_event.h>
|
|
#include <linux/rbtree.h>
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/vmalloc.h>
|
|
|
|
#include <asm/firmware.h>
|
|
#include <asm/hvcall.h>
|
|
#include <asm/io.h>
|
|
#include <linux/byteorder/generic.h>
|
|
|
|
#include "hv-24x7.h"
|
|
#include "hv-24x7-catalog.h"
|
|
#include "hv-common.h"
|
|
|
|
static const char *event_domain_suffix(unsigned domain)
|
|
{
|
|
switch (domain) {
|
|
#define DOMAIN(n, v, x, c) \
|
|
case HV_PERF_DOMAIN_##n: \
|
|
return "__" #n;
|
|
#include "hv-24x7-domains.h"
|
|
#undef DOMAIN
|
|
default:
|
|
WARN(1, "unknown domain %d\n", domain);
|
|
return "__UNKNOWN_DOMAIN_SUFFIX";
|
|
}
|
|
}
|
|
|
|
static bool domain_is_valid(unsigned domain)
|
|
{
|
|
switch (domain) {
|
|
#define DOMAIN(n, v, x, c) \
|
|
case HV_PERF_DOMAIN_##n: \
|
|
/* fall through */
|
|
#include "hv-24x7-domains.h"
|
|
#undef DOMAIN
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool is_physical_domain(unsigned domain)
|
|
{
|
|
switch (domain) {
|
|
#define DOMAIN(n, v, x, c) \
|
|
case HV_PERF_DOMAIN_##n: \
|
|
return c;
|
|
#include "hv-24x7-domains.h"
|
|
#undef DOMAIN
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool catalog_entry_domain_is_valid(unsigned domain)
|
|
{
|
|
return is_physical_domain(domain);
|
|
}
|
|
|
|
/*
|
|
* TODO: Merging events:
|
|
* - Think of the hcall as an interface to a 4d array of counters:
|
|
* - x = domains
|
|
* - y = indexes in the domain (core, chip, vcpu, node, etc)
|
|
* - z = offset into the counter space
|
|
* - w = lpars (guest vms, "logical partitions")
|
|
* - A single request is: x,y,y_last,z,z_last,w,w_last
|
|
* - this means we can retrieve a rectangle of counters in y,z for a single x.
|
|
*
|
|
* - Things to consider (ignoring w):
|
|
* - input cost_per_request = 16
|
|
* - output cost_per_result(ys,zs) = 8 + 8 * ys + ys * zs
|
|
* - limited number of requests per hcall (must fit into 4K bytes)
|
|
* - 4k = 16 [buffer header] - 16 [request size] * request_count
|
|
* - 255 requests per hcall
|
|
* - sometimes it will be more efficient to read extra data and discard
|
|
*/
|
|
|
|
/*
|
|
* Example usage:
|
|
* perf stat -e 'hv_24x7/domain=2,offset=8,vcpu=0,lpar=0xffffffff/'
|
|
*/
|
|
|
|
/* u3 0-6, one of HV_24X7_PERF_DOMAIN */
|
|
EVENT_DEFINE_RANGE_FORMAT(domain, config, 0, 3);
|
|
/* u16 */
|
|
EVENT_DEFINE_RANGE_FORMAT(core, config, 16, 31);
|
|
EVENT_DEFINE_RANGE_FORMAT(vcpu, config, 16, 31);
|
|
/* u32, see "data_offset" */
|
|
EVENT_DEFINE_RANGE_FORMAT(offset, config, 32, 63);
|
|
/* u16 */
|
|
EVENT_DEFINE_RANGE_FORMAT(lpar, config1, 0, 15);
|
|
|
|
EVENT_DEFINE_RANGE(reserved1, config, 4, 15);
|
|
EVENT_DEFINE_RANGE(reserved2, config1, 16, 63);
|
|
EVENT_DEFINE_RANGE(reserved3, config2, 0, 63);
|
|
|
|
static struct attribute *format_attrs[] = {
|
|
&format_attr_domain.attr,
|
|
&format_attr_offset.attr,
|
|
&format_attr_core.attr,
|
|
&format_attr_vcpu.attr,
|
|
&format_attr_lpar.attr,
|
|
NULL,
|
|
};
|
|
|
|
static struct attribute_group format_group = {
|
|
.name = "format",
|
|
.attrs = format_attrs,
|
|
};
|
|
|
|
static struct attribute_group event_group = {
|
|
.name = "events",
|
|
/* .attrs is set in init */
|
|
};
|
|
|
|
static struct attribute_group event_desc_group = {
|
|
.name = "event_descs",
|
|
/* .attrs is set in init */
|
|
};
|
|
|
|
static struct attribute_group event_long_desc_group = {
|
|
.name = "event_long_descs",
|
|
/* .attrs is set in init */
|
|
};
|
|
|
|
static struct kmem_cache *hv_page_cache;
|
|
|
|
DEFINE_PER_CPU(int, hv_24x7_txn_flags);
|
|
DEFINE_PER_CPU(int, hv_24x7_txn_err);
|
|
|
|
struct hv_24x7_hw {
|
|
struct perf_event *events[255];
|
|
};
|
|
|
|
DEFINE_PER_CPU(struct hv_24x7_hw, hv_24x7_hw);
|
|
|
|
/*
|
|
* request_buffer and result_buffer are not required to be 4k aligned,
|
|
* but are not allowed to cross any 4k boundary. Aligning them to 4k is
|
|
* the simplest way to ensure that.
|
|
*/
|
|
#define H24x7_DATA_BUFFER_SIZE 4096
|
|
DEFINE_PER_CPU(char, hv_24x7_reqb[H24x7_DATA_BUFFER_SIZE]) __aligned(4096);
|
|
DEFINE_PER_CPU(char, hv_24x7_resb[H24x7_DATA_BUFFER_SIZE]) __aligned(4096);
|
|
|
|
static char *event_name(struct hv_24x7_event_data *ev, int *len)
|
|
{
|
|
*len = be16_to_cpu(ev->event_name_len) - 2;
|
|
return (char *)ev->remainder;
|
|
}
|
|
|
|
static char *event_desc(struct hv_24x7_event_data *ev, int *len)
|
|
{
|
|
unsigned nl = be16_to_cpu(ev->event_name_len);
|
|
__be16 *desc_len = (__be16 *)(ev->remainder + nl - 2);
|
|
|
|
*len = be16_to_cpu(*desc_len) - 2;
|
|
return (char *)ev->remainder + nl;
|
|
}
|
|
|
|
static char *event_long_desc(struct hv_24x7_event_data *ev, int *len)
|
|
{
|
|
unsigned nl = be16_to_cpu(ev->event_name_len);
|
|
__be16 *desc_len_ = (__be16 *)(ev->remainder + nl - 2);
|
|
unsigned desc_len = be16_to_cpu(*desc_len_);
|
|
__be16 *long_desc_len = (__be16 *)(ev->remainder + nl + desc_len - 2);
|
|
|
|
*len = be16_to_cpu(*long_desc_len) - 2;
|
|
return (char *)ev->remainder + nl + desc_len;
|
|
}
|
|
|
|
static bool event_fixed_portion_is_within(struct hv_24x7_event_data *ev,
|
|
void *end)
|
|
{
|
|
void *start = ev;
|
|
|
|
return (start + offsetof(struct hv_24x7_event_data, remainder)) < end;
|
|
}
|
|
|
|
/*
|
|
* Things we don't check:
|
|
* - padding for desc, name, and long/detailed desc is required to be '\0'
|
|
* bytes.
|
|
*
|
|
* Return NULL if we pass end,
|
|
* Otherwise return the address of the byte just following the event.
|
|
*/
|
|
static void *event_end(struct hv_24x7_event_data *ev, void *end)
|
|
{
|
|
void *start = ev;
|
|
__be16 *dl_, *ldl_;
|
|
unsigned dl, ldl;
|
|
unsigned nl = be16_to_cpu(ev->event_name_len);
|
|
|
|
if (nl < 2) {
|
|
pr_debug("%s: name length too short: %d", __func__, nl);
|
|
return NULL;
|
|
}
|
|
|
|
if (start + nl > end) {
|
|
pr_debug("%s: start=%p + nl=%u > end=%p",
|
|
__func__, start, nl, end);
|
|
return NULL;
|
|
}
|
|
|
|
dl_ = (__be16 *)(ev->remainder + nl - 2);
|
|
if (!IS_ALIGNED((uintptr_t)dl_, 2))
|
|
pr_warn("desc len not aligned %p", dl_);
|
|
dl = be16_to_cpu(*dl_);
|
|
if (dl < 2) {
|
|
pr_debug("%s: desc len too short: %d", __func__, dl);
|
|
return NULL;
|
|
}
|
|
|
|
if (start + nl + dl > end) {
|
|
pr_debug("%s: (start=%p + nl=%u + dl=%u)=%p > end=%p",
|
|
__func__, start, nl, dl, start + nl + dl, end);
|
|
return NULL;
|
|
}
|
|
|
|
ldl_ = (__be16 *)(ev->remainder + nl + dl - 2);
|
|
if (!IS_ALIGNED((uintptr_t)ldl_, 2))
|
|
pr_warn("long desc len not aligned %p", ldl_);
|
|
ldl = be16_to_cpu(*ldl_);
|
|
if (ldl < 2) {
|
|
pr_debug("%s: long desc len too short (ldl=%u)",
|
|
__func__, ldl);
|
|
return NULL;
|
|
}
|
|
|
|
if (start + nl + dl + ldl > end) {
|
|
pr_debug("%s: start=%p + nl=%u + dl=%u + ldl=%u > end=%p",
|
|
__func__, start, nl, dl, ldl, end);
|
|
return NULL;
|
|
}
|
|
|
|
return start + nl + dl + ldl;
|
|
}
|
|
|
|
static unsigned long h_get_24x7_catalog_page_(unsigned long phys_4096,
|
|
unsigned long version,
|
|
unsigned long index)
|
|
{
|
|
pr_devel("h_get_24x7_catalog_page(0x%lx, %lu, %lu)",
|
|
phys_4096, version, index);
|
|
|
|
WARN_ON(!IS_ALIGNED(phys_4096, 4096));
|
|
|
|
return plpar_hcall_norets(H_GET_24X7_CATALOG_PAGE,
|
|
phys_4096, version, index);
|
|
}
|
|
|
|
static unsigned long h_get_24x7_catalog_page(char page[],
|
|
u64 version, u32 index)
|
|
{
|
|
return h_get_24x7_catalog_page_(virt_to_phys(page),
|
|
version, index);
|
|
}
|
|
|
|
static unsigned core_domains[] = {
|
|
HV_PERF_DOMAIN_PHYS_CORE,
|
|
HV_PERF_DOMAIN_VCPU_HOME_CORE,
|
|
HV_PERF_DOMAIN_VCPU_HOME_CHIP,
|
|
HV_PERF_DOMAIN_VCPU_HOME_NODE,
|
|
HV_PERF_DOMAIN_VCPU_REMOTE_NODE,
|
|
};
|
|
/* chip event data always yeilds a single event, core yeilds multiple */
|
|
#define MAX_EVENTS_PER_EVENT_DATA ARRAY_SIZE(core_domains)
|
|
|
|
static char *event_fmt(struct hv_24x7_event_data *event, unsigned domain)
|
|
{
|
|
const char *sindex;
|
|
const char *lpar;
|
|
|
|
if (is_physical_domain(domain)) {
|
|
lpar = "0x0";
|
|
sindex = "core";
|
|
} else {
|
|
lpar = "?";
|
|
sindex = "vcpu";
|
|
}
|
|
|
|
return kasprintf(GFP_KERNEL,
|
|
"domain=0x%x,offset=0x%x,%s=?,lpar=%s",
|
|
domain,
|
|
be16_to_cpu(event->event_counter_offs) +
|
|
be16_to_cpu(event->event_group_record_offs),
|
|
sindex,
|
|
lpar);
|
|
}
|
|
|
|
/* Avoid trusting fw to NUL terminate strings */
|
|
static char *memdup_to_str(char *maybe_str, int max_len, gfp_t gfp)
|
|
{
|
|
return kasprintf(gfp, "%.*s", max_len, maybe_str);
|
|
}
|
|
|
|
static ssize_t device_show_string(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct dev_ext_attribute *d;
|
|
|
|
d = container_of(attr, struct dev_ext_attribute, attr);
|
|
|
|
return sprintf(buf, "%s\n", (char *)d->var);
|
|
}
|
|
|
|
static struct attribute *device_str_attr_create_(char *name, char *str)
|
|
{
|
|
struct dev_ext_attribute *attr = kzalloc(sizeof(*attr), GFP_KERNEL);
|
|
|
|
if (!attr)
|
|
return NULL;
|
|
|
|
sysfs_attr_init(&attr->attr.attr);
|
|
|
|
attr->var = str;
|
|
attr->attr.attr.name = name;
|
|
attr->attr.attr.mode = 0444;
|
|
attr->attr.show = device_show_string;
|
|
|
|
return &attr->attr.attr;
|
|
}
|
|
|
|
static struct attribute *device_str_attr_create(char *name, int name_max,
|
|
int name_nonce,
|
|
char *str, size_t str_max)
|
|
{
|
|
char *n;
|
|
char *s = memdup_to_str(str, str_max, GFP_KERNEL);
|
|
struct attribute *a;
|
|
|
|
if (!s)
|
|
return NULL;
|
|
|
|
if (!name_nonce)
|
|
n = kasprintf(GFP_KERNEL, "%.*s", name_max, name);
|
|
else
|
|
n = kasprintf(GFP_KERNEL, "%.*s__%d", name_max, name,
|
|
name_nonce);
|
|
if (!n)
|
|
goto out_s;
|
|
|
|
a = device_str_attr_create_(n, s);
|
|
if (!a)
|
|
goto out_n;
|
|
|
|
return a;
|
|
out_n:
|
|
kfree(n);
|
|
out_s:
|
|
kfree(s);
|
|
return NULL;
|
|
}
|
|
|
|
static void device_str_attr_destroy(struct attribute *attr)
|
|
{
|
|
struct dev_ext_attribute *d;
|
|
|
|
d = container_of(attr, struct dev_ext_attribute, attr.attr);
|
|
kfree(d->var);
|
|
kfree(d->attr.attr.name);
|
|
kfree(d);
|
|
}
|
|
|
|
static struct attribute *event_to_attr(unsigned ix,
|
|
struct hv_24x7_event_data *event,
|
|
unsigned domain,
|
|
int nonce)
|
|
{
|
|
int event_name_len;
|
|
char *ev_name, *a_ev_name, *val;
|
|
const char *ev_suffix;
|
|
struct attribute *attr;
|
|
|
|
if (!domain_is_valid(domain)) {
|
|
pr_warn("catalog event %u has invalid domain %u\n",
|
|
ix, domain);
|
|
return NULL;
|
|
}
|
|
|
|
val = event_fmt(event, domain);
|
|
if (!val)
|
|
return NULL;
|
|
|
|
ev_suffix = event_domain_suffix(domain);
|
|
ev_name = event_name(event, &event_name_len);
|
|
if (!nonce)
|
|
a_ev_name = kasprintf(GFP_KERNEL, "%.*s%s",
|
|
(int)event_name_len, ev_name, ev_suffix);
|
|
else
|
|
a_ev_name = kasprintf(GFP_KERNEL, "%.*s%s__%d",
|
|
(int)event_name_len, ev_name, ev_suffix, nonce);
|
|
|
|
if (!a_ev_name)
|
|
goto out_val;
|
|
|
|
attr = device_str_attr_create_(a_ev_name, val);
|
|
if (!attr)
|
|
goto out_name;
|
|
|
|
return attr;
|
|
out_name:
|
|
kfree(a_ev_name);
|
|
out_val:
|
|
kfree(val);
|
|
return NULL;
|
|
}
|
|
|
|
static struct attribute *event_to_desc_attr(struct hv_24x7_event_data *event,
|
|
int nonce)
|
|
{
|
|
int nl, dl;
|
|
char *name = event_name(event, &nl);
|
|
char *desc = event_desc(event, &dl);
|
|
|
|
/* If there isn't a description, don't create the sysfs file */
|
|
if (!dl)
|
|
return NULL;
|
|
|
|
return device_str_attr_create(name, nl, nonce, desc, dl);
|
|
}
|
|
|
|
static struct attribute *
|
|
event_to_long_desc_attr(struct hv_24x7_event_data *event, int nonce)
|
|
{
|
|
int nl, dl;
|
|
char *name = event_name(event, &nl);
|
|
char *desc = event_long_desc(event, &dl);
|
|
|
|
/* If there isn't a description, don't create the sysfs file */
|
|
if (!dl)
|
|
return NULL;
|
|
|
|
return device_str_attr_create(name, nl, nonce, desc, dl);
|
|
}
|
|
|
|
static ssize_t event_data_to_attrs(unsigned ix, struct attribute **attrs,
|
|
struct hv_24x7_event_data *event, int nonce)
|
|
{
|
|
unsigned i;
|
|
|
|
switch (event->domain) {
|
|
case HV_PERF_DOMAIN_PHYS_CHIP:
|
|
*attrs = event_to_attr(ix, event, event->domain, nonce);
|
|
return 1;
|
|
case HV_PERF_DOMAIN_PHYS_CORE:
|
|
for (i = 0; i < ARRAY_SIZE(core_domains); i++) {
|
|
attrs[i] = event_to_attr(ix, event, core_domains[i],
|
|
nonce);
|
|
if (!attrs[i]) {
|
|
pr_warn("catalog event %u: individual attr %u "
|
|
"creation failure\n", ix, i);
|
|
for (; i; i--)
|
|
device_str_attr_destroy(attrs[i - 1]);
|
|
return -1;
|
|
}
|
|
}
|
|
return i;
|
|
default:
|
|
pr_warn("catalog event %u: domain %u is not allowed in the "
|
|
"catalog\n", ix, event->domain);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static size_t event_to_attr_ct(struct hv_24x7_event_data *event)
|
|
{
|
|
switch (event->domain) {
|
|
case HV_PERF_DOMAIN_PHYS_CHIP:
|
|
return 1;
|
|
case HV_PERF_DOMAIN_PHYS_CORE:
|
|
return ARRAY_SIZE(core_domains);
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static unsigned long vmalloc_to_phys(void *v)
|
|
{
|
|
struct page *p = vmalloc_to_page(v);
|
|
|
|
BUG_ON(!p);
|
|
return page_to_phys(p) + offset_in_page(v);
|
|
}
|
|
|
|
/* */
|
|
struct event_uniq {
|
|
struct rb_node node;
|
|
const char *name;
|
|
int nl;
|
|
unsigned ct;
|
|
unsigned domain;
|
|
};
|
|
|
|
static int memord(const void *d1, size_t s1, const void *d2, size_t s2)
|
|
{
|
|
if (s1 < s2)
|
|
return 1;
|
|
if (s2 > s1)
|
|
return -1;
|
|
|
|
return memcmp(d1, d2, s1);
|
|
}
|
|
|
|
static int ev_uniq_ord(const void *v1, size_t s1, unsigned d1, const void *v2,
|
|
size_t s2, unsigned d2)
|
|
{
|
|
int r = memord(v1, s1, v2, s2);
|
|
|
|
if (r)
|
|
return r;
|
|
if (d1 > d2)
|
|
return 1;
|
|
if (d2 > d1)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
static int event_uniq_add(struct rb_root *root, const char *name, int nl,
|
|
unsigned domain)
|
|
{
|
|
struct rb_node **new = &(root->rb_node), *parent = NULL;
|
|
struct event_uniq *data;
|
|
|
|
/* Figure out where to put new node */
|
|
while (*new) {
|
|
struct event_uniq *it;
|
|
int result;
|
|
|
|
it = container_of(*new, struct event_uniq, node);
|
|
result = ev_uniq_ord(name, nl, domain, it->name, it->nl,
|
|
it->domain);
|
|
|
|
parent = *new;
|
|
if (result < 0)
|
|
new = &((*new)->rb_left);
|
|
else if (result > 0)
|
|
new = &((*new)->rb_right);
|
|
else {
|
|
it->ct++;
|
|
pr_info("found a duplicate event %.*s, ct=%u\n", nl,
|
|
name, it->ct);
|
|
return it->ct;
|
|
}
|
|
}
|
|
|
|
data = kmalloc(sizeof(*data), GFP_KERNEL);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
*data = (struct event_uniq) {
|
|
.name = name,
|
|
.nl = nl,
|
|
.ct = 0,
|
|
.domain = domain,
|
|
};
|
|
|
|
/* Add new node and rebalance tree. */
|
|
rb_link_node(&data->node, parent, new);
|
|
rb_insert_color(&data->node, root);
|
|
|
|
/* data->ct */
|
|
return 0;
|
|
}
|
|
|
|
static void event_uniq_destroy(struct rb_root *root)
|
|
{
|
|
/*
|
|
* the strings we point to are in the giant block of memory filled by
|
|
* the catalog, and are freed separately.
|
|
*/
|
|
struct event_uniq *pos, *n;
|
|
|
|
rbtree_postorder_for_each_entry_safe(pos, n, root, node)
|
|
kfree(pos);
|
|
}
|
|
|
|
|
|
/*
|
|
* ensure the event structure's sizes are self consistent and don't cause us to
|
|
* read outside of the event
|
|
*
|
|
* On success, return the event length in bytes.
|
|
* Otherwise, return -1 (and print as appropriate).
|
|
*/
|
|
static ssize_t catalog_event_len_validate(struct hv_24x7_event_data *event,
|
|
size_t event_idx,
|
|
size_t event_data_bytes,
|
|
size_t event_entry_count,
|
|
size_t offset, void *end)
|
|
{
|
|
ssize_t ev_len;
|
|
void *ev_end, *calc_ev_end;
|
|
|
|
if (offset >= event_data_bytes)
|
|
return -1;
|
|
|
|
if (event_idx >= event_entry_count) {
|
|
pr_devel("catalog event data has %zu bytes of padding after last event\n",
|
|
event_data_bytes - offset);
|
|
return -1;
|
|
}
|
|
|
|
if (!event_fixed_portion_is_within(event, end)) {
|
|
pr_warn("event %zu fixed portion is not within range\n",
|
|
event_idx);
|
|
return -1;
|
|
}
|
|
|
|
ev_len = be16_to_cpu(event->length);
|
|
|
|
if (ev_len % 16)
|
|
pr_info("event %zu has length %zu not divisible by 16: event=%pK\n",
|
|
event_idx, ev_len, event);
|
|
|
|
ev_end = (__u8 *)event + ev_len;
|
|
if (ev_end > end) {
|
|
pr_warn("event %zu has .length=%zu, ends after buffer end: ev_end=%pK > end=%pK, offset=%zu\n",
|
|
event_idx, ev_len, ev_end, end,
|
|
offset);
|
|
return -1;
|
|
}
|
|
|
|
calc_ev_end = event_end(event, end);
|
|
if (!calc_ev_end) {
|
|
pr_warn("event %zu has a calculated length which exceeds buffer length %zu: event=%pK end=%pK, offset=%zu\n",
|
|
event_idx, event_data_bytes, event, end,
|
|
offset);
|
|
return -1;
|
|
}
|
|
|
|
if (calc_ev_end > ev_end) {
|
|
pr_warn("event %zu exceeds it's own length: event=%pK, end=%pK, offset=%zu, calc_ev_end=%pK\n",
|
|
event_idx, event, ev_end, offset, calc_ev_end);
|
|
return -1;
|
|
}
|
|
|
|
return ev_len;
|
|
}
|
|
|
|
#define MAX_4K (SIZE_MAX / 4096)
|
|
|
|
static int create_events_from_catalog(struct attribute ***events_,
|
|
struct attribute ***event_descs_,
|
|
struct attribute ***event_long_descs_)
|
|
{
|
|
unsigned long hret;
|
|
size_t catalog_len, catalog_page_len, event_entry_count,
|
|
event_data_len, event_data_offs,
|
|
event_data_bytes, junk_events, event_idx, event_attr_ct, i,
|
|
attr_max, event_idx_last, desc_ct, long_desc_ct;
|
|
ssize_t ct, ev_len;
|
|
uint32_t catalog_version_num;
|
|
struct attribute **events, **event_descs, **event_long_descs;
|
|
struct hv_24x7_catalog_page_0 *page_0 =
|
|
kmem_cache_alloc(hv_page_cache, GFP_KERNEL);
|
|
void *page = page_0;
|
|
void *event_data, *end;
|
|
struct hv_24x7_event_data *event;
|
|
struct rb_root ev_uniq = RB_ROOT;
|
|
int ret = 0;
|
|
|
|
if (!page) {
|
|
ret = -ENOMEM;
|
|
goto e_out;
|
|
}
|
|
|
|
hret = h_get_24x7_catalog_page(page, 0, 0);
|
|
if (hret) {
|
|
ret = -EIO;
|
|
goto e_free;
|
|
}
|
|
|
|
catalog_version_num = be64_to_cpu(page_0->version);
|
|
catalog_page_len = be32_to_cpu(page_0->length);
|
|
|
|
if (MAX_4K < catalog_page_len) {
|
|
pr_err("invalid page count: %zu\n", catalog_page_len);
|
|
ret = -EIO;
|
|
goto e_free;
|
|
}
|
|
|
|
catalog_len = catalog_page_len * 4096;
|
|
|
|
event_entry_count = be16_to_cpu(page_0->event_entry_count);
|
|
event_data_offs = be16_to_cpu(page_0->event_data_offs);
|
|
event_data_len = be16_to_cpu(page_0->event_data_len);
|
|
|
|
pr_devel("cv %zu cl %zu eec %zu edo %zu edl %zu\n",
|
|
(size_t)catalog_version_num, catalog_len,
|
|
event_entry_count, event_data_offs, event_data_len);
|
|
|
|
if ((MAX_4K < event_data_len)
|
|
|| (MAX_4K < event_data_offs)
|
|
|| (MAX_4K - event_data_offs < event_data_len)) {
|
|
pr_err("invalid event data offs %zu and/or len %zu\n",
|
|
event_data_offs, event_data_len);
|
|
ret = -EIO;
|
|
goto e_free;
|
|
}
|
|
|
|
if ((event_data_offs + event_data_len) > catalog_page_len) {
|
|
pr_err("event data %zu-%zu does not fit inside catalog 0-%zu\n",
|
|
event_data_offs,
|
|
event_data_offs + event_data_len,
|
|
catalog_page_len);
|
|
ret = -EIO;
|
|
goto e_free;
|
|
}
|
|
|
|
if (SIZE_MAX / MAX_EVENTS_PER_EVENT_DATA - 1 < event_entry_count) {
|
|
pr_err("event_entry_count %zu is invalid\n",
|
|
event_entry_count);
|
|
ret = -EIO;
|
|
goto e_free;
|
|
}
|
|
|
|
event_data_bytes = event_data_len * 4096;
|
|
|
|
/*
|
|
* event data can span several pages, events can cross between these
|
|
* pages. Use vmalloc to make this easier.
|
|
*/
|
|
event_data = vmalloc(event_data_bytes);
|
|
if (!event_data) {
|
|
pr_err("could not allocate event data\n");
|
|
ret = -ENOMEM;
|
|
goto e_free;
|
|
}
|
|
|
|
end = event_data + event_data_bytes;
|
|
|
|
/*
|
|
* using vmalloc_to_phys() like this only works if PAGE_SIZE is
|
|
* divisible by 4096
|
|
*/
|
|
BUILD_BUG_ON(PAGE_SIZE % 4096);
|
|
|
|
for (i = 0; i < event_data_len; i++) {
|
|
hret = h_get_24x7_catalog_page_(
|
|
vmalloc_to_phys(event_data + i * 4096),
|
|
catalog_version_num,
|
|
i + event_data_offs);
|
|
if (hret) {
|
|
pr_err("failed to get event data in page %zu\n",
|
|
i + event_data_offs);
|
|
ret = -EIO;
|
|
goto e_event_data;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* scan the catalog to determine the number of attributes we need, and
|
|
* verify it at the same time.
|
|
*/
|
|
for (junk_events = 0, event = event_data, event_idx = 0, attr_max = 0;
|
|
;
|
|
event_idx++, event = (void *)event + ev_len) {
|
|
size_t offset = (void *)event - (void *)event_data;
|
|
char *name;
|
|
int nl;
|
|
|
|
ev_len = catalog_event_len_validate(event, event_idx,
|
|
event_data_bytes,
|
|
event_entry_count,
|
|
offset, end);
|
|
if (ev_len < 0)
|
|
break;
|
|
|
|
name = event_name(event, &nl);
|
|
|
|
if (event->event_group_record_len == 0) {
|
|
pr_devel("invalid event %zu (%.*s): group_record_len == 0, skipping\n",
|
|
event_idx, nl, name);
|
|
junk_events++;
|
|
continue;
|
|
}
|
|
|
|
if (!catalog_entry_domain_is_valid(event->domain)) {
|
|
pr_info("event %zu (%.*s) has invalid domain %d\n",
|
|
event_idx, nl, name, event->domain);
|
|
junk_events++;
|
|
continue;
|
|
}
|
|
|
|
attr_max += event_to_attr_ct(event);
|
|
}
|
|
|
|
event_idx_last = event_idx;
|
|
if (event_idx_last != event_entry_count)
|
|
pr_warn("event buffer ended before listed # of events were parsed (got %zu, wanted %zu, junk %zu)\n",
|
|
event_idx_last, event_entry_count, junk_events);
|
|
|
|
events = kmalloc_array(attr_max + 1, sizeof(*events), GFP_KERNEL);
|
|
if (!events) {
|
|
ret = -ENOMEM;
|
|
goto e_event_data;
|
|
}
|
|
|
|
event_descs = kmalloc_array(event_idx + 1, sizeof(*event_descs),
|
|
GFP_KERNEL);
|
|
if (!event_descs) {
|
|
ret = -ENOMEM;
|
|
goto e_event_attrs;
|
|
}
|
|
|
|
event_long_descs = kmalloc_array(event_idx + 1,
|
|
sizeof(*event_long_descs), GFP_KERNEL);
|
|
if (!event_long_descs) {
|
|
ret = -ENOMEM;
|
|
goto e_event_descs;
|
|
}
|
|
|
|
/* Iterate over the catalog filling in the attribute vector */
|
|
for (junk_events = 0, event_attr_ct = 0, desc_ct = 0, long_desc_ct = 0,
|
|
event = event_data, event_idx = 0;
|
|
event_idx < event_idx_last;
|
|
event_idx++, ev_len = be16_to_cpu(event->length),
|
|
event = (void *)event + ev_len) {
|
|
char *name;
|
|
int nl;
|
|
int nonce;
|
|
/*
|
|
* these are the only "bad" events that are intermixed and that
|
|
* we can ignore without issue. make sure to skip them here
|
|
*/
|
|
if (event->event_group_record_len == 0)
|
|
continue;
|
|
if (!catalog_entry_domain_is_valid(event->domain))
|
|
continue;
|
|
|
|
name = event_name(event, &nl);
|
|
nonce = event_uniq_add(&ev_uniq, name, nl, event->domain);
|
|
ct = event_data_to_attrs(event_idx, events + event_attr_ct,
|
|
event, nonce);
|
|
if (ct <= 0) {
|
|
pr_warn("event %zu (%.*s) creation failure, skipping\n",
|
|
event_idx, nl, name);
|
|
junk_events++;
|
|
} else {
|
|
event_attr_ct += ct;
|
|
event_descs[desc_ct] = event_to_desc_attr(event, nonce);
|
|
if (event_descs[desc_ct])
|
|
desc_ct++;
|
|
event_long_descs[long_desc_ct] =
|
|
event_to_long_desc_attr(event, nonce);
|
|
if (event_long_descs[long_desc_ct])
|
|
long_desc_ct++;
|
|
}
|
|
}
|
|
|
|
pr_info("read %zu catalog entries, created %zu event attrs (%zu failures), %zu descs\n",
|
|
event_idx, event_attr_ct, junk_events, desc_ct);
|
|
|
|
events[event_attr_ct] = NULL;
|
|
event_descs[desc_ct] = NULL;
|
|
event_long_descs[long_desc_ct] = NULL;
|
|
|
|
event_uniq_destroy(&ev_uniq);
|
|
vfree(event_data);
|
|
kmem_cache_free(hv_page_cache, page);
|
|
|
|
*events_ = events;
|
|
*event_descs_ = event_descs;
|
|
*event_long_descs_ = event_long_descs;
|
|
return 0;
|
|
|
|
e_event_descs:
|
|
kfree(event_descs);
|
|
e_event_attrs:
|
|
kfree(events);
|
|
e_event_data:
|
|
vfree(event_data);
|
|
e_free:
|
|
kmem_cache_free(hv_page_cache, page);
|
|
e_out:
|
|
*events_ = NULL;
|
|
*event_descs_ = NULL;
|
|
*event_long_descs_ = NULL;
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t catalog_read(struct file *filp, struct kobject *kobj,
|
|
struct bin_attribute *bin_attr, char *buf,
|
|
loff_t offset, size_t count)
|
|
{
|
|
unsigned long hret;
|
|
ssize_t ret = 0;
|
|
size_t catalog_len = 0, catalog_page_len = 0;
|
|
loff_t page_offset = 0;
|
|
loff_t offset_in_page;
|
|
size_t copy_len;
|
|
uint64_t catalog_version_num = 0;
|
|
void *page = kmem_cache_alloc(hv_page_cache, GFP_USER);
|
|
struct hv_24x7_catalog_page_0 *page_0 = page;
|
|
|
|
if (!page)
|
|
return -ENOMEM;
|
|
|
|
hret = h_get_24x7_catalog_page(page, 0, 0);
|
|
if (hret) {
|
|
ret = -EIO;
|
|
goto e_free;
|
|
}
|
|
|
|
catalog_version_num = be64_to_cpu(page_0->version);
|
|
catalog_page_len = be32_to_cpu(page_0->length);
|
|
catalog_len = catalog_page_len * 4096;
|
|
|
|
page_offset = offset / 4096;
|
|
offset_in_page = offset % 4096;
|
|
|
|
if (page_offset >= catalog_page_len)
|
|
goto e_free;
|
|
|
|
if (page_offset != 0) {
|
|
hret = h_get_24x7_catalog_page(page, catalog_version_num,
|
|
page_offset);
|
|
if (hret) {
|
|
ret = -EIO;
|
|
goto e_free;
|
|
}
|
|
}
|
|
|
|
copy_len = 4096 - offset_in_page;
|
|
if (copy_len > count)
|
|
copy_len = count;
|
|
|
|
memcpy(buf, page+offset_in_page, copy_len);
|
|
ret = copy_len;
|
|
|
|
e_free:
|
|
if (hret)
|
|
pr_err("h_get_24x7_catalog_page(ver=%lld, page=%lld) failed:"
|
|
" rc=%ld\n",
|
|
catalog_version_num, page_offset, hret);
|
|
kmem_cache_free(hv_page_cache, page);
|
|
|
|
pr_devel("catalog_read: offset=%lld(%lld) count=%zu "
|
|
"catalog_len=%zu(%zu) => %zd\n", offset, page_offset,
|
|
count, catalog_len, catalog_page_len, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#define PAGE_0_ATTR(_name, _fmt, _expr) \
|
|
static ssize_t _name##_show(struct device *dev, \
|
|
struct device_attribute *dev_attr, \
|
|
char *buf) \
|
|
{ \
|
|
unsigned long hret; \
|
|
ssize_t ret = 0; \
|
|
void *page = kmem_cache_alloc(hv_page_cache, GFP_USER); \
|
|
struct hv_24x7_catalog_page_0 *page_0 = page; \
|
|
if (!page) \
|
|
return -ENOMEM; \
|
|
hret = h_get_24x7_catalog_page(page, 0, 0); \
|
|
if (hret) { \
|
|
ret = -EIO; \
|
|
goto e_free; \
|
|
} \
|
|
ret = sprintf(buf, _fmt, _expr); \
|
|
e_free: \
|
|
kmem_cache_free(hv_page_cache, page); \
|
|
return ret; \
|
|
} \
|
|
static DEVICE_ATTR_RO(_name)
|
|
|
|
PAGE_0_ATTR(catalog_version, "%lld\n",
|
|
(unsigned long long)be64_to_cpu(page_0->version));
|
|
PAGE_0_ATTR(catalog_len, "%lld\n",
|
|
(unsigned long long)be32_to_cpu(page_0->length) * 4096);
|
|
static BIN_ATTR_RO(catalog, 0/* real length varies */);
|
|
|
|
static struct bin_attribute *if_bin_attrs[] = {
|
|
&bin_attr_catalog,
|
|
NULL,
|
|
};
|
|
|
|
static struct attribute *if_attrs[] = {
|
|
&dev_attr_catalog_len.attr,
|
|
&dev_attr_catalog_version.attr,
|
|
NULL,
|
|
};
|
|
|
|
static struct attribute_group if_group = {
|
|
.name = "interface",
|
|
.bin_attrs = if_bin_attrs,
|
|
.attrs = if_attrs,
|
|
};
|
|
|
|
static const struct attribute_group *attr_groups[] = {
|
|
&format_group,
|
|
&event_group,
|
|
&event_desc_group,
|
|
&event_long_desc_group,
|
|
&if_group,
|
|
NULL,
|
|
};
|
|
|
|
static void log_24x7_hcall(struct hv_24x7_request_buffer *request_buffer,
|
|
struct hv_24x7_data_result_buffer *result_buffer,
|
|
unsigned long ret)
|
|
{
|
|
struct hv_24x7_request *req;
|
|
|
|
req = &request_buffer->requests[0];
|
|
pr_notice_ratelimited("hcall failed: [%d %#x %#x %d] => "
|
|
"ret 0x%lx (%ld) detail=0x%x failing ix=%x\n",
|
|
req->performance_domain, req->data_offset,
|
|
req->starting_ix, req->starting_lpar_ix, ret, ret,
|
|
result_buffer->detailed_rc,
|
|
result_buffer->failing_request_ix);
|
|
}
|
|
|
|
/*
|
|
* Start the process for a new H_GET_24x7_DATA hcall.
|
|
*/
|
|
static void init_24x7_request(struct hv_24x7_request_buffer *request_buffer,
|
|
struct hv_24x7_data_result_buffer *result_buffer)
|
|
{
|
|
|
|
memset(request_buffer, 0, 4096);
|
|
memset(result_buffer, 0, 4096);
|
|
|
|
request_buffer->interface_version = HV_24X7_IF_VERSION_CURRENT;
|
|
/* memset above set request_buffer->num_requests to 0 */
|
|
}
|
|
|
|
/*
|
|
* Commit (i.e perform) the H_GET_24x7_DATA hcall using the data collected
|
|
* by 'init_24x7_request()' and 'add_event_to_24x7_request()'.
|
|
*/
|
|
static int make_24x7_request(struct hv_24x7_request_buffer *request_buffer,
|
|
struct hv_24x7_data_result_buffer *result_buffer)
|
|
{
|
|
unsigned long ret;
|
|
|
|
/*
|
|
* NOTE: Due to variable number of array elements in request and
|
|
* result buffer(s), sizeof() is not reliable. Use the actual
|
|
* allocated buffer size, H24x7_DATA_BUFFER_SIZE.
|
|
*/
|
|
ret = plpar_hcall_norets(H_GET_24X7_DATA,
|
|
virt_to_phys(request_buffer), H24x7_DATA_BUFFER_SIZE,
|
|
virt_to_phys(result_buffer), H24x7_DATA_BUFFER_SIZE);
|
|
|
|
if (ret)
|
|
log_24x7_hcall(request_buffer, result_buffer, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Add the given @event to the next slot in the 24x7 request_buffer.
|
|
*
|
|
* Note that H_GET_24X7_DATA hcall allows reading several counters'
|
|
* values in a single HCALL. We expect the caller to add events to the
|
|
* request buffer one by one, make the HCALL and process the results.
|
|
*/
|
|
static int add_event_to_24x7_request(struct perf_event *event,
|
|
struct hv_24x7_request_buffer *request_buffer)
|
|
{
|
|
u16 idx;
|
|
int i;
|
|
struct hv_24x7_request *req;
|
|
|
|
if (request_buffer->num_requests > 254) {
|
|
pr_devel("Too many requests for 24x7 HCALL %d\n",
|
|
request_buffer->num_requests);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (is_physical_domain(event_get_domain(event)))
|
|
idx = event_get_core(event);
|
|
else
|
|
idx = event_get_vcpu(event);
|
|
|
|
i = request_buffer->num_requests++;
|
|
req = &request_buffer->requests[i];
|
|
|
|
req->performance_domain = event_get_domain(event);
|
|
req->data_size = cpu_to_be16(8);
|
|
req->data_offset = cpu_to_be32(event_get_offset(event));
|
|
req->starting_lpar_ix = cpu_to_be16(event_get_lpar(event)),
|
|
req->max_num_lpars = cpu_to_be16(1);
|
|
req->starting_ix = cpu_to_be16(idx);
|
|
req->max_ix = cpu_to_be16(1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static unsigned long single_24x7_request(struct perf_event *event, u64 *count)
|
|
{
|
|
unsigned long ret;
|
|
struct hv_24x7_request_buffer *request_buffer;
|
|
struct hv_24x7_data_result_buffer *result_buffer;
|
|
|
|
BUILD_BUG_ON(sizeof(*request_buffer) > 4096);
|
|
BUILD_BUG_ON(sizeof(*result_buffer) > 4096);
|
|
|
|
request_buffer = (void *)get_cpu_var(hv_24x7_reqb);
|
|
result_buffer = (void *)get_cpu_var(hv_24x7_resb);
|
|
|
|
init_24x7_request(request_buffer, result_buffer);
|
|
|
|
ret = add_event_to_24x7_request(event, request_buffer);
|
|
if (ret)
|
|
goto out;
|
|
|
|
ret = make_24x7_request(request_buffer, result_buffer);
|
|
if (ret) {
|
|
log_24x7_hcall(request_buffer, result_buffer, ret);
|
|
goto out;
|
|
}
|
|
|
|
/* process result from hcall */
|
|
*count = be64_to_cpu(result_buffer->results[0].elements[0].element_data[0]);
|
|
|
|
out:
|
|
put_cpu_var(hv_24x7_reqb);
|
|
put_cpu_var(hv_24x7_resb);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int h_24x7_event_init(struct perf_event *event)
|
|
{
|
|
struct hv_perf_caps caps;
|
|
unsigned domain;
|
|
unsigned long hret;
|
|
u64 ct;
|
|
|
|
/* Not our event */
|
|
if (event->attr.type != event->pmu->type)
|
|
return -ENOENT;
|
|
|
|
/* Unused areas must be 0 */
|
|
if (event_get_reserved1(event) ||
|
|
event_get_reserved2(event) ||
|
|
event_get_reserved3(event)) {
|
|
pr_devel("reserved set when forbidden 0x%llx(0x%llx) 0x%llx(0x%llx) 0x%llx(0x%llx)\n",
|
|
event->attr.config,
|
|
event_get_reserved1(event),
|
|
event->attr.config1,
|
|
event_get_reserved2(event),
|
|
event->attr.config2,
|
|
event_get_reserved3(event));
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* unsupported modes and filters */
|
|
if (event->attr.exclude_user ||
|
|
event->attr.exclude_kernel ||
|
|
event->attr.exclude_hv ||
|
|
event->attr.exclude_idle ||
|
|
event->attr.exclude_host ||
|
|
event->attr.exclude_guest)
|
|
return -EINVAL;
|
|
|
|
/* no branch sampling */
|
|
if (has_branch_stack(event))
|
|
return -EOPNOTSUPP;
|
|
|
|
/* offset must be 8 byte aligned */
|
|
if (event_get_offset(event) % 8) {
|
|
pr_devel("bad alignment\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Domains above 6 are invalid */
|
|
domain = event_get_domain(event);
|
|
if (domain > 6) {
|
|
pr_devel("invalid domain %d\n", domain);
|
|
return -EINVAL;
|
|
}
|
|
|
|
hret = hv_perf_caps_get(&caps);
|
|
if (hret) {
|
|
pr_devel("could not get capabilities: rc=%ld\n", hret);
|
|
return -EIO;
|
|
}
|
|
|
|
/* Physical domains & other lpars require extra capabilities */
|
|
if (!caps.collect_privileged && (is_physical_domain(domain) ||
|
|
(event_get_lpar(event) != event_get_lpar_max()))) {
|
|
pr_devel("hv permissions disallow: is_physical_domain:%d, lpar=0x%llx\n",
|
|
is_physical_domain(domain),
|
|
event_get_lpar(event));
|
|
return -EACCES;
|
|
}
|
|
|
|
/* see if the event complains */
|
|
if (single_24x7_request(event, &ct)) {
|
|
pr_devel("test hcall failed\n");
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static u64 h_24x7_get_value(struct perf_event *event)
|
|
{
|
|
unsigned long ret;
|
|
u64 ct;
|
|
ret = single_24x7_request(event, &ct);
|
|
if (ret)
|
|
/* We checked this in event init, shouldn't fail here... */
|
|
return 0;
|
|
|
|
return ct;
|
|
}
|
|
|
|
static void update_event_count(struct perf_event *event, u64 now)
|
|
{
|
|
s64 prev;
|
|
|
|
prev = local64_xchg(&event->hw.prev_count, now);
|
|
local64_add(now - prev, &event->count);
|
|
}
|
|
|
|
static void h_24x7_event_read(struct perf_event *event)
|
|
{
|
|
u64 now;
|
|
struct hv_24x7_request_buffer *request_buffer;
|
|
struct hv_24x7_hw *h24x7hw;
|
|
int txn_flags;
|
|
|
|
txn_flags = __this_cpu_read(hv_24x7_txn_flags);
|
|
|
|
/*
|
|
* If in a READ transaction, add this counter to the list of
|
|
* counters to read during the next HCALL (i.e commit_txn()).
|
|
* If not in a READ transaction, go ahead and make the HCALL
|
|
* to read this counter by itself.
|
|
*/
|
|
|
|
if (txn_flags & PERF_PMU_TXN_READ) {
|
|
int i;
|
|
int ret;
|
|
|
|
if (__this_cpu_read(hv_24x7_txn_err))
|
|
return;
|
|
|
|
request_buffer = (void *)get_cpu_var(hv_24x7_reqb);
|
|
|
|
ret = add_event_to_24x7_request(event, request_buffer);
|
|
if (ret) {
|
|
__this_cpu_write(hv_24x7_txn_err, ret);
|
|
} else {
|
|
/*
|
|
* Assoicate the event with the HCALL request index,
|
|
* so ->commit_txn() can quickly find/update count.
|
|
*/
|
|
i = request_buffer->num_requests - 1;
|
|
|
|
h24x7hw = &get_cpu_var(hv_24x7_hw);
|
|
h24x7hw->events[i] = event;
|
|
put_cpu_var(h24x7hw);
|
|
}
|
|
|
|
put_cpu_var(hv_24x7_reqb);
|
|
} else {
|
|
now = h_24x7_get_value(event);
|
|
update_event_count(event, now);
|
|
}
|
|
}
|
|
|
|
static void h_24x7_event_start(struct perf_event *event, int flags)
|
|
{
|
|
if (flags & PERF_EF_RELOAD)
|
|
local64_set(&event->hw.prev_count, h_24x7_get_value(event));
|
|
}
|
|
|
|
static void h_24x7_event_stop(struct perf_event *event, int flags)
|
|
{
|
|
h_24x7_event_read(event);
|
|
}
|
|
|
|
static int h_24x7_event_add(struct perf_event *event, int flags)
|
|
{
|
|
if (flags & PERF_EF_START)
|
|
h_24x7_event_start(event, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* 24x7 counters only support READ transactions. They are
|
|
* always counting and dont need/support ADD transactions.
|
|
* Cache the flags, but otherwise ignore transactions that
|
|
* are not PERF_PMU_TXN_READ.
|
|
*/
|
|
static void h_24x7_event_start_txn(struct pmu *pmu, unsigned int flags)
|
|
{
|
|
struct hv_24x7_request_buffer *request_buffer;
|
|
struct hv_24x7_data_result_buffer *result_buffer;
|
|
|
|
/* We should not be called if we are already in a txn */
|
|
WARN_ON_ONCE(__this_cpu_read(hv_24x7_txn_flags));
|
|
|
|
__this_cpu_write(hv_24x7_txn_flags, flags);
|
|
if (flags & ~PERF_PMU_TXN_READ)
|
|
return;
|
|
|
|
request_buffer = (void *)get_cpu_var(hv_24x7_reqb);
|
|
result_buffer = (void *)get_cpu_var(hv_24x7_resb);
|
|
|
|
init_24x7_request(request_buffer, result_buffer);
|
|
|
|
put_cpu_var(hv_24x7_resb);
|
|
put_cpu_var(hv_24x7_reqb);
|
|
}
|
|
|
|
/*
|
|
* Clean up transaction state.
|
|
*
|
|
* NOTE: Ignore state of request and result buffers for now.
|
|
* We will initialize them during the next read/txn.
|
|
*/
|
|
static void reset_txn(void)
|
|
{
|
|
__this_cpu_write(hv_24x7_txn_flags, 0);
|
|
__this_cpu_write(hv_24x7_txn_err, 0);
|
|
}
|
|
|
|
/*
|
|
* 24x7 counters only support READ transactions. They are always counting
|
|
* and dont need/support ADD transactions. Clear ->txn_flags but otherwise
|
|
* ignore transactions that are not of type PERF_PMU_TXN_READ.
|
|
*
|
|
* For READ transactions, submit all pending 24x7 requests (i.e requests
|
|
* that were queued by h_24x7_event_read()), to the hypervisor and update
|
|
* the event counts.
|
|
*/
|
|
static int h_24x7_event_commit_txn(struct pmu *pmu)
|
|
{
|
|
struct hv_24x7_request_buffer *request_buffer;
|
|
struct hv_24x7_data_result_buffer *result_buffer;
|
|
struct hv_24x7_result *resb;
|
|
struct perf_event *event;
|
|
u64 count;
|
|
int i, ret, txn_flags;
|
|
struct hv_24x7_hw *h24x7hw;
|
|
|
|
txn_flags = __this_cpu_read(hv_24x7_txn_flags);
|
|
WARN_ON_ONCE(!txn_flags);
|
|
|
|
ret = 0;
|
|
if (txn_flags & ~PERF_PMU_TXN_READ)
|
|
goto out;
|
|
|
|
ret = __this_cpu_read(hv_24x7_txn_err);
|
|
if (ret)
|
|
goto out;
|
|
|
|
request_buffer = (void *)get_cpu_var(hv_24x7_reqb);
|
|
result_buffer = (void *)get_cpu_var(hv_24x7_resb);
|
|
|
|
ret = make_24x7_request(request_buffer, result_buffer);
|
|
if (ret) {
|
|
log_24x7_hcall(request_buffer, result_buffer, ret);
|
|
goto put_reqb;
|
|
}
|
|
|
|
h24x7hw = &get_cpu_var(hv_24x7_hw);
|
|
|
|
/* Update event counts from hcall */
|
|
for (i = 0; i < request_buffer->num_requests; i++) {
|
|
resb = &result_buffer->results[i];
|
|
count = be64_to_cpu(resb->elements[0].element_data[0]);
|
|
event = h24x7hw->events[i];
|
|
h24x7hw->events[i] = NULL;
|
|
update_event_count(event, count);
|
|
}
|
|
|
|
put_cpu_var(hv_24x7_hw);
|
|
|
|
put_reqb:
|
|
put_cpu_var(hv_24x7_resb);
|
|
put_cpu_var(hv_24x7_reqb);
|
|
out:
|
|
reset_txn();
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* 24x7 counters only support READ transactions. They are always counting
|
|
* and dont need/support ADD transactions. However, regardless of type
|
|
* of transaction, all we need to do is cleanup, so we don't have to check
|
|
* the type of transaction.
|
|
*/
|
|
static void h_24x7_event_cancel_txn(struct pmu *pmu)
|
|
{
|
|
WARN_ON_ONCE(!__this_cpu_read(hv_24x7_txn_flags));
|
|
reset_txn();
|
|
}
|
|
|
|
static struct pmu h_24x7_pmu = {
|
|
.task_ctx_nr = perf_invalid_context,
|
|
|
|
.name = "hv_24x7",
|
|
.attr_groups = attr_groups,
|
|
.event_init = h_24x7_event_init,
|
|
.add = h_24x7_event_add,
|
|
.del = h_24x7_event_stop,
|
|
.start = h_24x7_event_start,
|
|
.stop = h_24x7_event_stop,
|
|
.read = h_24x7_event_read,
|
|
.start_txn = h_24x7_event_start_txn,
|
|
.commit_txn = h_24x7_event_commit_txn,
|
|
.cancel_txn = h_24x7_event_cancel_txn,
|
|
};
|
|
|
|
static int hv_24x7_init(void)
|
|
{
|
|
int r;
|
|
unsigned long hret;
|
|
struct hv_perf_caps caps;
|
|
|
|
if (!firmware_has_feature(FW_FEATURE_LPAR)) {
|
|
pr_debug("not a virtualized system, not enabling\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
hret = hv_perf_caps_get(&caps);
|
|
if (hret) {
|
|
pr_debug("could not obtain capabilities, not enabling, rc=%ld\n",
|
|
hret);
|
|
return -ENODEV;
|
|
}
|
|
|
|
hv_page_cache = kmem_cache_create("hv-page-4096", 4096, 4096, 0, NULL);
|
|
if (!hv_page_cache)
|
|
return -ENOMEM;
|
|
|
|
/* sampling not supported */
|
|
h_24x7_pmu.capabilities |= PERF_PMU_CAP_NO_INTERRUPT;
|
|
|
|
r = create_events_from_catalog(&event_group.attrs,
|
|
&event_desc_group.attrs,
|
|
&event_long_desc_group.attrs);
|
|
|
|
if (r)
|
|
return r;
|
|
|
|
r = perf_pmu_register(&h_24x7_pmu, h_24x7_pmu.name, -1);
|
|
if (r)
|
|
return r;
|
|
|
|
return 0;
|
|
}
|
|
|
|
device_initcall(hv_24x7_init);
|