|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | /* | 
|  | * Copyright (c) 2019 MediaTek Inc. | 
|  | */ | 
|  |  | 
|  | #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); | 
|  |  | 
|  | #if IS_ENABLED(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); | 
|  |  | 
|  | #if IS_ENABLED(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++; | 
|  | } | 
|  | } | 
|  |  | 
|  | #if IS_ENABLED(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; | 
|  |  | 
|  |  | 
|  | #if IS_ENABLED(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); | 
|  | if (str_len >= 16) | 
|  | goto error; | 
|  | memcpy(choice, arg, str_len); | 
|  |  | 
|  | if (len - (str_len + 1) >= 32) | 
|  | goto error; | 
|  | 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 | 
|  | }; |