Squashed backup commit (no shallow-clone history issues) of xiaomi-mt6833-dev/kernel_xiaomi_mt6833 (lineage-23.2 branch, upstream commit 9eece662e) plus all fixes applied to get everpal_defconfig building clean against a modern (GCC 15.2.0) toolchain -- see the previous commit message on the lineage-23.2 branch (also in this repo history is not available here since this is a squashed snapshot; full incremental history lives in the local DL380 checkout and the upstream GitHub remote). Upstream: https://github.com/xiaomi-mt6833-dev/kernel_xiaomi_mt6833 (lineage-23.2)
528 lines
12 KiB
C
528 lines
12 KiB
C
/*
|
|
* Copyright (C) 2018 MediaTek Inc.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#undef pr_fmt
|
|
#define pr_fmt(fmt) "Cache-Auditor: " fmt
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/string.h>
|
|
#include <linux/perf_event.h>
|
|
#include <linux/printk.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/cgroup-defs.h>
|
|
#include <linux/cgroup.h>
|
|
#include <linux/moduleparam.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/notifier.h>
|
|
|
|
|
|
#include "internal.h"
|
|
|
|
#define CREATE_TRACE_POINTS
|
|
#include "trace_cache_auditor.h"
|
|
|
|
int ca55_register[] = {
|
|
[L2_PF_ST] = 0x12c,
|
|
[L2_PF_UNUSED] = 0x150,
|
|
// [L2_PF] = 0x148,
|
|
// [L2_PF_LD] = 0x12b,
|
|
// [L2_PF_REFILL] = 0x10a,
|
|
};
|
|
int ca75_register[] = {
|
|
[L2_PF_ST] = 0x12c,
|
|
[L2_PF_UNUSED] = 0x150,
|
|
// [L2_PF] = 0x148,
|
|
// [L2_PF_LD] = 0x12b,
|
|
// [L2_PF_REFILL] = 0x10a,
|
|
};
|
|
int ca76_register[] = {
|
|
[L2_PF_ST] = 0x12c,
|
|
[L2_PF_UNUSED] = 0x150,
|
|
};
|
|
|
|
DEFINE_PER_CPU(struct ca_pmu_stats, ca_pmu_stats);
|
|
|
|
static bool force_stop;
|
|
static int ca_hp_online = -1;
|
|
|
|
/* true, if perf_event alloc done */
|
|
static bool is_events_created;
|
|
|
|
static DEFINE_MUTEX(ca_pmu_lock);
|
|
|
|
void hook_ca_scheduler_tick(int cpu)
|
|
{
|
|
if (!is_events_created || !pftch_env.is_enabled)
|
|
return;
|
|
|
|
pftch_qos_tick(cpu);
|
|
}
|
|
|
|
static void ca_pmu_overflow_handler(struct perf_event *event,
|
|
struct perf_sample_data *data, struct pt_regs *regs)
|
|
{
|
|
unsigned long long count = local64_read(&event->count);
|
|
|
|
pr_info("[Overflow]: ignoring spurious overflow on cpu %u,config=%llu count=%llu\n",
|
|
event->cpu, event->attr.config, count);
|
|
}
|
|
|
|
static inline void ca_pmu_toggle_cpu_locked(int cpu, int enable)
|
|
{
|
|
struct perf_event **events;
|
|
int i;
|
|
|
|
events = per_cpu(ca_pmu_stats, cpu).events;
|
|
|
|
for (i = 0; i < NR_PMU_COUNTERS; i++) {
|
|
if (!events[i])
|
|
break;
|
|
|
|
if (enable)
|
|
perf_event_enable(events[i]);
|
|
else
|
|
perf_event_disable(events[i]);
|
|
}
|
|
}
|
|
|
|
static void ca_pmu_toggle_cpu(int cpu, bool enable)
|
|
{
|
|
mutex_lock(&ca_pmu_lock);
|
|
|
|
ca_pmu_toggle_cpu_locked(cpu, enable);
|
|
|
|
mutex_unlock(&ca_pmu_lock);
|
|
}
|
|
|
|
static int ca_pmu_create_counter(int cpu)
|
|
{
|
|
int i;
|
|
int *configs;
|
|
struct perf_event_attr attr = {
|
|
.type = PERF_TYPE_RAW,
|
|
.size = sizeof(struct perf_event_attr),
|
|
.pinned = 1,
|
|
.sample_period = 0,
|
|
.disabled = 1,
|
|
};
|
|
configs = per_cpu(ca_pmu_stats, cpu).config;
|
|
|
|
for (i = 0; i < NR_PMU_COUNTERS; i++) {
|
|
struct perf_event *event = per_cpu(ca_pmu_stats, cpu).events[i];
|
|
|
|
if (event)
|
|
break;
|
|
|
|
attr.config = configs[i];
|
|
event = perf_event_create_kernel_counter(&attr, cpu,
|
|
NULL, ca_pmu_overflow_handler, NULL);
|
|
|
|
if (IS_ERR(event)) {
|
|
pr_info("error code: %lu\n", PTR_ERR(event));
|
|
goto error;
|
|
}
|
|
per_cpu(ca_pmu_stats, cpu).events[i] = event;
|
|
}
|
|
|
|
return 0;
|
|
error:
|
|
pr_info("%s: [ERROR] in CPU%d, i=%d, config=%x\n",
|
|
__func__, cpu, i, configs[i]);
|
|
return -1;
|
|
}
|
|
|
|
static void ca_pmu_event_disable(void)
|
|
{
|
|
int cpu;
|
|
|
|
for_each_online_cpu(cpu) {
|
|
if (cpu >= num_possible_cpus())
|
|
break;
|
|
if (cpu_online(cpu))
|
|
ca_pmu_toggle_cpu_locked(cpu, false);
|
|
}
|
|
}
|
|
|
|
static void ca_pmu_event_enable(void)
|
|
{
|
|
int cpu;
|
|
|
|
for_each_online_cpu(cpu) {
|
|
if (cpu >= num_possible_cpus())
|
|
break;
|
|
if (cpu_online(cpu))
|
|
ca_pmu_toggle_cpu_locked(cpu, true);
|
|
}
|
|
}
|
|
|
|
int ca_cpu_online(unsigned int cpu)
|
|
{
|
|
ca_pmu_toggle_cpu(cpu, true);
|
|
return 0;
|
|
}
|
|
|
|
int ca_cpu_offline(unsigned int cpu)
|
|
{
|
|
ca_pmu_toggle_cpu(cpu, false);
|
|
return 0;
|
|
}
|
|
|
|
struct state_partition_stats {
|
|
int state[CONFIG_NR_CPUS];
|
|
};
|
|
|
|
void dump_preftech_config_per_cpu(void *info)
|
|
{
|
|
struct state_partition_stats *pstats;
|
|
int cpu = smp_processor_id();
|
|
|
|
unsigned long val = 5566;
|
|
|
|
pstats = (struct state_partition_stats *)info;
|
|
|
|
if (IS_ERR(pstats) || !pstats) {
|
|
pr_info("%s: [Error] Invalid info address\n", __func__);
|
|
return;
|
|
}
|
|
asm volatile(
|
|
"mrs %0, s3_0_c15_c1_4\n"
|
|
: "=r"(val)
|
|
);
|
|
if (IS_BIG_CORE(cpu))
|
|
pstats->state[cpu] = (val & 0x80a0);
|
|
else
|
|
pstats->state[cpu] = (val >> 10) & 0x7;
|
|
}
|
|
|
|
/* PROCFS */
|
|
static int ca_debug_proc_show(struct seq_file *m, void *v)
|
|
{
|
|
int i, cpu;
|
|
struct perf_event *event;
|
|
struct state_partition_stats pstats;
|
|
|
|
unsigned long val = 5566;
|
|
|
|
asm volatile(
|
|
"mrs %0, s3_0_c15_c1_4\n"
|
|
: "=r"(val)
|
|
);
|
|
seq_printf(m, "%s: show [%lu]\n", __func__, val);
|
|
|
|
|
|
seq_printf(m, "%s: show\n", __func__);
|
|
seq_puts(m, "ca55 configs = [");
|
|
for (i = 0; i < NR_PMU_COUNTERS; i++)
|
|
seq_printf(m, "0x%x, ", ca55_register[i]);
|
|
seq_puts(m, "]\n");
|
|
seq_puts(m, "ca75 configs = [");
|
|
for (i = 0; i < NR_PMU_COUNTERS; i++)
|
|
seq_printf(m, "0x%x, ", ca75_register[i]);
|
|
seq_puts(m, "]\n");
|
|
|
|
for_each_online_cpu(cpu) {
|
|
seq_printf(m, "cpu%d {\n", cpu);
|
|
for (i = 0; i < NR_PMU_COUNTERS; i++) {
|
|
event = per_cpu(ca_pmu_stats, cpu).events[i];
|
|
seq_printf(m, "\tconfig[%d]: 0x%llx (event=%p, state=%d)\n",
|
|
i, event->attr.config,
|
|
event, event->state);
|
|
}
|
|
seq_puts(m, "}\n");
|
|
}
|
|
|
|
seq_puts(m, "Dump ARM's preftech register.\n");
|
|
for (i = 0; i < nr_cpu_ids; i++) {
|
|
smp_call_function_single(i,
|
|
dump_preftech_config_per_cpu, &pstats, 1);
|
|
}
|
|
for (i = 0; i < nr_cpu_ids; i++) {
|
|
int nr_caches = -1;
|
|
|
|
if (IS_BIG_CORE(i)) {
|
|
seq_printf(m, "\tCPU%d: PF_DIS[15]: %s, PF_STS_DIS[7]:%s",
|
|
i, pstats.state[i] & 0x4 ? "off" : "on",
|
|
pstats.state[i] & 0x2 ? "off" : "on");
|
|
seq_printf(m, ", RPF_DIS[5]: %s\n",
|
|
pstats.state[i] & 0x1 ? "off" : "on");
|
|
} else {
|
|
if (pstats.state[i] == 4)
|
|
nr_caches = 0;
|
|
else if ((pstats.state[i] + 4) % 8 < 6)
|
|
nr_caches = 1 << ((pstats.state[i] + 4) % 8);
|
|
|
|
seq_printf(m, "\tCPU%d: 0x%x (%d cache lines)\n", i,
|
|
pstats.state[i], nr_caches);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int ca_debug_proc_open(struct inode *inode,
|
|
struct file *file)
|
|
{
|
|
return single_open(file, ca_debug_proc_show, NULL);
|
|
}
|
|
|
|
static const struct file_operations ca_debug_procfs = {
|
|
.owner = THIS_MODULE,
|
|
.open = ca_debug_proc_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
|
|
int set_pftch_qos_control(const char *buf, const struct kernel_param *kp)
|
|
{
|
|
int ret = 0;
|
|
int val, paranoid_backup;
|
|
unsigned long long start, end;
|
|
|
|
start = sched_clock();
|
|
ret = kstrtoint(buf, 0, &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
if (!is_events_created)
|
|
return -EAGAIN;
|
|
mutex_lock(&ca_pmu_lock);
|
|
if (force_stop) {
|
|
pr_debug("Stopped by force_stop property. (Please config to false to enable)\n");
|
|
mutex_unlock(&ca_pmu_lock);
|
|
return -EAGAIN;
|
|
}
|
|
if (val == pftch_env.is_enabled) {
|
|
pr_debug("No need to change config\n");
|
|
mutex_unlock(&ca_pmu_lock);
|
|
return 0;
|
|
}
|
|
pr_info("Changing state from %d to %d ...\n",
|
|
pftch_env.is_enabled, val);
|
|
// backup paranoid and set to 0 for pmu counter permission
|
|
paranoid_backup = sysctl_perf_event_paranoid;
|
|
sysctl_perf_event_paranoid = 0;
|
|
if (val == 0) {
|
|
ret = param_set_int(buf, kp);
|
|
ca_pmu_event_disable();
|
|
} else if (val > 0) {
|
|
if (pftch_env.is_enabled == 0)
|
|
ca_pmu_event_enable();
|
|
ret = param_set_int(buf, kp);
|
|
}
|
|
sysctl_perf_event_paranoid = paranoid_backup;
|
|
mutex_unlock(&ca_pmu_lock);
|
|
if (ret < 0)
|
|
return ret;
|
|
end = sched_clock();
|
|
pr_info("[Change complete] %s with %llu ns with mode %d\n",
|
|
pftch_env.is_enabled ? "enable":"disable",
|
|
(end - start), pftch_env.is_enabled);
|
|
return 0;
|
|
}
|
|
|
|
int ca_force_stop_set_in_kernel(int val)
|
|
{
|
|
int paranoid_backup;
|
|
|
|
if (!is_events_created)
|
|
return -EAGAIN;
|
|
mutex_lock(&ca_pmu_lock);
|
|
if (val == force_stop) {
|
|
pr_debug("No need to change force_stop config\n");
|
|
mutex_unlock(&ca_pmu_lock);
|
|
return 0;
|
|
}
|
|
pr_info("Change force_stop setting [%d -> %d], pftch_env.is_enabled:%d from:%pS\n",
|
|
force_stop, val, pftch_env.is_enabled,
|
|
__builtin_return_address(0));
|
|
// backup paranoid and set to 0 for pmu counter permission
|
|
paranoid_backup = sysctl_perf_event_paranoid;
|
|
sysctl_perf_event_paranoid = 0;
|
|
if (val == true && pftch_env.is_enabled > 0) {
|
|
ca_pmu_event_disable();
|
|
pftch_env.is_enabled = 0;
|
|
}
|
|
force_stop = val;
|
|
sysctl_perf_event_paranoid = paranoid_backup;
|
|
mutex_unlock(&ca_pmu_lock);
|
|
pr_info("[Change complete] force_stop:%d\n", force_stop);
|
|
return 0;
|
|
}
|
|
|
|
static int ca_force_stop_set(const char *buf, const struct kernel_param *kp)
|
|
{
|
|
int ret = 0;
|
|
bool val;
|
|
int paranoid_backup;
|
|
|
|
ret = kstrtobool(buf, &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
if (!is_events_created)
|
|
return -EAGAIN;
|
|
mutex_lock(&ca_pmu_lock);
|
|
if (val == force_stop) {
|
|
pr_debug("No need to change force_stop config\n");
|
|
mutex_unlock(&ca_pmu_lock);
|
|
return 0;
|
|
}
|
|
pr_info("Change force_stop setting [%d -> %d], pftch_env.is_enabled:%d\n",
|
|
force_stop, val, pftch_env.is_enabled);
|
|
// backup paranoid and set to 0 for pmu counter permission
|
|
paranoid_backup = sysctl_perf_event_paranoid;
|
|
sysctl_perf_event_paranoid = 0;
|
|
if (val == true && pftch_env.is_enabled > 0) {
|
|
ca_pmu_event_disable();
|
|
pftch_env.is_enabled = 0;
|
|
}
|
|
ret = param_set_bool(buf, kp);
|
|
sysctl_perf_event_paranoid = paranoid_backup;
|
|
mutex_unlock(&ca_pmu_lock);
|
|
if (ret < 0)
|
|
return ret;
|
|
pr_info("[Change complete] force_stop:%d\n", force_stop);
|
|
return 0;
|
|
}
|
|
struct kernel_param_ops force_stop_cb = {
|
|
/* Returns 0, or -errno. arg is in kp->arg. */
|
|
.set = ca_force_stop_set,
|
|
/* Returns length written or -errno. Buffer is 4k (ie. be short!) */
|
|
.get = param_get_bool,
|
|
};
|
|
param_check_bool(force_stop, &force_stop);
|
|
module_param_cb(force_stop, &force_stop_cb, &force_stop, 0664);
|
|
__MODULE_PARM_TYPE(force_stop, "bool");
|
|
|
|
static const struct cpu_config_node config_table[] = {
|
|
{ "arm,cortex-a76", ca76_register},
|
|
{ "arm,cortex-a75", ca75_register},
|
|
{ "arm,cortex-a55", ca55_register},
|
|
{ NULL, },
|
|
};
|
|
|
|
static void __init init_cpu_config(void)
|
|
{
|
|
const struct cpu_config_node *config_index;
|
|
struct device_node *cn = NULL;
|
|
int cpu;
|
|
|
|
for_each_possible_cpu(cpu) {
|
|
cn = of_get_cpu_node(cpu, NULL);
|
|
|
|
if (!cn) {
|
|
pr_info("Missing device node for CPU %d\n", cpu);
|
|
continue;
|
|
}
|
|
|
|
for (config_index = config_table;
|
|
config_index->compatible; config_index++)
|
|
if (of_device_is_compatible(cn,
|
|
config_index->compatible))
|
|
break;
|
|
|
|
if (config_index->compatible == NULL) {
|
|
pr_info("No compatible register for cpu%d: %s\n",
|
|
cpu, config_index->compatible);
|
|
continue;
|
|
}
|
|
per_cpu(ca_pmu_stats, cpu).config = config_index->config;
|
|
pr_debug("%s: Init cpu%d with %pf", __func__,
|
|
cpu, config_index->config);
|
|
}
|
|
pr_info("%s: [Done]\n", __func__);
|
|
}
|
|
|
|
static int __init ca_init(void)
|
|
{
|
|
struct pentry {
|
|
const char *name;
|
|
const struct file_operations *fops;
|
|
};
|
|
int cpu, ret;
|
|
|
|
if (!proc_create("ca_debug", 0444, NULL, &ca_debug_procfs)) {
|
|
pr_info("[Error] create /proc/ca_debug failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
init_cache_priority();
|
|
init_cpu_config();
|
|
for_each_possible_cpu(cpu) {
|
|
|
|
if (cpu >= num_possible_cpus())
|
|
break;
|
|
|
|
ret = ca_pmu_create_counter(cpu);
|
|
if (ret) {
|
|
pr_info("%s: [ERROR] ca_pmu_create_counter(CPU%d)\n",
|
|
__func__, cpu);
|
|
return ret;
|
|
}
|
|
}
|
|
is_events_created = true;
|
|
|
|
register_qos_notifier(&nb);
|
|
|
|
ret = cpuhp_setup_state_nocalls(CPUHP_AP_ONLINE_DYN,
|
|
"ca_drv/l3cc:online", ca_cpu_online, ca_cpu_offline);
|
|
if (ret < 0) {
|
|
pr_info("%s: [Error] fail to register_cpu_notifier.\n",
|
|
__func__);
|
|
return ret;
|
|
}
|
|
|
|
ca_hp_online = ret;
|
|
pr_info("%s: [Done]\n", __func__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __exit ca_exit(void)
|
|
{
|
|
int i, cpu;
|
|
struct perf_event *event;
|
|
|
|
if (!is_events_created)
|
|
return;
|
|
|
|
unregister_qos_notifier(&nb);
|
|
cpuhp_remove_state_nocalls(ca_hp_online);
|
|
|
|
mutex_lock(&ca_pmu_lock);
|
|
if (pftch_env.is_enabled)
|
|
ca_pmu_event_disable();
|
|
mutex_unlock(&ca_pmu_lock);
|
|
|
|
for_each_possible_cpu(cpu) {
|
|
|
|
if (cpu >= num_possible_cpus())
|
|
break;
|
|
|
|
for (i = 0; i < NR_PMU_COUNTERS; i++) {
|
|
event = per_cpu(ca_pmu_stats, cpu).events[i];
|
|
|
|
if (!event)
|
|
break;
|
|
perf_event_release_kernel(event);
|
|
}
|
|
}
|
|
}
|
|
|
|
module_init(ca_init);
|
|
module_exit(ca_exit);
|
|
|