blob: 3e6426d551d41262866dbc23c55bd0ccf8492d3d [file] [edit]
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2014-2018, 2019, The Linux Foundation. All rights reserved.
*/
#define pr_fmt(fmt) "arm-memlat-mon: " fmt
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/cpuidle.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/slab.h>
#include <linux/irq.h>
#include <linux/cpu_pm.h>
#include <linux/cpu.h>
#include <linux/of_fdt.h>
#include <linux/perf_event.h>
#include <linux/of_device.h>
#include <linux/mutex.h>
#include <trace/hooks/cpuidle.h>
#include <linux/spinlock.h>
// Note: This header comes from $KERNEL_SRC/drivers/devfreq
#include <governor.h>
#include "governor_memlat.h"
static DEFINE_PER_CPU(bool, is_idle);
static DEFINE_PER_CPU(bool, is_on);
static DEFINE_PER_CPU(int, cpu_idle_state);
enum mon_type {
MEMLAT_CPU_GRP,
MEMLAT_MON,
NUM_MON_TYPES
};
struct event_data {
struct perf_event *pevent;
unsigned long prev_count;
unsigned long last_delta;
unsigned long total;
};
struct cpu_data {
struct event_data common_evs[NUM_COMMON_EVS];
unsigned long freq;
unsigned long stall_pct;
spinlock_t pmu_lock;
unsigned long inst;
unsigned long cyc;
unsigned long stall;
unsigned long cachemiss;
};
/**
* struct memlat_cpu_grp - A coordinator of both HW reads and devfreq updates
* for one or more memlat_mons.
*
* @cpus: The CPUs this cpu_grp will read events from.
* @common_ev_ids: The event codes of the events all mons need.
* @cpus_data: The cpus data array of length #cpus. Includes
* event_data of all the events all mons need as
* well as common computed cpu data like freq.
* @last_update_ts: Used to avoid redundant reads.
* @last_ts_delta_us: The time difference between the most recent
* update and the one before that. Used to compute
* effective frequency.
* @work: The delayed_work used for handling updates.
* @update_ms: The frequency with which @work triggers.
* @num_mons: The number of @mons for this cpu_grp.
* @num_inited_mons: The number of @mons who have probed.
* @num_active_mons: The number of @mons currently running
* memlat.
* @mons: All of the memlat_mon structs representing
* the different voters who share this cpu_grp.
* @mons_lock: A lock used to protect the @mons.
*/
struct memlat_cpu_grp {
struct list_head node;
cpumask_t cpus;
unsigned int common_ev_ids[NUM_COMMON_EVS];
struct cpu_data *cpus_data;
ktime_t last_update_ts;
unsigned long last_ts_delta_us;
struct delayed_work work;
unsigned int update_ms;
unsigned int num_mons;
unsigned int num_inited_mons;
unsigned int num_active_mons;
struct memlat_mon *mons;
struct mutex mons_lock;
bool initialized;
};
struct memlat_mon_spec {
enum mon_type type;
};
#define to_cpu_data(cpu_grp, cpu) \
(&cpu_grp->cpus_data[cpu - cpumask_first(&cpu_grp->cpus)])
#define to_common_evs(cpu_grp, cpu) \
(cpu_grp->cpus_data[cpu - cpumask_first(&cpu_grp->cpus)].common_evs)
#define to_devstats(mon, cpu) \
(&mon->hw.core_stats[cpu - cpumask_first(&mon->cpus)])
static struct workqueue_struct *memlat_wq;
static LIST_HEAD(cpu_grp_list);
static DEFINE_PER_CPU(struct memlat_cpu_grp*, cpu_grp_p);
#define MAX_COUNT_LIM 0xFFFFFFFFFFFFFFFF
int get_ev_data(int cpu, int inst_ev, int cyc_ev, int stall_ev, int cachemiss_ev,
unsigned long *inst, unsigned long *cyc,
unsigned long *stall, unsigned long *cachemiss)
{
struct memlat_cpu_grp *cpu_grp = per_cpu(cpu_grp_p, cpu);
struct memlat_mon *mons = cpu_grp->mons;
struct cpu_data *cpu_data = to_cpu_data(cpu_grp, cpu);
if (cpu_grp->common_ev_ids[INST_IDX] != inst_ev ||
cpu_grp->common_ev_ids[CYC_IDX] != cyc_ev ||
cpu_grp->common_ev_ids[STALL_IDX] != stall_ev ||
mons->miss_ev_id != cachemiss_ev)
return -EINVAL;
spin_lock(&cpu_data->pmu_lock);
*inst = cpu_data->inst;
*cyc = cpu_data->cyc;
*stall = cpu_data->stall;
*cachemiss = cpu_data->cachemiss;
spin_unlock(&cpu_data->pmu_lock);
return 0;
}
EXPORT_SYMBOL(get_ev_data);
static inline void read_event(struct event_data *event)
{
unsigned long ev_count = 0;
u64 total, enabled, running;
if (!event->pevent)
return;
if (!per_cpu(is_on, event->pevent->cpu))
return;
total = per_cpu(is_idle, event->pevent->cpu) ?
event->total : perf_event_read_value(event->pevent, &enabled, &running);
ev_count = total - event->prev_count;
event->prev_count = total;
event->last_delta = ev_count;
}
static inline void read_event_local(struct event_data *event)
{
u64 total, enabled, running;
int ret = 0;
if (!event->pevent)
return;
if (event->pevent->oncpu == -1)
return;
if (!per_cpu(is_on, event->pevent->cpu))
return;
ret = perf_event_read_local(event->pevent, &total,
&enabled, &running);
if (ret)
pr_err("read event fail %d\n", ret);
else
event->total = total;
}
static void vendor_update_event_cpu_idle_enter(void *data, int *state, struct cpuidle_device *dev)
{
if (!__this_cpu_read(is_on))
return;
__this_cpu_write(is_idle, true);
__this_cpu_write(cpu_idle_state, *state);
}
static void vendor_update_event_cpu_idle_exit(void *data, int state, struct cpuidle_device *dev)
{
if (!__this_cpu_read(is_on))
return;
__this_cpu_write(is_idle, false);
__this_cpu_write(cpu_idle_state, state);
}
static int get_cpu_idle_state(unsigned int cpu)
{
return per_cpu(is_idle, cpu) ? per_cpu(cpu_idle_state, cpu) : -1;
}
static void update_counts(struct memlat_cpu_grp *cpu_grp)
{
unsigned int cpu, i;
struct memlat_mon *mon;
ktime_t now = ktime_get();
unsigned long delta = ktime_us_delta(now, cpu_grp->last_update_ts);
struct cpu_data *cpu_data;
struct event_data *common_evs;
unsigned int mon_idx;
cpu_grp->last_ts_delta_us = delta;
cpu_grp->last_update_ts = now;
for_each_cpu(cpu, &cpu_grp->cpus) {
cpu_data = to_cpu_data(cpu_grp, cpu);
common_evs = cpu_data->common_evs;
for (i = 0; i < NUM_COMMON_EVS; i++)
read_event(&common_evs[i]);
if (!common_evs[STALL_IDX].pevent)
common_evs[STALL_IDX].last_delta =
common_evs[CYC_IDX].last_delta;
cpu_data->freq = common_evs[CYC_IDX].last_delta / delta;
cpu_data->stall_pct = mult_frac(100,
common_evs[STALL_IDX].last_delta,
common_evs[CYC_IDX].last_delta);
}
for (i = 0; i < cpu_grp->num_mons; i++) {
mon = &cpu_grp->mons[i];
if (!mon->is_active || !mon->miss_ev)
continue;
for_each_cpu(cpu, &mon->cpus) {
mon_idx = cpu - cpumask_first(&mon->cpus);
read_event(&mon->miss_ev[mon_idx]);
}
}
spin_lock(&cpu_data->pmu_lock);
cpu_data->inst = common_evs[INST_IDX].last_delta;
cpu_data->cyc = common_evs[CYC_IDX].last_delta;
cpu_data->stall = common_evs[STALL_IDX].last_delta;
cpu_data->cachemiss = mon->miss_ev[mon_idx].last_delta;
spin_unlock(&cpu_data->pmu_lock);
}
static unsigned long get_cnt(struct memlat_hwmon *hw)
{
struct memlat_mon *mon = to_mon(hw);
struct memlat_cpu_grp *cpu_grp = mon->cpu_grp;
unsigned int cpu;
for_each_cpu(cpu, &mon->cpus) {
struct cpu_data *cpu_data = to_cpu_data(cpu_grp, cpu);
struct event_data *common_evs = cpu_data->common_evs;
unsigned int mon_idx =
cpu - cpumask_first(&mon->cpus);
struct dev_stats *devstats = to_devstats(mon, cpu);
devstats->freq = cpu_data->freq;
devstats->stall_pct = cpu_data->stall_pct;
devstats->inst_count = common_evs[INST_IDX].last_delta;
if (mon->miss_ev)
devstats->mem_count =
mon->miss_ev[mon_idx].last_delta;
else {
devstats->inst_count = 0;
devstats->mem_count = 1;
}
}
return 0;
}
static void delete_event(struct event_data *event)
{
event->prev_count = event->last_delta = 0;
if (event->pevent) {
perf_event_release_kernel(event->pevent);
event->pevent = NULL;
}
}
static struct perf_event_attr *alloc_attr(void)
{
struct perf_event_attr *attr;
attr = kzalloc(sizeof(struct perf_event_attr), GFP_KERNEL);
if (!attr)
return attr;
attr->type = PERF_TYPE_RAW;
attr->size = sizeof(struct perf_event_attr);
attr->pinned = 1;
return attr;
}
static int set_event(struct event_data *ev, int cpu, unsigned int event_id,
struct perf_event_attr *attr)
{
struct perf_event *pevent;
if (!event_id)
return 0;
attr->config = event_id;
pevent = perf_event_create_kernel_counter(attr, cpu, NULL, NULL, NULL);
if (IS_ERR(pevent))
return PTR_ERR(pevent);
ev->pevent = pevent;
perf_event_enable(pevent);
return 0;
}
static int memlat_cpuhp_up(unsigned int cpu)
{
int ret = 0;
unsigned int i, mon_idx;
struct memlat_cpu_grp *cpu_grp;
struct memlat_mon *mon;
struct cpu_data *cpu_data;
struct event_data *common_evs;
struct perf_event_attr *attr = alloc_attr();
if (!attr)
return -ENOMEM;
list_for_each_entry(cpu_grp, &cpu_grp_list, node) {
if (!cpu_grp->initialized)
continue;
if (cpumask_test_cpu(cpu, &cpu_grp->cpus))
break;
}
if (!cpu_grp) {
kfree(attr);
return -EINVAL;
}
mutex_lock(&cpu_grp->mons_lock);
if (!cpu_grp->num_active_mons)
goto unlock_out;
cpu_data = to_cpu_data(cpu_grp, cpu);
common_evs = cpu_data->common_evs;
for (i = 0; i < NUM_COMMON_EVS; i++) {
ret = set_event(&common_evs[i], cpu,
cpu_grp->common_ev_ids[i], attr);
if (ret) {
pr_err("set event %u on CPU %u fail: %d",
cpu_grp->common_ev_ids[i],
cpu, ret);
goto unlock_out;
}
}
for (i = 0; i < cpu_grp->num_mons; i++) {
mon = &cpu_grp->mons[i];
if (!mon->is_active || !mon->miss_ev)
continue;
mon_idx = cpu - cpumask_first(&mon->cpus);
ret = set_event(&mon->miss_ev[mon_idx], cpu,
mon->miss_ev_id, attr);
if (ret) {
pr_err("set event %u on CPU %u fail: %d",
mon->miss_ev_id,
cpu, ret);
goto unlock_out;
}
}
per_cpu(is_on, cpu) = true;
unlock_out:
mutex_unlock(&cpu_grp->mons_lock);
kfree(attr);
return ret;
}
static int memlat_cpuhp_down(unsigned int cpu)
{
int ret = 0;
unsigned int i, mon_idx;
struct memlat_cpu_grp *cpu_grp;
struct memlat_mon *mon;
struct cpu_data *cpu_data;
struct event_data *common_evs;
list_for_each_entry(cpu_grp, &cpu_grp_list, node) {
if (!cpu_grp->initialized)
continue;
if (cpumask_test_cpu(cpu, &cpu_grp->cpus))
break;
}
if (!cpu_grp)
return -EINVAL;
mutex_lock(&cpu_grp->mons_lock);
if (!cpu_grp->num_active_mons)
goto unlock_out;
cpu_data = to_cpu_data(cpu_grp, cpu);
common_evs = cpu_data->common_evs;
for (i = 0; i < NUM_COMMON_EVS; i++)
delete_event(&common_evs[i]);
for (i = 0; i < cpu_grp->num_mons; i++) {
mon = &cpu_grp->mons[i];
if (!mon->is_active || !mon->miss_ev)
continue;
mon_idx = cpu - cpumask_first(&mon->cpus);
delete_event(&mon->miss_ev[mon_idx]);
}
per_cpu(is_on, cpu) = false;
unlock_out:
mutex_unlock(&cpu_grp->mons_lock);
return ret;
}
static int init_memlat_cpuhp(void)
{
int ret = 0;
ret = cpuhp_setup_state_nocalls(CPUHP_AP_ONLINE_DYN, "mem_latency",
memlat_cpuhp_up, memlat_cpuhp_down);
if (ret < 0)
pr_err("init cpuhp fail:%d\n", ret);
return ret;
}
static int init_common_evs(struct memlat_cpu_grp *cpu_grp,
struct perf_event_attr *attr)
{
unsigned int cpu, i;
int ret = 0;
cpu_grp->initialized = false;
for_each_cpu(cpu, &cpu_grp->cpus) {
struct event_data *common_evs = to_common_evs(cpu_grp, cpu);
for (i = 0; i < NUM_COMMON_EVS; i++) {
ret = set_event(&common_evs[i], cpu,
cpu_grp->common_ev_ids[i], attr);
if (ret)
break;
}
}
cpu_grp->initialized = true;
return ret;
}
static inline void queue_cpugrp_work(struct memlat_cpu_grp *cpu_grp)
{
if (cpumask_weight(&cpu_grp->cpus) == 1)
queue_delayed_work_on(cpumask_first(&cpu_grp->cpus), memlat_wq, &cpu_grp->work,
msecs_to_jiffies(cpu_grp->update_ms));
else
queue_delayed_work(memlat_wq, &cpu_grp->work,
msecs_to_jiffies(cpu_grp->update_ms));
}
static void free_common_evs(struct memlat_cpu_grp *cpu_grp)
{
unsigned int cpu, i;
for_each_cpu(cpu, &cpu_grp->cpus) {
struct event_data *common_evs = to_common_evs(cpu_grp, cpu);
for (i = 0; i < NUM_COMMON_EVS; i++)
delete_event(&common_evs[i]);
}
}
static void memlat_monitor_work(struct work_struct *work)
{
int err;
struct memlat_cpu_grp *cpu_grp =
container_of(work, struct memlat_cpu_grp, work.work);
struct memlat_mon *mon;
unsigned int i;
mutex_lock(&cpu_grp->mons_lock);
if (!cpu_grp->num_active_mons)
goto unlock_out;
update_counts(cpu_grp);
for (i = 0; i < cpu_grp->num_mons; i++) {
struct devfreq *df;
mon = &cpu_grp->mons[i];
if (!mon->is_active)
continue;
df = mon->hw.df;
mutex_lock(&df->lock);
err = update_devfreq(df);
if (err)
dev_err(mon->hw.dev, "Memlat update failed: %d\n", err);
mutex_unlock(&df->lock);
}
queue_cpugrp_work(cpu_grp);
unlock_out:
mutex_unlock(&cpu_grp->mons_lock);
}
static int start_hwmon(struct memlat_hwmon *hw)
{
int ret = 0;
unsigned int cpu;
struct memlat_mon *mon = to_mon(hw);
struct memlat_cpu_grp *cpu_grp = mon->cpu_grp;
bool should_init_cpu_grp;
struct perf_event_attr *attr = alloc_attr();
if (!attr)
return -ENOMEM;
mutex_lock(&cpu_grp->mons_lock);
should_init_cpu_grp = !(cpu_grp->num_active_mons++);
if (should_init_cpu_grp) {
ret = init_common_evs(cpu_grp, attr);
if (ret)
goto unlock_out;
INIT_LIST_HEAD(&cpu_grp->node);
list_add(&cpu_grp->node, &cpu_grp_list);
INIT_DEFERRABLE_WORK(&cpu_grp->work, &memlat_monitor_work);
}
if (mon->miss_ev) {
for_each_cpu(cpu, &mon->cpus) {
unsigned int idx = cpu - cpumask_first(&mon->cpus);
ret = set_event(&mon->miss_ev[idx], cpu,
mon->miss_ev_id, attr);
if (ret)
goto unlock_out;
}
}
mon->is_active = true;
if (should_init_cpu_grp)
queue_cpugrp_work(cpu_grp);
unlock_out:
mutex_unlock(&cpu_grp->mons_lock);
kfree(attr);
return ret;
}
static void stop_hwmon(struct memlat_hwmon *hw)
{
unsigned int cpu;
struct memlat_mon *mon = to_mon(hw);
struct memlat_cpu_grp *cpu_grp = mon->cpu_grp;
mutex_lock(&cpu_grp->mons_lock);
mon->is_active = false;
cpu_grp->num_active_mons--;
for_each_cpu(cpu, &mon->cpus) {
unsigned int idx = cpu - cpumask_first(&mon->cpus);
struct dev_stats *devstats = to_devstats(mon, cpu);
if (mon->miss_ev)
delete_event(&mon->miss_ev[idx]);
devstats->inst_count = 0;
devstats->mem_count = 0;
devstats->freq = 0;
devstats->stall_pct = 0;
}
if (!cpu_grp->num_active_mons) {
cancel_delayed_work(&cpu_grp->work);
free_common_evs(cpu_grp);
cpu_grp->initialized=false;
list_del(&cpu_grp->node);
}
mutex_unlock(&cpu_grp->mons_lock);
}
/**
* We should set update_ms to the lowest requested_update_ms of all of the
* active mons, or 0 (i.e. stop polling) if ALL active mons have 0.
* This is expected to be called with cpu_grp->mons_lock taken.
*/
static void set_update_ms(struct memlat_cpu_grp *cpu_grp)
{
struct memlat_mon *mon;
unsigned int i, new_update_ms = UINT_MAX;
for (i = 0; i < cpu_grp->num_mons; i++) {
mon = &cpu_grp->mons[i];
if (mon->is_active && mon->requested_update_ms)
new_update_ms =
min(new_update_ms, mon->requested_update_ms);
}
if (new_update_ms == UINT_MAX) {
cancel_delayed_work(&cpu_grp->work);
} else if (cpu_grp->update_ms == UINT_MAX) {
queue_cpugrp_work(cpu_grp);
} else if (new_update_ms > cpu_grp->update_ms) {
cancel_delayed_work(&cpu_grp->work);
queue_cpugrp_work(cpu_grp);
}
cpu_grp->update_ms = new_update_ms;
}
static void request_update_ms(struct memlat_hwmon *hw, unsigned int update_ms)
{
struct devfreq *df = hw->df;
struct memlat_mon *mon = to_mon(hw);
struct memlat_cpu_grp *cpu_grp = mon->cpu_grp;
mutex_lock(&df->lock);
df->profile->polling_ms = update_ms;
mutex_unlock(&df->lock);
mutex_lock(&cpu_grp->mons_lock);
mon->requested_update_ms = update_ms;
set_update_ms(cpu_grp);
mutex_unlock(&cpu_grp->mons_lock);
}
static int get_mask_from_dev_handle(struct platform_device *pdev,
cpumask_t *mask)
{
struct device *dev = &pdev->dev;
struct device_node *dev_phandle;
struct device *cpu_dev;
int cpu, i = 0;
int ret = -ENOENT;
dev_phandle = of_parse_phandle(dev->of_node, "cpulist", i++);
while (dev_phandle) {
for_each_possible_cpu(cpu) {
cpu_dev = get_cpu_device(cpu);
if (cpu_dev && cpu_dev->of_node == dev_phandle) {
cpumask_set_cpu(cpu, mask);
ret = 0;
break;
}
}
dev_phandle = of_parse_phandle(dev->of_node,
"cpulist", i++);
}
return ret;
}
#define DEFAULT_UPDATE_MS 100
static int memlat_cpu_grp_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct memlat_cpu_grp *cpu_grp;
int ret = 0;
unsigned int event_id, num_cpus, num_mons;
cpu_grp = devm_kzalloc(dev, sizeof(*cpu_grp), GFP_KERNEL);
if (!cpu_grp)
return -ENOMEM;
if (get_mask_from_dev_handle(pdev, &cpu_grp->cpus)) {
dev_err(dev, "No CPUs specified.\n");
return -ENODEV;
}
num_mons = of_get_available_child_count(dev->of_node);
if (!num_mons) {
dev_err(dev, "No mons provided.\n");
return -ENODEV;
}
cpu_grp->num_mons = num_mons;
cpu_grp->num_inited_mons = 0;
cpu_grp->mons =
devm_kzalloc(dev, num_mons * sizeof(*cpu_grp->mons),
GFP_KERNEL);
if (!cpu_grp->mons)
return -ENOMEM;
ret = of_property_read_u32(dev->of_node, "inst-ev", &event_id);
if (ret) {
dev_dbg(dev, "Inst event not specified. Using def:0x%x\n",
INST_EV);
event_id = INST_EV;
}
cpu_grp->common_ev_ids[INST_IDX] = event_id;
ret = of_property_read_u32(dev->of_node, "cyc-ev", &event_id);
if (ret) {
dev_dbg(dev, "Cyc event not specified. Using def:0x%x\n",
CYC_EV);
event_id = CYC_EV;
}
cpu_grp->common_ev_ids[CYC_IDX] = event_id;
ret = of_property_read_u32(dev->of_node, "stall-ev", &event_id);
if (ret) {
dev_dbg(dev, "Stall event not specified. Skipping.\n");
event_id = STALL_EV;
}
cpu_grp->common_ev_ids[STALL_IDX] = event_id;
num_cpus = cpumask_weight(&cpu_grp->cpus);
cpu_grp->cpus_data =
devm_kzalloc(dev, num_cpus * sizeof(*cpu_grp->cpus_data),
GFP_KERNEL);
if (!cpu_grp->cpus_data)
return -ENOMEM;
mutex_init(&cpu_grp->mons_lock);
cpu_grp->update_ms = DEFAULT_UPDATE_MS;
dev_set_drvdata(dev, cpu_grp);
return 0;
}
static int memlat_mon_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int ret = 0;
struct memlat_cpu_grp *cpu_grp;
struct memlat_mon *mon;
struct memlat_hwmon *hw;
unsigned int event_id, num_cpus, cpu;
struct cpu_data *cpu_data;
static int arm_mon_probe_count = 0;
if (!memlat_wq)
memlat_wq = alloc_workqueue("memlat_wq",
__WQ_LEGACY | WQ_FREEZABLE | WQ_MEM_RECLAIM, 1);
if (!memlat_wq) {
dev_err(dev, "Couldn't create memlat workqueue.\n");
return -ENOMEM;
}
cpu_grp = dev_get_drvdata(dev->parent);
if (!cpu_grp) {
dev_err(dev, "Mon initialized without cpu_grp.\n");
return -ENODEV;
}
mutex_lock_nested(&cpu_grp->mons_lock, SINGLE_DEPTH_NESTING);
mon = &cpu_grp->mons[cpu_grp->num_inited_mons];
mon->is_active = false;
mon->requested_update_ms = 0;
mon->cpu_grp = cpu_grp;
for_each_cpu (cpu, &cpu_grp->cpus) {
per_cpu(cpu_grp_p, cpu) = cpu_grp;
cpu_data = to_cpu_data(cpu_grp, cpu);
spin_lock_init(&cpu_data->pmu_lock);
}
if (get_mask_from_dev_handle(pdev, &mon->cpus)) {
cpumask_copy(&mon->cpus, &cpu_grp->cpus);
} else {
if (!cpumask_subset(&mon->cpus, &cpu_grp->cpus)) {
dev_err(dev,
"Mon CPUs must be a subset of cpu_grp CPUs. mon=%*pbl cpu_grp=%*pbl\n",
cpumask_pr_args(&mon->cpus), cpumask_pr_args(&cpu_grp->cpus));
ret = -EINVAL;
goto unlock_out;
}
}
num_cpus = cpumask_weight(&mon->cpus);
hw = &mon->hw;
hw->of_node = of_parse_phandle(dev->of_node, "target-dev", 0);
if (!hw->of_node) {
dev_err(dev, "Couldn't find a target device.\n");
ret = -ENODEV;
goto unlock_out;
}
hw->dev = dev;
hw->num_cores = num_cpus;
hw->should_ignore_df_monitor = true;
hw->core_stats = devm_kzalloc(dev, num_cpus * sizeof(*(hw->core_stats)),
GFP_KERNEL);
if (!hw->core_stats) {
ret = -ENOMEM;
goto unlock_out;
}
for_each_cpu(cpu, &mon->cpus)
to_devstats(mon, cpu)->id = cpu;
hw->start_hwmon = &start_hwmon;
hw->stop_hwmon = &stop_hwmon;
hw->get_cnt = &get_cnt;
hw->get_cpu_idle_state = &get_cpu_idle_state;
hw->request_update_ms = &request_update_ms;
mon->miss_ev =
devm_kzalloc(dev, num_cpus * sizeof(*mon->miss_ev),
GFP_KERNEL);
if (!mon->miss_ev) {
ret = -ENOMEM;
goto unlock_out;
}
ret = of_property_read_u32(dev->of_node, "cachemiss-ev",
&event_id);
if (ret) {
dev_err(dev, "Cache miss event missing for mon: %d\n",
ret);
ret = -EINVAL;
goto unlock_out;
}
mon->miss_ev_id = event_id;
ret = register_memlat(dev, hw);
arm_mon_probe_count++;
if (!ret)
cpu_grp->num_inited_mons++;
unlock_out:
if (arm_mon_probe_count == num_possible_cpus())
set_arm_mon_probe_done(true);
mutex_unlock(&cpu_grp->mons_lock);
return ret;
}
static bool hook_registered;
static bool cpuhp_registered;
static int arm_memlat_mon_driver_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int ret = 0;
unsigned int cpu;
const struct memlat_mon_spec *spec = of_device_get_match_data(dev);
enum mon_type type = NUM_MON_TYPES;
if (spec)
type = spec->type;
switch (type) {
case MEMLAT_CPU_GRP:
ret = memlat_cpu_grp_probe(pdev);
if (of_get_available_child_count(dev->of_node))
of_platform_populate(dev->of_node, NULL, NULL, dev);
break;
case MEMLAT_MON:
ret = memlat_mon_probe(pdev);
break;
default:
/*
* This should never happen.
*/
dev_err(dev, "Invalid memlat mon type specified: %u\n", type);
return -EINVAL;
}
if (ret) {
dev_err(dev, "Failure to probe memlat device: %d\n", ret);
return ret;
}
if (!cpuhp_registered) {
ret = init_memlat_cpuhp();
if (ret < 0) {
dev_err(dev, "Register CPU hotplug notifier fail %d\n", ret);
return ret;
}
for_each_cpu(cpu, cpu_online_mask) {
per_cpu(is_on, cpu) = true;
}
cpuhp_registered = true;
}
if (!hook_registered) {
ret = register_trace_android_vh_cpu_idle_enter(
vendor_update_event_cpu_idle_enter, NULL);
if (ret) {
dev_err(dev, "Register enter vendor hook fail %d\n", ret);
return ret;
}
ret = register_trace_android_vh_cpu_idle_exit(
vendor_update_event_cpu_idle_exit, NULL);
if (ret) {
dev_err(dev, "Register exit vendor hook fail %d\n", ret);
return ret;
}
hook_registered = true;
}
return 0;
}
static const struct memlat_mon_spec spec[] = {
[0] = { MEMLAT_CPU_GRP },
[1] = { MEMLAT_MON },
};
static const struct of_device_id memlat_match_table[] = {
{ .compatible = "arm-memlat-cpugrp", .data = &spec[0] },
{ .compatible = "arm-memlat-mon", .data = &spec[1] },
{}
};
MODULE_DEVICE_TABLE(of, memlat_match_table);
static struct platform_driver arm_memlat_mon_driver = {
.probe = arm_memlat_mon_driver_probe,
.driver = {
.name = "arm-memlat-mon",
.of_match_table = memlat_match_table,
.suppress_bind_attrs = true,
},
};
module_platform_driver(arm_memlat_mon_driver);
MODULE_LICENSE("GPL v2");