blob: 4fed966d707c7e1a021bd42ea9c4c125c064a71c [file] [log] [blame]
/*
* Copyright (C) 2019 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/mm.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/cpu.h>
#include <linux/module.h>
#include <linux/fs.h>
#include "met_drv.h"
#include "mem_stat.h"
#include "trace.h"
/* define MEMSTAT_DEBUG */
#ifdef MEMSTAT_DEBUG
#define debug_memstat(fmt, arg...) pr_debug(fmt, ##arg)
#else
#define debug_memstat(fmt, arg...) do {} while (0)
#endif
struct metdevice met_memstat;
unsigned int phy_memstat_mask;
unsigned int vir_memstat_mask;
#define MAX_PHY_MEMSTAT_EVENT_AMOUNT 6
#define MAX_VIR_MEMSTAT_EVENT_AMOUNT 6
struct mem_event phy_memstat_table[] = {
{FREE_MEM, "free_mem", "Free Memory"}
};
#define PHY_MEMSTAT_TABLE_SIZE (sizeof(phy_memstat_table) / sizeof(struct mem_event))
struct mem_event vir_memstat_table[] = {
{FILE_PAGES, "file_pages", "File Pages"},
{FILE_DIRTY, "file_dirty", "FD APP->FS(KB)"},
{NUM_DIRTIED, "num_dirtied", "Num Dirtied"},
{WRITE_BACK, "write_back", "WB. FS->Block IO(KB)"},
{NUM_WRITTEN, "num_written", "Num Written"},
{PG_FAULT_CNT, "pg_fault_cnt", "Page Fault Count"}
};
#define VIR_MEMSTAT_TABLE_SIZE (sizeof(vir_memstat_table) / sizeof(struct mem_event))
int vm_event_counters_enable;
unsigned long *vm_status;
static struct delayed_work dwork;
noinline void memstat(unsigned int cnt, unsigned int *value)
{
MET_GENERAL_PRINT(MET_TRACE, cnt, value);
}
static int get_phy_memstat(unsigned int *value)
{
int i, cnt = 0;
struct sysinfo info;
#define K(x) ((x) << (PAGE_SHIFT - 10))
si_meminfo(&info);
for (i = 0; i < MAX_PHY_MEMSTAT_EVENT_AMOUNT; i++) {
if (phy_memstat_mask & (1 << i)) {
switch (i) {
case FREE_MEM:
value[cnt] = K(info.freeram);
break;
}
cnt++;
}
}
return cnt;
}
static int get_vir_memstat(unsigned int *value)
{
int i, cnt = 0;
for (i = 0; i < NR_VM_ZONE_STAT_ITEMS; i++)
vm_status[i] = global_zone_page_state(i);
all_vm_events(vm_status + NR_VM_ZONE_STAT_ITEMS);
for (i = 0; i < MAX_VIR_MEMSTAT_EVENT_AMOUNT; i++) {
if (vir_memstat_mask & (1 << i)) {
switch (i) {
case FILE_PAGES:
value[cnt] = vm_status[NR_FILE_PAGES] << (PAGE_SHIFT - 10);
break;
case FILE_DIRTY:
value[cnt] = vm_status[NR_FILE_DIRTY] << (PAGE_SHIFT - 10);
break;
case NUM_DIRTIED:
value[cnt] = vm_status[NR_DIRTIED] << (PAGE_SHIFT - 10);
break;
case WRITE_BACK:
value[cnt] = vm_status[NR_WRITEBACK] << (PAGE_SHIFT - 10);
break;
case NUM_WRITTEN:
value[cnt] = vm_status[NR_WRITTEN] << (PAGE_SHIFT - 10);
break;
case PG_FAULT_CNT:
value[cnt] = vm_status[NR_VM_ZONE_STAT_ITEMS + PGFAULT];
break;
}
cnt++;
}
}
return cnt;
}
static void wq_get_memstat(struct work_struct *work)
{
int total_event_amount = 0, phy_event_amount = 0;
unsigned int stat_val[MAX_PHY_MEMSTAT_EVENT_AMOUNT + MAX_VIR_MEMSTAT_EVENT_AMOUNT];
memset(stat_val, 0, sizeof(unsigned int) * (MAX_PHY_MEMSTAT_EVENT_AMOUNT + MAX_VIR_MEMSTAT_EVENT_AMOUNT));
total_event_amount = phy_event_amount = get_phy_memstat(stat_val);
if (vm_event_counters_enable)
total_event_amount += get_vir_memstat(&(stat_val[phy_event_amount]));
if (total_event_amount <= (MAX_PHY_MEMSTAT_EVENT_AMOUNT + MAX_VIR_MEMSTAT_EVENT_AMOUNT))
memstat(total_event_amount-1, stat_val);
}
void met_memstat_polling(unsigned long long stamp, int cpu)
{
schedule_delayed_work(&dwork, 0);
}
static void met_memstat_start(void)
{
int stat_items_size = 0;
stat_items_size = NR_VM_ZONE_STAT_ITEMS * sizeof(unsigned long);
#ifdef CONFIG_VM_EVENT_COUNTERS
stat_items_size += sizeof(struct vm_event_state);
#endif
vm_status = kmalloc(stat_items_size, GFP_KERNEL);
if (vm_status == NULL)
return;
INIT_DELAYED_WORK(&dwork, wq_get_memstat);
}
static void met_memstat_stop(void)
{
kfree(vm_status);
cancel_delayed_work_sync(&dwork);
}
static const char help[] =
" --memstat=[phy_mem_stat|vir_mem_stat]:event_name enable sampling physical & virtual memory status\n";
static int met_memstat_print_help(char *buf, int len)
{
int i, l;
l = snprintf(buf, PAGE_SIZE, help);
for (i = 0; i < PHY_MEMSTAT_TABLE_SIZE; i++)
l += snprintf(buf + l, PAGE_SIZE - l, " --memstat=phy_mem_stat:%s\n",
phy_memstat_table[i].name);
#ifdef CONFIG_VM_EVENT_COUNTERS
for (i = 0; i < VIR_MEMSTAT_TABLE_SIZE; i++)
l += snprintf(buf + l, PAGE_SIZE - l, " --memstat=vir_mem_stat:%s\n",
vir_memstat_table[i].name);
#endif
return l;
}
static const char header[] = "met-info [000] 0.0: ms_ud_sys_header: memstat,";
static int met_memstat_print_header(char *buf, int len)
{
int i, l;
int event_amount = 0;
l = snprintf(buf, PAGE_SIZE, header);
for (i = 0; i < MAX_PHY_MEMSTAT_EVENT_AMOUNT; i++) {
if ((phy_memstat_mask & (1 << i)) && (i < PHY_MEMSTAT_TABLE_SIZE)) {
l += snprintf(buf + l, PAGE_SIZE - l, phy_memstat_table[i].header_name);
l += snprintf(buf + l, PAGE_SIZE - l, ",");
event_amount++;
}
}
#ifdef CONFIG_VM_EVENT_COUNTERS
for (i = 0; i < MAX_VIR_MEMSTAT_EVENT_AMOUNT; i++) {
if ((vir_memstat_mask & (1 << i)) && (i < VIR_MEMSTAT_TABLE_SIZE)) {
l += snprintf(buf + l, PAGE_SIZE - l, vir_memstat_table[i].header_name);
l += snprintf(buf + l, PAGE_SIZE - l, ",");
event_amount++;
}
}
#endif
for (i = 0; i < event_amount; i++) {
l += snprintf(buf + l, PAGE_SIZE - l, "x");
l += snprintf(buf + l, PAGE_SIZE - l, ",");
}
phy_memstat_mask = 0;
vir_memstat_mask = 0;
l += snprintf(buf + l, PAGE_SIZE - l, "\n");
return l;
}
static int met_memstat_process_argument(const char *arg, int len)
{
int i, found_event = 0;
char choice[16], event[32];
char *pch;
int str_len;
#ifdef CONFIG_VM_EVENT_COUNTERS
vm_event_counters_enable = 1;
#endif
pch = strchr(arg, ':');
if (pch == NULL)
goto error;
memset(choice, 0, sizeof(choice));
memset(event, 0, sizeof(event));
str_len = (int)(pch - arg);
memcpy(choice, arg, str_len);
memcpy(event, arg + str_len + 1, len - (str_len + 1));
if (strncmp(choice, "phy_mem_stat", 12) == 0) {
for (i = 0; i < PHY_MEMSTAT_TABLE_SIZE; i++) {
if (strncmp(event, phy_memstat_table[i].name, MAX_EVENT_NAME_LEN) == 0) {
phy_memstat_mask |= (1 << phy_memstat_table[i].id);
found_event = 1;
break;
}
}
} else if (strncmp(choice, "vir_mem_stat", 12) == 0) {
if (!vm_event_counters_enable) {
pr_debug("[%s] %d: CONFIG_VM_EVENT_COUNTERS is not configured\n", __func__,
__LINE__);
goto error;
}
for (i = 0; i < VIR_MEMSTAT_TABLE_SIZE; i++) {
if (strncmp(event, vir_memstat_table[i].name, MAX_EVENT_NAME_LEN) == 0) {
vir_memstat_mask |= (1 << vir_memstat_table[i].id);
found_event = 1;
break;
}
}
} else {
pr_debug("[%s] %d: only support phy_mem_stat & vir_mem_stat keyword\n", __func__,
__LINE__);
goto error;
}
if (!found_event) {
pr_debug("[%s] %d: input event name error\n", __func__, __LINE__);
goto error;
}
met_memstat.mode = 1;
return 0;
error:
met_memstat.mode = 0;
return -EINVAL;
}
struct metdevice met_memstat = {
.name = "memstat",
.type = MET_TYPE_PMU,
.cpu_related = 0,
.start = met_memstat_start,
.stop = met_memstat_stop,
.polling_interval = 1,
.timed_polling = met_memstat_polling,
.tagged_polling = met_memstat_polling,
.print_help = met_memstat_print_help,
.print_header = met_memstat_print_header,
.process_argument = met_memstat_process_argument
};