blob: b30ab0dfcbbdba7e5d857b2ae39f75ac123ff569 [file] [log] [blame]
/*
* 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.
*/
#include <linux/perf_event.h>
#include "met_drv.h"
#include "met_kernel_symbol.h"
#include "interface.h"
#include "trace.h"
#include "cpu_dsu.h"
#include "core_plf_init.h"
struct cpu_dsu_hw *cpu_dsu;
static int counter_cnt;
static struct kobject *kobj_dsu;
static int nr_arg;
static unsigned long long perfCurr[MXNR_DSU_EVENTS];
static unsigned long long perfPrev[MXNR_DSU_EVENTS];
static int perfCntFirst[MXNR_DSU_EVENTS];
static struct perf_event * pevent[MXNR_DSU_EVENTS];
static struct perf_event_attr pevent_attr[MXNR_DSU_EVENTS];
static unsigned int perf_device_type = 7;
static ssize_t perf_type_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%d\n", perf_device_type);
}
static ssize_t perf_type_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t n)
{
if (kstrtouint(buf, 0, &perf_device_type) != 0)
return -EINVAL;
return n;
}
static struct kobj_attribute perf_type_attr = __ATTR(perf_type, 0664, perf_type_show, perf_type_store);
noinline void mp_dsu(unsigned char cnt, unsigned int *value)
{
MET_GENERAL_PRINT(MET_TRACE, cnt, value);
}
static void dummy_handler(struct perf_event *event, struct perf_sample_data *data,
struct pt_regs *regs)
{
/*
* Required as perf_event_create_kernel_counter() requires an overflow handler,
* even though all we do is poll.
*/
}
static void perf_cpudsu_polling(unsigned long long stamp, int cpu)
{
int event_count = cpu_dsu->event_count;
struct met_dsu *pmu = cpu_dsu->pmu;
int i, count;
unsigned long long delta;
struct perf_event *ev;
unsigned int pmu_value[MXNR_DSU_EVENTS];
count = 0;
for (i = 0; i < event_count; i++) {
if (pmu[i].mode == 0)
continue;
ev = pevent[i];
if ((ev != NULL) && (ev->state == PERF_EVENT_STATE_ACTIVE)) {
perfCurr[i] = met_perf_event_read_local_symbol(ev);
delta = (perfCurr[i] - perfPrev[i]);
perfPrev[i] = perfCurr[i];
if (perfCntFirst[i] == 1) {
/* we shall omit delta counter when we get first counter */
perfCntFirst[i] = 0;
continue;
}
pmu_value[count] = (unsigned int)delta;
count++;
}
}
if (count == counter_cnt)
mp_dsu(count, pmu_value);
}
static int perf_thread_set_perf_events(unsigned int cpu)
{
int i, size;
struct perf_event *ev;
struct perf_event_attr *ev_attr;
int event_count = cpu_dsu->event_count;
struct met_dsu *pmu = cpu_dsu->pmu;
size = sizeof(struct perf_event_attr);
for (i = 0; i < event_count; i++) {
pevent[i] = NULL;
if (!pmu[i].mode)
continue; /* Skip disabled counters */
perfPrev[i] = 0;
perfCurr[i] = 0;
ev_attr = pevent_attr+i;
memset(ev_attr, 0, size);
ev_attr->config = pmu[i].event;
ev_attr->type = perf_device_type;
ev_attr->size = size;
ev_attr->sample_period = 0;
ev_attr->pinned = 1;
ev = perf_event_create_kernel_counter(ev_attr, cpu, NULL, dummy_handler, NULL);
if (IS_ERR(ev))
continue;
if (ev->state != PERF_EVENT_STATE_ACTIVE) {
perf_event_release_kernel(ev);
continue;
}
pevent[i] = ev;
if (ev != NULL)
perf_event_enable(ev);
} /* for all PMU counter */
return 0;
}
void met_perf_cpudsu_down(void)
{
int i;
struct perf_event *ev;
int event_count;
struct met_dsu *pmu;
if (met_cpudsu.mode == 0)
return;
event_count = cpu_dsu->event_count;
pmu = cpu_dsu->pmu;
for (i = 0; i < event_count; i++) {
if (!pmu[i].mode)
continue;
ev = pevent[i];
if ((ev != NULL) && (ev->state == PERF_EVENT_STATE_ACTIVE)) {
perf_event_disable(ev);
perf_event_release_kernel(ev);
}
pevent[i] = NULL;
}
//perf_delayed_work_setup = NULL;
}
inline static void met_perf_cpudsu_start(int cpu)
{
if (met_cpudsu.mode == 0)
return;
if (cpu != 0)
return;
perf_thread_set_perf_events(cpu);
}
static int cpudsu_create_subfs(struct kobject *parent)
{
int ret = 0;
cpu_dsu = cpu_dsu_hw_init();
if (cpu_dsu == NULL) {
PR_BOOTMSG("Failed to init CPU PMU HW!!\n");
return -ENODEV;
}
kobj_dsu = parent;
ret = sysfs_create_file(kobj_dsu, &perf_type_attr.attr);
if (ret != 0) {
PR_BOOTMSG("Failed to create perf_type in sysfs\n");
goto out;
}
out:
return ret;
}
static void cpudsu_delete_subfs(void)
{
}
void met_perf_cpudsu_polling(unsigned long long stamp, int cpu)
{
perf_cpudsu_polling(stamp, cpu);
}
static void cpudsu_start(void)
{
int cpu = raw_smp_processor_id();
for_each_online_cpu(cpu)
met_perf_cpudsu_start(cpu);
}
static void cpudsu_stop(void)
{
met_perf_cpudsu_down();
}
static const char header[] =
"met-info [000] 0.0: met_dsu_pmu_header: DSU";
static const char help[] =
" --dsu=EVENT select DSU-PMU events.\n"
" you can enable at most \"%d general purpose events\"\n";
static int cpudsu_print_help(char *buf, int len)
{
return snprintf(buf, PAGE_SIZE, help, cpu_dsu->event_count);
}
static int reset_driver_stat(void)
{
int i;
int event_count;
struct met_dsu *pmu;
met_cpudsu.mode = 0;
event_count = cpu_dsu->event_count;
pmu = cpu_dsu->pmu;
counter_cnt = 0;
nr_arg = 0;
for (i = 0; i < event_count; i++) {
pmu[i].mode = MODE_DISABLED;
pmu[i].event = 0;
pmu[i].freq = 0;
}
return 0;
}
static int cpudsu_print_header(char *buf, int len)
{
int first;
int i, ret;
int event_count;
struct met_dsu *pmu;
ret = 0;
ret += snprintf(buf + ret, PAGE_SIZE - ret, "# mp_dsu: pmu_value1, ...\n");
event_count = cpu_dsu->event_count;
pmu = cpu_dsu->pmu;
first = 1;
for (i = 0; i < event_count; i++) {
if (pmu[i].mode == 0)
continue;
if (first) {
ret += snprintf(buf + ret, PAGE_SIZE - ret, header);
first = 0;
}
ret += snprintf(buf + ret, PAGE_SIZE - ret, ",0x%x", pmu[i].event);
pmu[i].mode = 0;
}
ret += snprintf(buf + ret, PAGE_SIZE - ret, "\n");
reset_driver_stat();
return ret;
}
static int met_parse_num_list(char *arg, int len, int *list, int list_cnt)
{
int nr_num = 0;
char *num;
int num_len;
/* search ',' as the splitter */
while (len) {
num = arg;
num_len = 0;
if (list_cnt <= 0)
return -1;
while (len) {
len--;
if (*arg == ',') {
*(arg++) = '\0';
break;
}
arg++;
num_len++;
}
if (met_parse_num(num, list, num_len) < 0)
return -1;
list++;
list_cnt--;
nr_num++;
}
return nr_num;
}
static int cpudsu_process_argument(const char *arg, int len)
{
int nr_events, event_list[MXNR_DSU_EVENTS];
int i;
int nr_counters;
struct met_dsu *pmu;
int arg_nr;
int counters;
int event_no;
/* get event_list */
if ((nr_events = met_parse_num_list((char*)arg, len, event_list, ARRAY_SIZE(event_list))) <= 0)
goto arg_out;
/* for each cpu in cpu_list, add all the events in event_list */
nr_counters = cpu_dsu->event_count;
pmu = cpu_dsu->pmu;
arg_nr = nr_arg;
/*
* setup nr_counters for linux native perf mode.
* because the selected events are stored in pmu,
* so nr_counters can't large then event count in pmu.
*/
counters = perf_num_counters();
if (counters < nr_counters)
nr_counters = counters;
if (nr_counters == 0)
goto arg_out;
for (i = 0; i < nr_events; i++) {
event_no = event_list[i];
/*
* check if event is duplicate,
* but may not include 0xff when met_cpu_dsu_method == 0.
*/
if (cpu_dsu->check_event(pmu, arg_nr, event_no) < 0)
goto arg_out;
if (arg_nr >= nr_counters)
goto arg_out;
pmu[arg_nr].mode = MODE_POLLING;
pmu[arg_nr].event = event_no;
pmu[arg_nr].freq = 0;
arg_nr++;
counter_cnt++;
}
nr_arg = arg_nr;
met_cpudsu.mode = 1;
return 0;
arg_out:
reset_driver_stat();
return -EINVAL;
}
struct metdevice met_cpudsu = {
.name = "dsu",
.type = MET_TYPE_PMU,
.cpu_related = 0,
.create_subfs = cpudsu_create_subfs,
.delete_subfs = cpudsu_delete_subfs,
.start = cpudsu_start,
.stop = cpudsu_stop,
.polling_interval = 1,
.timed_polling = met_perf_cpudsu_polling,
.print_help = cpudsu_print_help,
.print_header = cpudsu_print_header,
.process_argument = cpudsu_process_argument
};