| /** |
| * Copyright (C) ARM Limited 2010-2013. All rights reserved. |
| * |
| * 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. |
| * |
| */ |
| |
| #include "gator.h" |
| #include <linux/workqueue.h> |
| #include <trace/events/kmem.h> |
| #include <linux/hardirq.h> |
| |
| #define MEMINFO_MEMFREE 0 |
| #define MEMINFO_MEMUSED 1 |
| #define MEMINFO_BUFFERRAM 2 |
| #define MEMINFO_TOTAL 3 |
| |
| static ulong meminfo_global_enabled; |
| static ulong meminfo_enabled[MEMINFO_TOTAL]; |
| static ulong meminfo_key[MEMINFO_TOTAL]; |
| static unsigned long long meminfo_buffer[MEMINFO_TOTAL * 2]; |
| static int meminfo_length = 0; |
| static unsigned int mem_event = 0; |
| static bool new_data_avail; |
| |
| static void wq_sched_handler(struct work_struct *wsptr); |
| DECLARE_WORK(work, wq_sched_handler); |
| static struct timer_list meminfo_wake_up_timer; |
| static void meminfo_wake_up_handler(unsigned long unused_data); |
| |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 3, 0) |
| GATOR_DEFINE_PROBE(mm_page_free_direct, TP_PROTO(struct page *page, unsigned int order)) |
| #else |
| GATOR_DEFINE_PROBE(mm_page_free, TP_PROTO(struct page *page, unsigned int order)) |
| #endif |
| { |
| mem_event++; |
| } |
| |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 3, 0) |
| GATOR_DEFINE_PROBE(mm_pagevec_free, TP_PROTO(struct page *page, int cold)) |
| #else |
| GATOR_DEFINE_PROBE(mm_page_free_batched, TP_PROTO(struct page *page, int cold)) |
| #endif |
| { |
| mem_event++; |
| } |
| |
| GATOR_DEFINE_PROBE(mm_page_alloc, TP_PROTO(struct page *page, unsigned int order, gfp_t gfp_flags, int migratetype)) |
| { |
| mem_event++; |
| } |
| |
| static int gator_events_meminfo_create_files(struct super_block *sb, struct dentry *root) |
| { |
| struct dentry *dir; |
| int i; |
| |
| for (i = 0; i < MEMINFO_TOTAL; i++) { |
| switch (i) { |
| case MEMINFO_MEMFREE: |
| dir = gatorfs_mkdir(sb, root, "Linux_meminfo_memfree"); |
| break; |
| case MEMINFO_MEMUSED: |
| dir = gatorfs_mkdir(sb, root, "Linux_meminfo_memused"); |
| break; |
| case MEMINFO_BUFFERRAM: |
| dir = gatorfs_mkdir(sb, root, "Linux_meminfo_bufferram"); |
| break; |
| default: |
| return -1; |
| } |
| if (!dir) { |
| return -1; |
| } |
| gatorfs_create_ulong(sb, dir, "enabled", &meminfo_enabled[i]); |
| gatorfs_create_ro_ulong(sb, dir, "key", &meminfo_key[i]); |
| } |
| |
| return 0; |
| } |
| |
| static int gator_events_meminfo_start(void) |
| { |
| int i; |
| |
| new_data_avail = true; |
| for (i = 0; i < MEMINFO_TOTAL; i++) { |
| if (meminfo_enabled[i]) { |
| meminfo_global_enabled = 1; |
| } |
| } |
| |
| if (meminfo_global_enabled == 0) |
| return 0; |
| |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 3, 0) |
| if (GATOR_REGISTER_TRACE(mm_page_free_direct)) |
| #else |
| if (GATOR_REGISTER_TRACE(mm_page_free)) |
| #endif |
| goto mm_page_free_exit; |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 3, 0) |
| if (GATOR_REGISTER_TRACE(mm_pagevec_free)) |
| #else |
| if (GATOR_REGISTER_TRACE(mm_page_free_batched)) |
| #endif |
| goto mm_page_free_batched_exit; |
| if (GATOR_REGISTER_TRACE(mm_page_alloc)) |
| goto mm_page_alloc_exit; |
| |
| setup_timer(&meminfo_wake_up_timer, meminfo_wake_up_handler, 0); |
| return 0; |
| |
| mm_page_alloc_exit: |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 3, 0) |
| GATOR_UNREGISTER_TRACE(mm_pagevec_free); |
| #else |
| GATOR_UNREGISTER_TRACE(mm_page_free_batched); |
| #endif |
| mm_page_free_batched_exit: |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 3, 0) |
| GATOR_UNREGISTER_TRACE(mm_page_free_direct); |
| #else |
| GATOR_UNREGISTER_TRACE(mm_page_free); |
| #endif |
| mm_page_free_exit: |
| return -1; |
| } |
| |
| static void gator_events_meminfo_stop(void) |
| { |
| int i; |
| |
| if (meminfo_global_enabled) { |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 3, 0) |
| GATOR_UNREGISTER_TRACE(mm_page_free_direct); |
| GATOR_UNREGISTER_TRACE(mm_pagevec_free); |
| #else |
| GATOR_UNREGISTER_TRACE(mm_page_free); |
| GATOR_UNREGISTER_TRACE(mm_page_free_batched); |
| #endif |
| GATOR_UNREGISTER_TRACE(mm_page_alloc); |
| |
| del_timer_sync(&meminfo_wake_up_timer); |
| } |
| |
| meminfo_global_enabled = 0; |
| for (i = 0; i < MEMINFO_TOTAL; i++) { |
| meminfo_enabled[i] = 0; |
| } |
| } |
| |
| // Must be run in process context as the kernel function si_meminfo() can sleep |
| static void wq_sched_handler(struct work_struct *wsptr) |
| { |
| struct sysinfo info; |
| int i, len; |
| unsigned long long value; |
| |
| meminfo_length = len = 0; |
| |
| si_meminfo(&info); |
| for (i = 0; i < MEMINFO_TOTAL; i++) { |
| if (meminfo_enabled[i]) { |
| switch (i) { |
| case MEMINFO_MEMFREE: |
| value = info.freeram * PAGE_SIZE; |
| break; |
| case MEMINFO_MEMUSED: |
| value = (info.totalram - info.freeram) * PAGE_SIZE; |
| break; |
| case MEMINFO_BUFFERRAM: |
| value = info.bufferram * PAGE_SIZE; |
| break; |
| default: |
| value = 0; |
| break; |
| } |
| meminfo_buffer[len++] = (unsigned long long)meminfo_key[i]; |
| meminfo_buffer[len++] = value; |
| } |
| } |
| |
| meminfo_length = len; |
| new_data_avail = true; |
| } |
| |
| static void meminfo_wake_up_handler(unsigned long unused_data) |
| { |
| // had to delay scheduling work as attempting to schedule work during the context switch is illegal in kernel versions 3.5 and greater |
| schedule_work(&work); |
| } |
| |
| static int gator_events_meminfo_read(long long **buffer) |
| { |
| static unsigned int last_mem_event = 0; |
| |
| if (!on_primary_core() || !meminfo_global_enabled) |
| return 0; |
| |
| if (last_mem_event != mem_event) { |
| last_mem_event = mem_event; |
| mod_timer(&meminfo_wake_up_timer, jiffies + 1); |
| } |
| |
| if (!new_data_avail) |
| return 0; |
| |
| new_data_avail = false; |
| |
| if (buffer) |
| *buffer = meminfo_buffer; |
| |
| return meminfo_length; |
| } |
| |
| static struct gator_interface gator_events_meminfo_interface = { |
| .create_files = gator_events_meminfo_create_files, |
| .start = gator_events_meminfo_start, |
| .stop = gator_events_meminfo_stop, |
| .read64 = gator_events_meminfo_read, |
| }; |
| |
| int gator_events_meminfo_init(void) |
| { |
| int i; |
| |
| meminfo_global_enabled = 0; |
| for (i = 0; i < MEMINFO_TOTAL; i++) { |
| meminfo_enabled[i] = 0; |
| meminfo_key[i] = gator_events_get_key(); |
| } |
| |
| return gator_events_install(&gator_events_meminfo_interface); |
| } |
| |
| gator_events_init(gator_events_meminfo_init); |