1 /* 2 * Copyright (C) IBM Corporation, 2014, 2017 3 * Anton Blanchard, Rashmica Gupta. 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 */ 10 11 #define pr_fmt(fmt) "memtrace: " fmt 12 13 #include <linux/bitops.h> 14 #include <linux/string.h> 15 #include <linux/memblock.h> 16 #include <linux/init.h> 17 #include <linux/moduleparam.h> 18 #include <linux/fs.h> 19 #include <linux/debugfs.h> 20 #include <linux/slab.h> 21 #include <linux/memory.h> 22 #include <linux/memory_hotplug.h> 23 #include <asm/machdep.h> 24 #include <asm/debugfs.h> 25 26 /* This enables us to keep track of the memory removed from each node. */ 27 struct memtrace_entry { 28 void *mem; 29 u64 start; 30 u64 size; 31 u32 nid; 32 struct dentry *dir; 33 char name[16]; 34 }; 35 36 static u64 memtrace_size; 37 38 static struct memtrace_entry *memtrace_array; 39 static unsigned int memtrace_array_nr; 40 41 42 static ssize_t memtrace_read(struct file *filp, char __user *ubuf, 43 size_t count, loff_t *ppos) 44 { 45 struct memtrace_entry *ent = filp->private_data; 46 47 return simple_read_from_buffer(ubuf, count, ppos, ent->mem, ent->size); 48 } 49 50 static const struct file_operations memtrace_fops = { 51 .llseek = default_llseek, 52 .read = memtrace_read, 53 .open = simple_open, 54 }; 55 56 static int check_memblock_online(struct memory_block *mem, void *arg) 57 { 58 if (mem->state != MEM_ONLINE) 59 return -1; 60 61 return 0; 62 } 63 64 static int change_memblock_state(struct memory_block *mem, void *arg) 65 { 66 unsigned long state = (unsigned long)arg; 67 68 mem->state = state; 69 70 return 0; 71 } 72 73 static bool memtrace_offline_pages(u32 nid, u64 start_pfn, u64 nr_pages) 74 { 75 u64 end_pfn = start_pfn + nr_pages - 1; 76 77 if (walk_memory_range(start_pfn, end_pfn, NULL, 78 check_memblock_online)) 79 return false; 80 81 walk_memory_range(start_pfn, end_pfn, (void *)MEM_GOING_OFFLINE, 82 change_memblock_state); 83 84 if (offline_pages(start_pfn, nr_pages)) { 85 walk_memory_range(start_pfn, end_pfn, (void *)MEM_ONLINE, 86 change_memblock_state); 87 return false; 88 } 89 90 walk_memory_range(start_pfn, end_pfn, (void *)MEM_OFFLINE, 91 change_memblock_state); 92 93 lock_device_hotplug(); 94 remove_memory(nid, start_pfn << PAGE_SHIFT, nr_pages << PAGE_SHIFT); 95 unlock_device_hotplug(); 96 97 return true; 98 } 99 100 static u64 memtrace_alloc_node(u32 nid, u64 size) 101 { 102 u64 start_pfn, end_pfn, nr_pages; 103 u64 base_pfn; 104 105 if (!node_spanned_pages(nid)) 106 return 0; 107 108 start_pfn = node_start_pfn(nid); 109 end_pfn = node_end_pfn(nid); 110 nr_pages = size >> PAGE_SHIFT; 111 112 /* Trace memory needs to be aligned to the size */ 113 end_pfn = round_down(end_pfn - nr_pages, nr_pages); 114 115 for (base_pfn = end_pfn; base_pfn > start_pfn; base_pfn -= nr_pages) { 116 if (memtrace_offline_pages(nid, base_pfn, nr_pages) == true) 117 return base_pfn << PAGE_SHIFT; 118 } 119 120 return 0; 121 } 122 123 static int memtrace_init_regions_runtime(u64 size) 124 { 125 u32 nid; 126 u64 m; 127 128 memtrace_array = kcalloc(num_online_nodes(), 129 sizeof(struct memtrace_entry), GFP_KERNEL); 130 if (!memtrace_array) { 131 pr_err("Failed to allocate memtrace_array\n"); 132 return -EINVAL; 133 } 134 135 for_each_online_node(nid) { 136 m = memtrace_alloc_node(nid, size); 137 138 /* 139 * A node might not have any local memory, so warn but 140 * continue on. 141 */ 142 if (!m) { 143 pr_err("Failed to allocate trace memory on node %d\n", nid); 144 continue; 145 } 146 147 pr_info("Allocated trace memory on node %d at 0x%016llx\n", nid, m); 148 149 memtrace_array[memtrace_array_nr].start = m; 150 memtrace_array[memtrace_array_nr].size = size; 151 memtrace_array[memtrace_array_nr].nid = nid; 152 memtrace_array_nr++; 153 } 154 155 return 0; 156 } 157 158 static struct dentry *memtrace_debugfs_dir; 159 160 static int memtrace_init_debugfs(void) 161 { 162 int ret = 0; 163 int i; 164 165 for (i = 0; i < memtrace_array_nr; i++) { 166 struct dentry *dir; 167 struct memtrace_entry *ent = &memtrace_array[i]; 168 169 ent->mem = ioremap(ent->start, ent->size); 170 /* Warn but continue on */ 171 if (!ent->mem) { 172 pr_err("Failed to map trace memory at 0x%llx\n", 173 ent->start); 174 ret = -1; 175 continue; 176 } 177 178 snprintf(ent->name, 16, "%08x", ent->nid); 179 dir = debugfs_create_dir(ent->name, memtrace_debugfs_dir); 180 if (!dir) 181 return -1; 182 183 ent->dir = dir; 184 debugfs_create_file("trace", 0400, dir, ent, &memtrace_fops); 185 debugfs_create_x64("start", 0400, dir, &ent->start); 186 debugfs_create_x64("size", 0400, dir, &ent->size); 187 } 188 189 return ret; 190 } 191 192 static int memtrace_enable_set(void *data, u64 val) 193 { 194 if (memtrace_size) 195 return -EINVAL; 196 197 if (!val) 198 return -EINVAL; 199 200 /* Make sure size is aligned to a memory block */ 201 if (val & (memory_block_size_bytes() - 1)) 202 return -EINVAL; 203 204 if (memtrace_init_regions_runtime(val)) 205 return -EINVAL; 206 207 if (memtrace_init_debugfs()) 208 return -EINVAL; 209 210 memtrace_size = val; 211 212 return 0; 213 } 214 215 static int memtrace_enable_get(void *data, u64 *val) 216 { 217 *val = memtrace_size; 218 return 0; 219 } 220 221 DEFINE_SIMPLE_ATTRIBUTE(memtrace_init_fops, memtrace_enable_get, 222 memtrace_enable_set, "0x%016llx\n"); 223 224 static int memtrace_init(void) 225 { 226 memtrace_debugfs_dir = debugfs_create_dir("memtrace", 227 powerpc_debugfs_root); 228 if (!memtrace_debugfs_dir) 229 return -1; 230 231 debugfs_create_file("enable", 0600, memtrace_debugfs_dir, 232 NULL, &memtrace_init_fops); 233 234 return 0; 235 } 236 machine_device_initcall(powernv, memtrace_init); 237