M7350/qcom-opensource/kernel/kernel-tests/page-nom/page-nom.c

546 lines
14 KiB
C
Raw Normal View History

2024-09-09 08:57:42 +00:00
/* Copyright (c) 2012-2014, Linux Foundation. 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 and
* only 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.
*/
/*
* page-nom is a memory eater. It can be used to finely allocate large amounts
* of system or vmalloc memory in order to better test applications in low
* memory situations.
*
* Memory is allocated in individual blocks of a given size which can be tuned.
* By allocating in chunks it makes it more likely that we can allocate as close
* to the full request as possible and also makes it easy to dynamically add and
* remove blocks without having to free and re-initalize (with all the panic
* that might entail on a system wide basis). New memory is allocated by
* specifying how many chunks one wants allocated. This all happens via
* debugfs:
*
* /sys/kernel/debug/page-nom/vmalloc_block_size - size in bytes of the
* individual blocks of memory allocated from vmalloc. The input size is
* page aligned. The block size can only be changed when page-nom hasn't eaten
* any vmalloc pages yet.
*
* /sys/kernel/debug/page-nom/vmalloc_blocks - number of 'vmalloc_block_size'
* blocks to eat from vmalloc. Inputing a number higher than the current value
* will allocate new memory and inputing a number lower than the current value
* will free memory. 0 will free all eaten vmalloc memory. The returned value
* will be the number of allocated blocks regardless if this method or the
* following method was used to allocate the blocks.
*
* /sys/kernel/ debug/page-nom/vmalloc_percent - percentage of the vmalloc
* memory to eat. This is an alternate method to 'vmalloc_blocks'. Where
* vmalloc_blocks is more precise, this is easier for automation. Just echo a
* number between 0 and 100 for page-nom to try to consume that percentage of
* total vmalloc memory. The returned value from this file is the current
* percentage of allocated vmalloc memory from either method. This number will
* be appropriately massaged so it ends up being a multiple of
* vmalloc_block_size, so the number written won't always be the precise
* percentage eaten.
*
* /sys/kernel/debug/page-nom/mem_block_size - size in bytes of the
* individual blocks of memory allocated from memory. The input size is
* order aligned. The block size can only be changed when page-nom hasn't eaten
* any pages yet.
*
* /sys/kernel/debug/page-nom/mem_blocks - number of 'mem_block_size'
* blocks to eat from memory. Inputing a number higher than the current value
* will allocate new memory and inputing a number lower than the current value
* will free memory. 0 will free all eaten vmalloc memory. The returned value
* will be the number of allocated blocks regardless if this method or the
* following method was used to allocate the blocks.
*
* /sys/kernel/ debug/page-nom/mem_percent - percentage of the vmalloc
* memory to eat. This is an alternate method to 'mem_blocks'. Where
* mem_blocks is more precise, this is easier for automation. Just echo a
* number between 0 and 100 for page-nom to try to consume that percentage of
* system memory. The returned value from this file is the current
* percentage of allocated system memory from either method. This number will
* be appropriately massaged so it ends up being a multiple of
* mem_block_size, so the number written won't always be the precise
* percentage eaten.
*(
*/
#define pr_fmt(fmt) "page-nom: " fmt
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/vmalloc.h>
#include <linux/list.h>
#include <linux/debugfs.h>
#include <linux/slab.h>
#include <linux/sysinfo.h>
#include <linux/mm.h>
#include <asm/pgtable.h>
static struct dentry *page_nom_dir;
/*
* Default to 256K sized blocks from vmalloc. This should be good enough to
* allocate enough memory fast enough and not hit bad fragmenting issues
*/
static int vmalloc_block_size = (256 * 1024);
/* Default to page sized blocks from system memory */
static int mem_block_size = PAGE_SIZE;
/* Number of blocks allocated to each entity */
static int vmalloc_block_count;
static int mem_block_count;
/* Percentage of vmalloc or system memory eaten */
static int vmalloc_percent;
static int mem_percent;
static LIST_HEAD(vmalloc_blocks);
static LIST_HEAD(mem_blocks);
/* Sysinfo gathered at load time */
static struct sysinfo sysinfo;
#ifdef VMALLOC_TOTAL
#undef VMALLOC_TOTAL
#define VMALLOC_TOTAL (VMALLOC_END - VMALLOC_START)
#endif
struct mem_block {
struct page *page;
int size;
struct list_head node;
};
struct vmalloc_block {
void *ptr;
int size;
struct list_head node;
};
#define TOKB(val) ((val) >> 10)
#define UNEATEN_MSG(_type, _blocks, _kb) \
pr_err("I just gave back (%d/%d Kb) of " _type "\n", \
(_blocks), (_kb))
#define REMAINING_MSG(_type, _blocks, _kb, _percent) \
pr_err("I still have (%d blocks/%d Kb/%d%%) of " _type "\n", \
(_blocks), (_kb), (_percent))
#define EATEN_MSG(_type, _blocks, _kb) \
pr_err("I just ate (%d blocks/%d Kb) of " _type ". Nom!\n", \
(_blocks), (_kb))
#define SOFAR_MSG(_type, _blocks, _kb, _percent) \
pr_err("So far I have eaten (%d blocks/%d Kb/%d%%) of " _type "\n", \
(_blocks), (_kb), (_percent))
/* Calculate the percentage in pages to avoid messy overflow workarounds */
static void update_mem_percent(void)
{
unsigned int val;
val = 100 * mem_block_count * (mem_block_size >> PAGE_SHIFT);
val /= sysinfo.totalram;
mem_percent = val;
}
static int free_mem_blocks(int count)
{
struct mem_block *block, *tmp;
int i = 0;
list_for_each_entry_safe(block, tmp, &mem_blocks, node) {
if (i == count || mem_block_count == 0)
break;
__free_page(block->page);
list_del(&block->node);
kfree(block);
mem_block_count--;
i++;
}
return i;
}
static void del_mem_blocks(int count)
{
int ret;
ret = free_mem_blocks(count);
update_mem_percent();
UNEATEN_MSG("sysmem", ret, TOKB(ret * mem_block_size));
if (mem_block_count)
REMAINING_MSG("sysmem", mem_block_count,
TOKB(mem_block_count * mem_block_size),
mem_percent);
else
pr_err("I no longer have any eaten memory. Feed me!\n");
}
static void add_mem_blocks(int count)
{
struct mem_block *block;
int i;
for (i = 0; i < count; i++) {
unsigned int gfp = GFP_KERNEL | __GFP_HIGHMEM | __GFP_NOWARN;
/*
* Don't let the allocator loop around and cause death
* TODO: Add an option to let the use shoot themselves in the
* foot
*/
gfp |= __GFP_NORETRY;
block = kzalloc(sizeof(*block), GFP_KERNEL);
if (block == NULL) {
pr_err("Unable to kmalloc a mem block\n");
break;
}
if (mem_block_size != PAGE_SIZE)
gfp |= __GFP_COMP;
block->size = mem_block_size;
block->page = alloc_pages(gfp, get_order(mem_block_size));
if (block->page == NULL) {
pr_err("Unable to get a %d Kb block of pages\n",
TOKB(mem_block_size));
break;
}
list_add(&block->node, &mem_blocks);
mem_block_count++;
}
update_mem_percent();
if (i != count)
pr_err("I could only eat %d of %d blocks requested\n",
i, count);
EATEN_MSG("sysmem", i, TOKB(i * mem_block_size));
SOFAR_MSG("sysmem", mem_block_count,
TOKB(mem_block_count * mem_block_size), mem_percent);
}
static int mem_blocks_set(void *data, u64 val)
{
unsigned int count = (unsigned int) val;
if (count > mem_block_count)
add_mem_blocks(count - mem_block_count);
else if (count < mem_block_count)
del_mem_blocks(mem_block_count - count);
return 0;
}
static int mem_blocks_get(void *data, u64 *val)
{
*val = mem_block_count;
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(mem_blocks_fops,
mem_blocks_get,
mem_blocks_set, "%lld\n");
static int mem_percent_set(void *data, u64 val)
{
unsigned int percent = (unsigned int) val;
unsigned int blocks = mem_block_count;
if (percent > 100)
return 0;
if (percent > 0) {
blocks = abs(percent - mem_percent);
/* Figure out how many pages of +/- memory we want */
blocks = (blocks * sysinfo.totalram) / 100;
/* Turn that into mem_block_size(d) blocks */
blocks = blocks / (mem_block_size >> PAGE_SHIFT);
}
if (percent > mem_percent)
add_mem_blocks(blocks);
else
del_mem_blocks(blocks);
return 0;
}
static int mem_percent_get(void *data, u64 *val)
{
*val = mem_percent;
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(mem_percent_fops,
mem_percent_get,
mem_percent_set, "%lld\n");
/* Calculate the percentage in pages to avoid messy overflow workarounds */
static void update_vmalloc_percent(void)
{
unsigned int val;
val = 100 * vmalloc_block_count * (vmalloc_block_size >> PAGE_SHIFT);
val /= VMALLOC_TOTAL >> PAGE_SHIFT;
pr_err("%d = %ld / (%d * %d)\n", val,
VMALLOC_TOTAL >> PAGE_SHIFT,
vmalloc_block_count,
vmalloc_block_size >> PAGE_SHIFT);
vmalloc_percent = val;
}
static int free_vmalloc_blocks(int count)
{
struct vmalloc_block *block, *tmp;
int i = 0;
list_for_each_entry_safe(block, tmp, &vmalloc_blocks, node) {
if (i == count || vmalloc_block_count == 0)
break;
vfree(block->ptr);
list_del(&block->node);
kfree(block);
vmalloc_block_count--;
i++;
}
return i;
}
static void del_vmalloc_blocks(int count)
{
int ret;
ret = free_vmalloc_blocks(count);
update_vmalloc_percent();
UNEATEN_MSG("vmalloc", ret, TOKB(ret * vmalloc_block_size));
if (vmalloc_block_count)
REMAINING_MSG("vmalloc", vmalloc_block_count,
TOKB(vmalloc_block_count * vmalloc_block_size),
vmalloc_percent);
else
pr_err("I no longer have any eaten memory. Feed me!\n");
}
static void add_vmalloc_blocks(int count)
{
struct vmalloc_block *block;
int i;
for (i = 0; i < count; i++) {
block = kmalloc(sizeof(*block), GFP_KERNEL);
if (block == NULL) {
pr_err("Unable to kmalloc a vmalloc block\n");
break;
}
block->size = vmalloc_block_size;
block->ptr = vmalloc(vmalloc_block_size);
if (block->ptr == NULL) {
pr_err("Unable to get a %d Kb vmalloc block\n",
TOKB(vmalloc_block_size));
break;
}
list_add(&block->node, &vmalloc_blocks);
vmalloc_block_count++;
}
update_vmalloc_percent();
if (i != count)
pr_err("I could only eat %d of %d blocks requested\n",
i, count);
EATEN_MSG("vmalloc", i, TOKB(i * vmalloc_block_size));
SOFAR_MSG("vmalloc",
vmalloc_block_count,
TOKB(vmalloc_block_count * vmalloc_block_size),
vmalloc_percent);
}
static int vmalloc_blocks_set(void *data, u64 val)
{
unsigned int count = (unsigned int) val;
if (count > vmalloc_block_count)
add_vmalloc_blocks(count - vmalloc_block_count);
else if (count < vmalloc_block_count)
del_vmalloc_blocks(vmalloc_block_count - count);
return 0;
}
static int vmalloc_blocks_get(void *data, u64 *val)
{
*val = vmalloc_block_count;
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(vmalloc_blocks_fops,
vmalloc_blocks_get,
vmalloc_blocks_set, "%lld\n");
static int vmalloc_percent_set(void *data, u64 val)
{
unsigned int percent = (unsigned int) val;
unsigned int blocks = vmalloc_block_count;
if (percent > 100)
return 0;
if (percent != 0) {
blocks = abs(percent - vmalloc_percent);
/* Figure out how many pages of +/- memory we want */
blocks = (blocks * (VMALLOC_TOTAL >> PAGE_SHIFT)) / 100;
/* Turn that into vmalloc_block_size(d) blocks */
blocks = blocks / (vmalloc_block_size >> PAGE_SHIFT);
}
if (percent > vmalloc_percent)
add_vmalloc_blocks(blocks);
else
del_vmalloc_blocks(blocks);
return 0;
}
static int vmalloc_percent_get(void *data, u64 *val)
{
*val = vmalloc_percent;
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(vmalloc_percent_fops,
vmalloc_percent_get,
vmalloc_percent_set, "%lld\n");
static int mem_block_size_set(void *data, u64 val)
{
int order;
if (vmalloc_block_count) {
pr_err("Cannot change the block size if any memory is eaten\n");
return 0;
}
/* Align the request to the next nearest order to make things easier */
order = get_order((int) val);
mem_block_size = 1 << (order + PAGE_SHIFT);
return 0;
}
static int mem_block_size_get(void *data, u64 *val)
{
*val = mem_block_size;
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(mem_block_size_fops,
mem_block_size_get,
mem_block_size_set, "%lld\n");
static int vmalloc_block_size_set(void *data, u64 val)
{
if (vmalloc_block_count) {
pr_err("Cannot change the block size if any memory is eaten\n");
return 0;
}
/* Align the requested size to a page */
vmalloc_block_size = PAGE_ALIGN((int) val);
return 0;
}
static int vmalloc_block_size_get(void *data, u64 *val)
{
*val = vmalloc_block_size;
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(vmalloc_block_size_fops,
vmalloc_block_size_get,
vmalloc_block_size_set, "%lld\n");
static int __init page_nom_init(void)
{
si_meminfo(&sysinfo);
page_nom_dir = debugfs_create_dir("page-nom", 0);
if (IS_ERR(page_nom_dir))
return PTR_ERR(page_nom_dir);
debugfs_create_file("vmalloc_block_size", 0644, page_nom_dir, NULL,
&vmalloc_block_size_fops);
debugfs_create_file("vmalloc_percent", 0644, page_nom_dir, NULL,
&vmalloc_percent_fops);
debugfs_create_file("vmalloc_blocks", 0644, page_nom_dir, NULL,
&vmalloc_blocks_fops);
debugfs_create_file("mem_block_size", 0644, page_nom_dir, NULL,
&mem_block_size_fops);
debugfs_create_file("mem_percent", 0644, page_nom_dir, NULL,
&mem_percent_fops);
debugfs_create_file("mem_blocks", 0644, page_nom_dir, NULL,
&mem_blocks_fops);
pr_err("I am hungry! Give me pages\n");
return 0;
}
static void __exit page_nom_exit(void)
{
if (!IS_ERR(page_nom_dir))
debugfs_remove_recursive(page_nom_dir);
free_mem_blocks(mem_block_count);
free_vmalloc_blocks(vmalloc_block_count);
pr_err("Thanks for the noms\n");
}
module_init(page_nom_init);
module_exit(page_nom_exit);
MODULE_DESCRIPTION("page-nom");
MODULE_LICENSE("GPL v2");