531 lines
12 KiB
C
531 lines
12 KiB
C
|
/*
|
||
|
* Intel Cache Quality-of-Service Monitoring (CQM) support.
|
||
|
*
|
||
|
* Based very, very heavily on work by Peter Zijlstra.
|
||
|
*/
|
||
|
|
||
|
#include <linux/perf_event.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <asm/cpu_device_id.h>
|
||
|
#include "perf_event.h"
|
||
|
|
||
|
#define MSR_IA32_PQR_ASSOC 0x0c8f
|
||
|
#define MSR_IA32_QM_CTR 0x0c8e
|
||
|
#define MSR_IA32_QM_EVTSEL 0x0c8d
|
||
|
|
||
|
static unsigned int cqm_max_rmid = -1;
|
||
|
static unsigned int cqm_l3_scale; /* supposedly cacheline size */
|
||
|
|
||
|
struct intel_cqm_state {
|
||
|
raw_spinlock_t lock;
|
||
|
int rmid;
|
||
|
int cnt;
|
||
|
};
|
||
|
|
||
|
static DEFINE_PER_CPU(struct intel_cqm_state, cqm_state);
|
||
|
|
||
|
/*
|
||
|
* Protects cache_cgroups.
|
||
|
*/
|
||
|
static DEFINE_MUTEX(cache_mutex);
|
||
|
|
||
|
/*
|
||
|
* Groups of events that have the same target(s), one RMID per group.
|
||
|
*/
|
||
|
static LIST_HEAD(cache_groups);
|
||
|
|
||
|
/*
|
||
|
* Mask of CPUs for reading CQM values. We only need one per-socket.
|
||
|
*/
|
||
|
static cpumask_t cqm_cpumask;
|
||
|
|
||
|
#define RMID_VAL_ERROR (1ULL << 63)
|
||
|
#define RMID_VAL_UNAVAIL (1ULL << 62)
|
||
|
|
||
|
#define QOS_L3_OCCUP_EVENT_ID (1 << 0)
|
||
|
|
||
|
#define QOS_EVENT_MASK QOS_L3_OCCUP_EVENT_ID
|
||
|
|
||
|
static u64 __rmid_read(unsigned long rmid)
|
||
|
{
|
||
|
u64 val;
|
||
|
|
||
|
/*
|
||
|
* Ignore the SDM, this thing is _NOTHING_ like a regular perfcnt,
|
||
|
* it just says that to increase confusion.
|
||
|
*/
|
||
|
wrmsr(MSR_IA32_QM_EVTSEL, QOS_L3_OCCUP_EVENT_ID, rmid);
|
||
|
rdmsrl(MSR_IA32_QM_CTR, val);
|
||
|
|
||
|
/*
|
||
|
* Aside from the ERROR and UNAVAIL bits, assume this thing returns
|
||
|
* the number of cachelines tagged with @rmid.
|
||
|
*/
|
||
|
return val;
|
||
|
}
|
||
|
|
||
|
static unsigned long *cqm_rmid_bitmap;
|
||
|
|
||
|
/*
|
||
|
* Returns < 0 on fail.
|
||
|
*/
|
||
|
static int __get_rmid(void)
|
||
|
{
|
||
|
return bitmap_find_free_region(cqm_rmid_bitmap, cqm_max_rmid, 0);
|
||
|
}
|
||
|
|
||
|
static void __put_rmid(int rmid)
|
||
|
{
|
||
|
bitmap_release_region(cqm_rmid_bitmap, rmid, 0);
|
||
|
}
|
||
|
|
||
|
static int intel_cqm_setup_rmid_cache(void)
|
||
|
{
|
||
|
cqm_rmid_bitmap = kmalloc(sizeof(long) * BITS_TO_LONGS(cqm_max_rmid), GFP_KERNEL);
|
||
|
if (!cqm_rmid_bitmap)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
bitmap_zero(cqm_rmid_bitmap, cqm_max_rmid);
|
||
|
|
||
|
/*
|
||
|
* RMID 0 is special and is always allocated. It's used for all
|
||
|
* tasks that are not monitored.
|
||
|
*/
|
||
|
bitmap_allocate_region(cqm_rmid_bitmap, 0, 0);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Determine if @a and @b measure the same set of tasks.
|
||
|
*/
|
||
|
static bool __match_event(struct perf_event *a, struct perf_event *b)
|
||
|
{
|
||
|
if ((a->attach_state & PERF_ATTACH_TASK) !=
|
||
|
(b->attach_state & PERF_ATTACH_TASK))
|
||
|
return false;
|
||
|
|
||
|
/* not task */
|
||
|
|
||
|
return true; /* if not task, we're machine wide */
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Determine if @a's tasks intersect with @b's tasks
|
||
|
*/
|
||
|
static bool __conflict_event(struct perf_event *a, struct perf_event *b)
|
||
|
{
|
||
|
/*
|
||
|
* If one of them is not a task, same story as above with cgroups.
|
||
|
*/
|
||
|
if (!(a->attach_state & PERF_ATTACH_TASK) ||
|
||
|
!(b->attach_state & PERF_ATTACH_TASK))
|
||
|
return true;
|
||
|
|
||
|
/*
|
||
|
* Must be non-overlapping.
|
||
|
*/
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Find a group and setup RMID.
|
||
|
*
|
||
|
* If we're part of a group, we use the group's RMID.
|
||
|
*/
|
||
|
static int intel_cqm_setup_event(struct perf_event *event,
|
||
|
struct perf_event **group)
|
||
|
{
|
||
|
struct perf_event *iter;
|
||
|
int rmid;
|
||
|
|
||
|
list_for_each_entry(iter, &cache_groups, hw.cqm_groups_entry) {
|
||
|
if (__match_event(iter, event)) {
|
||
|
/* All tasks in a group share an RMID */
|
||
|
event->hw.cqm_rmid = iter->hw.cqm_rmid;
|
||
|
*group = iter;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (__conflict_event(iter, event))
|
||
|
return -EBUSY;
|
||
|
}
|
||
|
|
||
|
rmid = __get_rmid();
|
||
|
if (rmid < 0)
|
||
|
return rmid;
|
||
|
|
||
|
event->hw.cqm_rmid = rmid;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void intel_cqm_event_read(struct perf_event *event)
|
||
|
{
|
||
|
unsigned long rmid = event->hw.cqm_rmid;
|
||
|
u64 val;
|
||
|
|
||
|
val = __rmid_read(rmid);
|
||
|
|
||
|
/*
|
||
|
* Ignore this reading on error states and do not update the value.
|
||
|
*/
|
||
|
if (val & (RMID_VAL_ERROR | RMID_VAL_UNAVAIL))
|
||
|
return;
|
||
|
|
||
|
local64_set(&event->count, val);
|
||
|
}
|
||
|
|
||
|
static void intel_cqm_event_start(struct perf_event *event, int mode)
|
||
|
{
|
||
|
struct intel_cqm_state *state = this_cpu_ptr(&cqm_state);
|
||
|
unsigned long rmid = event->hw.cqm_rmid;
|
||
|
unsigned long flags;
|
||
|
|
||
|
if (!(event->hw.cqm_state & PERF_HES_STOPPED))
|
||
|
return;
|
||
|
|
||
|
event->hw.cqm_state &= ~PERF_HES_STOPPED;
|
||
|
|
||
|
raw_spin_lock_irqsave(&state->lock, flags);
|
||
|
|
||
|
if (state->cnt++)
|
||
|
WARN_ON_ONCE(state->rmid != rmid);
|
||
|
else
|
||
|
WARN_ON_ONCE(state->rmid);
|
||
|
|
||
|
state->rmid = rmid;
|
||
|
wrmsrl(MSR_IA32_PQR_ASSOC, state->rmid);
|
||
|
|
||
|
raw_spin_unlock_irqrestore(&state->lock, flags);
|
||
|
}
|
||
|
|
||
|
static void intel_cqm_event_stop(struct perf_event *event, int mode)
|
||
|
{
|
||
|
struct intel_cqm_state *state = this_cpu_ptr(&cqm_state);
|
||
|
unsigned long flags;
|
||
|
|
||
|
if (event->hw.cqm_state & PERF_HES_STOPPED)
|
||
|
return;
|
||
|
|
||
|
event->hw.cqm_state |= PERF_HES_STOPPED;
|
||
|
|
||
|
raw_spin_lock_irqsave(&state->lock, flags);
|
||
|
intel_cqm_event_read(event);
|
||
|
|
||
|
if (!--state->cnt) {
|
||
|
state->rmid = 0;
|
||
|
wrmsrl(MSR_IA32_PQR_ASSOC, 0);
|
||
|
} else {
|
||
|
WARN_ON_ONCE(!state->rmid);
|
||
|
}
|
||
|
|
||
|
raw_spin_unlock_irqrestore(&state->lock, flags);
|
||
|
}
|
||
|
|
||
|
static int intel_cqm_event_add(struct perf_event *event, int mode)
|
||
|
{
|
||
|
int rmid;
|
||
|
|
||
|
event->hw.cqm_state = PERF_HES_STOPPED;
|
||
|
rmid = event->hw.cqm_rmid;
|
||
|
WARN_ON_ONCE(!rmid);
|
||
|
|
||
|
if (mode & PERF_EF_START)
|
||
|
intel_cqm_event_start(event, mode);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void intel_cqm_event_del(struct perf_event *event, int mode)
|
||
|
{
|
||
|
intel_cqm_event_stop(event, mode);
|
||
|
}
|
||
|
|
||
|
static void intel_cqm_event_destroy(struct perf_event *event)
|
||
|
{
|
||
|
struct perf_event *group_other = NULL;
|
||
|
|
||
|
mutex_lock(&cache_mutex);
|
||
|
|
||
|
/*
|
||
|
* If there's another event in this group...
|
||
|
*/
|
||
|
if (!list_empty(&event->hw.cqm_group_entry)) {
|
||
|
group_other = list_first_entry(&event->hw.cqm_group_entry,
|
||
|
struct perf_event,
|
||
|
hw.cqm_group_entry);
|
||
|
list_del(&event->hw.cqm_group_entry);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* And we're the group leader..
|
||
|
*/
|
||
|
if (!list_empty(&event->hw.cqm_groups_entry)) {
|
||
|
/*
|
||
|
* If there was a group_other, make that leader, otherwise
|
||
|
* destroy the group and return the RMID.
|
||
|
*/
|
||
|
if (group_other) {
|
||
|
list_replace(&event->hw.cqm_groups_entry,
|
||
|
&group_other->hw.cqm_groups_entry);
|
||
|
} else {
|
||
|
int rmid = event->hw.cqm_rmid;
|
||
|
|
||
|
__put_rmid(rmid);
|
||
|
list_del(&event->hw.cqm_groups_entry);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
mutex_unlock(&cache_mutex);
|
||
|
}
|
||
|
|
||
|
static struct pmu intel_cqm_pmu;
|
||
|
|
||
|
/*
|
||
|
* XXX there's a bit of a problem in that we cannot simply do the one
|
||
|
* event per node as one would want, since that one event would one get
|
||
|
* scheduled on the one cpu. But we want to 'schedule' the RMID on all
|
||
|
* CPUs.
|
||
|
*
|
||
|
* This means we want events for each CPU, however, that generates a lot
|
||
|
* of duplicate values out to userspace -- this is not to be helped
|
||
|
* unless we want to change the core code in some way. Fore more info,
|
||
|
* see intel_cqm_event_read().
|
||
|
*/
|
||
|
static int intel_cqm_event_init(struct perf_event *event)
|
||
|
{
|
||
|
struct perf_event *group = NULL;
|
||
|
int err;
|
||
|
|
||
|
if (event->attr.type != intel_cqm_pmu.type)
|
||
|
return -ENOENT;
|
||
|
|
||
|
if (event->attr.config & ~QOS_EVENT_MASK)
|
||
|
return -EINVAL;
|
||
|
|
||
|
if (event->cpu == -1)
|
||
|
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 ||
|
||
|
event->attr.sample_period) /* no sampling */
|
||
|
return -EINVAL;
|
||
|
|
||
|
INIT_LIST_HEAD(&event->hw.cqm_group_entry);
|
||
|
INIT_LIST_HEAD(&event->hw.cqm_groups_entry);
|
||
|
|
||
|
event->destroy = intel_cqm_event_destroy;
|
||
|
|
||
|
mutex_lock(&cache_mutex);
|
||
|
|
||
|
err = intel_cqm_setup_event(event, &group); /* will also set rmid */
|
||
|
if (err)
|
||
|
goto out;
|
||
|
|
||
|
if (group) {
|
||
|
list_add_tail(&event->hw.cqm_group_entry,
|
||
|
&group->hw.cqm_group_entry);
|
||
|
} else {
|
||
|
list_add_tail(&event->hw.cqm_groups_entry,
|
||
|
&cache_groups);
|
||
|
}
|
||
|
|
||
|
out:
|
||
|
mutex_unlock(&cache_mutex);
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
EVENT_ATTR_STR(llc_occupancy, intel_cqm_llc, "event=0x01");
|
||
|
EVENT_ATTR_STR(llc_occupancy.per-pkg, intel_cqm_llc_pkg, "1");
|
||
|
EVENT_ATTR_STR(llc_occupancy.unit, intel_cqm_llc_unit, "Bytes");
|
||
|
EVENT_ATTR_STR(llc_occupancy.scale, intel_cqm_llc_scale, NULL);
|
||
|
EVENT_ATTR_STR(llc_occupancy.snapshot, intel_cqm_llc_snapshot, "1");
|
||
|
|
||
|
static struct attribute *intel_cqm_events_attr[] = {
|
||
|
EVENT_PTR(intel_cqm_llc),
|
||
|
EVENT_PTR(intel_cqm_llc_pkg),
|
||
|
EVENT_PTR(intel_cqm_llc_unit),
|
||
|
EVENT_PTR(intel_cqm_llc_scale),
|
||
|
EVENT_PTR(intel_cqm_llc_snapshot),
|
||
|
NULL,
|
||
|
};
|
||
|
|
||
|
static struct attribute_group intel_cqm_events_group = {
|
||
|
.name = "events",
|
||
|
.attrs = intel_cqm_events_attr,
|
||
|
};
|
||
|
|
||
|
PMU_FORMAT_ATTR(event, "config:0-7");
|
||
|
static struct attribute *intel_cqm_formats_attr[] = {
|
||
|
&format_attr_event.attr,
|
||
|
NULL,
|
||
|
};
|
||
|
|
||
|
static struct attribute_group intel_cqm_format_group = {
|
||
|
.name = "format",
|
||
|
.attrs = intel_cqm_formats_attr,
|
||
|
};
|
||
|
|
||
|
static const struct attribute_group *intel_cqm_attr_groups[] = {
|
||
|
&intel_cqm_events_group,
|
||
|
&intel_cqm_format_group,
|
||
|
NULL,
|
||
|
};
|
||
|
|
||
|
static struct pmu intel_cqm_pmu = {
|
||
|
.attr_groups = intel_cqm_attr_groups,
|
||
|
.task_ctx_nr = perf_sw_context,
|
||
|
.event_init = intel_cqm_event_init,
|
||
|
.add = intel_cqm_event_add,
|
||
|
.del = intel_cqm_event_del,
|
||
|
.start = intel_cqm_event_start,
|
||
|
.stop = intel_cqm_event_stop,
|
||
|
.read = intel_cqm_event_read,
|
||
|
};
|
||
|
|
||
|
static inline void cqm_pick_event_reader(int cpu)
|
||
|
{
|
||
|
int phys_id = topology_physical_package_id(cpu);
|
||
|
int i;
|
||
|
|
||
|
for_each_cpu(i, &cqm_cpumask) {
|
||
|
if (phys_id == topology_physical_package_id(i))
|
||
|
return; /* already got reader for this socket */
|
||
|
}
|
||
|
|
||
|
cpumask_set_cpu(cpu, &cqm_cpumask);
|
||
|
}
|
||
|
|
||
|
static void intel_cqm_cpu_prepare(unsigned int cpu)
|
||
|
{
|
||
|
struct intel_cqm_state *state = &per_cpu(cqm_state, cpu);
|
||
|
struct cpuinfo_x86 *c = &cpu_data(cpu);
|
||
|
|
||
|
raw_spin_lock_init(&state->lock);
|
||
|
state->rmid = 0;
|
||
|
state->cnt = 0;
|
||
|
|
||
|
WARN_ON(c->x86_cache_max_rmid != cqm_max_rmid);
|
||
|
WARN_ON(c->x86_cache_occ_scale != cqm_l3_scale);
|
||
|
}
|
||
|
|
||
|
static void intel_cqm_cpu_exit(unsigned int cpu)
|
||
|
{
|
||
|
int phys_id = topology_physical_package_id(cpu);
|
||
|
int i;
|
||
|
|
||
|
/*
|
||
|
* Is @cpu a designated cqm reader?
|
||
|
*/
|
||
|
if (!cpumask_test_and_clear_cpu(cpu, &cqm_cpumask))
|
||
|
return;
|
||
|
|
||
|
for_each_online_cpu(i) {
|
||
|
if (i == cpu)
|
||
|
continue;
|
||
|
|
||
|
if (phys_id == topology_physical_package_id(i)) {
|
||
|
cpumask_set_cpu(i, &cqm_cpumask);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int intel_cqm_cpu_notifier(struct notifier_block *nb,
|
||
|
unsigned long action, void *hcpu)
|
||
|
{
|
||
|
unsigned int cpu = (unsigned long)hcpu;
|
||
|
|
||
|
switch (action & ~CPU_TASKS_FROZEN) {
|
||
|
case CPU_UP_PREPARE:
|
||
|
intel_cqm_cpu_prepare(cpu);
|
||
|
break;
|
||
|
case CPU_DOWN_PREPARE:
|
||
|
intel_cqm_cpu_exit(cpu);
|
||
|
break;
|
||
|
case CPU_STARTING:
|
||
|
cqm_pick_event_reader(cpu);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return NOTIFY_OK;
|
||
|
}
|
||
|
|
||
|
static const struct x86_cpu_id intel_cqm_match[] = {
|
||
|
{ .vendor = X86_VENDOR_INTEL, .feature = X86_FEATURE_CQM_OCCUP_LLC },
|
||
|
{}
|
||
|
};
|
||
|
|
||
|
static int __init intel_cqm_init(void)
|
||
|
{
|
||
|
char *str, scale[20];
|
||
|
int i, cpu, ret;
|
||
|
|
||
|
if (!x86_match_cpu(intel_cqm_match))
|
||
|
return -ENODEV;
|
||
|
|
||
|
cqm_l3_scale = boot_cpu_data.x86_cache_occ_scale;
|
||
|
|
||
|
/*
|
||
|
* It's possible that not all resources support the same number
|
||
|
* of RMIDs. Instead of making scheduling much more complicated
|
||
|
* (where we have to match a task's RMID to a cpu that supports
|
||
|
* that many RMIDs) just find the minimum RMIDs supported across
|
||
|
* all cpus.
|
||
|
*
|
||
|
* Also, check that the scales match on all cpus.
|
||
|
*/
|
||
|
cpu_notifier_register_begin();
|
||
|
|
||
|
for_each_online_cpu(cpu) {
|
||
|
struct cpuinfo_x86 *c = &cpu_data(cpu);
|
||
|
|
||
|
if (c->x86_cache_max_rmid < cqm_max_rmid)
|
||
|
cqm_max_rmid = c->x86_cache_max_rmid;
|
||
|
|
||
|
if (c->x86_cache_occ_scale != cqm_l3_scale) {
|
||
|
pr_err("Multiple LLC scale values, disabling\n");
|
||
|
ret = -EINVAL;
|
||
|
goto out;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
snprintf(scale, sizeof(scale), "%u", cqm_l3_scale);
|
||
|
str = kstrdup(scale, GFP_KERNEL);
|
||
|
if (!str) {
|
||
|
ret = -ENOMEM;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
event_attr_intel_cqm_llc_scale.event_str = str;
|
||
|
|
||
|
ret = intel_cqm_setup_rmid_cache();
|
||
|
if (ret)
|
||
|
goto out;
|
||
|
|
||
|
for_each_online_cpu(i) {
|
||
|
intel_cqm_cpu_prepare(i);
|
||
|
cqm_pick_event_reader(i);
|
||
|
}
|
||
|
|
||
|
__perf_cpu_notifier(intel_cqm_cpu_notifier);
|
||
|
|
||
|
ret = perf_pmu_register(&intel_cqm_pmu, "intel_cqm", -1);
|
||
|
|
||
|
if (ret)
|
||
|
pr_err("Intel CQM perf registration failed: %d\n", ret);
|
||
|
else
|
||
|
pr_info("Intel CQM monitoring enabled\n");
|
||
|
|
||
|
out:
|
||
|
cpu_notifier_register_done();
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
device_initcall(intel_cqm_init);
|