blob: 810849aa8e4f0d4aeafec54bf5a9e9d0da4055b9 [file] [log] [blame]
/*
* Copyright (C) 2013~2014 Marvell International Ltd.
* Jialing Fu <jlfu@marvell.com>
*
* Try to create a set of APIs to let some specail module/driver can
* alloc/free objects seperatly
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>
#include <linux/mm.h>
#include <linux/kexec.h>
#include <linux/reboot.h>
/*
* Background:
* As you know, kmalloc is called directlly and oftenly in the code of drivers
* and modules.
* In most case it works well, as it is a standard/recommend API provided
* by linux kernel. Also this API is efficient and benifit to save memory.
*
* Using the API, all driver and module would share same kmalloc-xxx for same
* buffer size range. like kmalloc-64 is shared, when size is from 1 to 64.
*
* As a result, it is hard to debug when kmem issue happens if kmalloc and
* kfree is misused. As it has limitation:
* 1) By default CONFIG_SLUB_DEBUB_ON is not set in kernel config, so that
* there is not Trace/Debug info for us to check, if somebody misuse it.
* 2) But if we enable CONFIG_SLUB_DEBUB_ON, all kmem_cache will enable the
* DEBUG feature, system performance would downgrade and memory space would be
* wasted too much.
*
*
* So we may want to use kmalloc more advanced and flexible. Like
* 1) The driver/module can still call kmalloc, but the buffer can be allocated
* from other separate kmem_cache which different as kmalloc-xxx.
* 2) The new kmem_cache can have DEBUG feature when need.
*
*
* So this file creates a list of kmem_cache to support the idea, named as
* "supper kmalloc"
*
*
* User Guide:
* 1) If "SUPPER_KMALLOC" is defined in driver/module, when kmalloc is called,
* the compile tool would help to use skmalloc-xxx instead.
*
* 2) For each kernel cycle, Only one Supper Kmalloc can be created.
* And you can change its behavior by changing skm_dbg_level
* #0: disable supper kmalloc, and use kmalloc-xxx
* #1: Create skmalloc-xxx with kernel's default debug flags
* #2: Create skmalloc-xxx, force to enable all debug flags
*
* TODO: Finish below TODO if need
*/
#define SKM_DBG_LEVEL0 0
#define SKM_DBG_LEVEL1 1
#define SKM_DBG_LEVEL2 2
#define SKM_DBG_LEVEL_MAX SKM_DBG_LEVEL2
#define SKM_DBG_LEVEL0_FLAGS 0
#define SKM_DBG_LEVEL1_FLAGS (SLAB_UNMERGE | SLAB_DEBUG_OBJECTS)
#define SKM_DBG_LEVEL2_FLAGS (SLAB_DEBUG_FREE | SLAB_RED_ZONE | \
SLAB_POISON | SLAB_STORE_USER)
static inline int check_skm_dbg_level(int dbg_level)
{
return (dbg_level >= 0 && dbg_level <= SKM_DBG_LEVEL_MAX);
}
/*
* As "skm_dbg_level" is very important to Supper Kmalloc's behavior
* There are 3 ways to set "skm_dbg_level" as below
* Note: For way 1,2,3, the biggest Number has highest priority
* Example #2 can overwrite #1's setting......
*/
/* #1 Default setting by Hard Code */
static int skm_dbg_level = SKM_DBG_LEVEL1;
/* #2 kernel cmd line, like "bootrags = skm_dbg_level=1" */
static int __init skm_dbg_level_setup(char *str)
{
int n;
if (!get_option(&str, &n))
return 0;
if (check_skm_dbg_level(n))
skm_dbg_level = n;
else
pr_info("cmd line invalid skm_dgb_level:%d\n", n);
return 1;
}
__setup("skm_dbg_level=", skm_dbg_level_setup);
/* #3 reboot cmd line, like "reboot skm_dbg_level=2" */
#ifdef CONFIG_SKMALLOC_REBOOT_NOTIFY
#define SKM_RSV_MAGIC1 0x2D4D4B53
#define SKM_RSV_MAGIC2 0x55464C4A
struct skm_rsvbuf {
unsigned int magic1;
int dbg_level;
unsigned int magic2;
int rsv[5];
};
void *get_skm_rsvbuf(void)
{
if (emmd_page)
return (void *)(emmd_page->p_rsvbuf);
else
return NULL;
}
static int save_skm_dbg_level(unsigned int value)
{
struct skm_rsvbuf *p_rsvbuf;
p_rsvbuf = get_skm_rsvbuf();
if (!p_rsvbuf)
return -1;
memset(p_rsvbuf, 0, sizeof(struct skm_rsvbuf));
if (check_skm_dbg_level(value)) {
p_rsvbuf->magic1 = SKM_RSV_MAGIC1;
p_rsvbuf->magic2 = SKM_RSV_MAGIC2;
p_rsvbuf->dbg_level = value;
return 0;
} else
pr_info("reboot invalid skm_dgb_level:%d\n", value);
return -1;
}
static int restore_skm_dbg_level(void)
{
struct skm_rsvbuf *p_rsvbuf;
p_rsvbuf = get_skm_rsvbuf();
if (unlikely(!p_rsvbuf))
return -1;
if ((p_rsvbuf->magic1 == SKM_RSV_MAGIC1) &&
(p_rsvbuf->magic2 == SKM_RSV_MAGIC2)) {
/* make sure it is reserved for SKM indeed */
if (check_skm_dbg_level(p_rsvbuf->dbg_level)) {
skm_dbg_level = p_rsvbuf->dbg_level;
return 0;
}
}
return -1;
}
static int skm_notifier_bootup_handle(void)
{
/*
* check whether skm_dbg_level was saved in last reboot
* If yes, restore skm_dbg_level and clear the saved information
*/
if (0 == restore_skm_dbg_level())
save_skm_dbg_level(-1);
return 0;
}
static int skm_notifier_shutdown_handle(struct notifier_block *this,
unsigned long code, void *cmd)
{
unsigned int tmp;
if (cmd && sscanf(cmd, "skm_dbg_level=%d", &tmp) == 1)
save_skm_dbg_level(tmp);
return 0;
}
static struct notifier_block skm_reboot_notifier = {
.notifier_call = skm_notifier_shutdown_handle,
};
#endif
/* End to init skm_dbg_level */
#define SKMALLOC_MAX_SIZE (8192)
static const size_t skmalloc_size[] = {
64, /* Alloc from 0~64 */
128, /* Alloc from 65~128 */
192,
256,
384,
512,
1024,
2048,
4096,
SKMALLOC_MAX_SIZE, /* Alloc from 4097~8192 */
};
static const int dbg_level[] = {
SKM_DBG_LEVEL1,
SKM_DBG_LEVEL2,
};
static const int dbg_flags[] = {
SKM_DBG_LEVEL1_FLAGS,
SKM_DBG_LEVEL2_FLAGS,
};
#define MALLOC_CACHE_NUMS (sizeof(skmalloc_size)/sizeof(size_t))
struct supper_kmalloc {
int dbg_level;
int dbg_flags;
char *name;
struct kmem_cache *k_c[MALLOC_CACHE_NUMS];
};
struct supper_kmalloc s_kmalloc;
static inline void *skm_get_supper_kmalloc(void)
{
return &s_kmalloc;
}
static int skm_size_to_index(size_t size)
{
int index = 0;
/* TODO: Use more efficiently way if need */
if (unlikely((size > SKMALLOC_MAX_SIZE) || (size <= 0)))
return -1;
while (1) {
if (size <= skmalloc_size[index])
return index;
else
index++;
}
}
static size_t skm_index_to_size(int index)
{
if (unlikely((index >= MALLOC_CACHE_NUMS) || (index < 0)))
return -1;
return skmalloc_size[index];
}
static struct kmem_cache *skm_get_kmem_cache(size_t size, gfp_t flags)
{
int index;
struct supper_kmalloc *active_sk;
active_sk = skm_get_supper_kmalloc();
index = skm_size_to_index(size);
if (likely(index >= 0))
return active_sk->k_c[index];
else
return NULL;
}
void *skmalloc(size_t size, gfp_t flags)
{
struct kmem_cache *p_kc;
if (skm_dbg_level) {
if (unlikely(size > SKMALLOC_MAX_SIZE)) {
pr_warn("alloc 0x%zx bytes by kmalloc\n", size);
return __kmalloc(size, flags);
}
p_kc = skm_get_kmem_cache(size, flags);
if (likely(p_kc))
return kmem_cache_alloc(p_kc, flags);
else {
/* supper_kmalloc_create failed to create kmem_cache */
return __kmalloc(size, flags);
}
} else
return __kmalloc(size, flags);
}
EXPORT_SYMBOL(skmalloc);
void skfree(void *x)
{
kfree(x);
}
EXPORT_SYMBOL(skfree);
static int supper_kmalloc_create(struct supper_kmalloc *p_sk)
{
int i;
size_t size;
char *s;
for (i = 0; i < MALLOC_CACHE_NUMS; i++) {
size = skm_index_to_size(i);
/* TODO: scnprintf not to use kmalloc as kasprintf */
s = kasprintf(GFP_KERNEL, "%s-%zd", p_sk->name, size);
if (likely(s)) {
p_sk->k_c[i] = kmem_cache_create(s, size, 0,
p_sk->dbg_flags, NULL);
kfree(s);
} else {
/* it is very unlikely */
p_sk->k_c[i] = NULL;
}
}
return 0;
}
static int supper_kmalloc_destory(struct supper_kmalloc *p_sk)
{
int i;
for (i = 0; i < MALLOC_CACHE_NUMS; i++) {
if (p_sk->k_c[i]) {
kmem_cache_destroy(p_sk->k_c[i]);
p_sk->k_c[i] = NULL;
}
}
return 0;
}
static int __init skmalloc_init(void)
{
struct supper_kmalloc *p_sk;
pr_info(">>>Create Supper Kmalloc start\n");
#ifdef CONFIG_SKMALLOC_REBOOT_NOTIFY
skm_notifier_bootup_handle();
register_reboot_notifier(&skm_reboot_notifier);
#endif
pr_info("Supper Kmalloc skm_dbg_level=%d\n", skm_dbg_level);
if (skm_dbg_level > 0 && skm_dbg_level <= SKM_DBG_LEVEL_MAX) {
p_sk = skm_get_supper_kmalloc();
p_sk->name = "skmalloc";
p_sk->dbg_level = dbg_level[skm_dbg_level-1];
p_sk->dbg_flags = dbg_flags[skm_dbg_level-1];
supper_kmalloc_create(p_sk);
pr_info("skm_dbg_flags=0x%x\n", p_sk->dbg_flags);
} else {
/* In fact, skm_dbg_level is 0 here */
pr_info("use normal kmalloc instead\n");
}
pr_info("<<<Create Supper Kmalloc end\n");
return 0;
}
/*
* Make sure skmalloc_init is called at:
* after SLUB system has been finished && before devices code is ready to run
*/
rootfs_initcall(skmalloc_init);
static void __exit skmalloc_exit(void)
{
struct supper_kmalloc *p_sk;
pr_info(">>>Destory Supper Kmalloc Start\n");
if (skm_dbg_level > 0 && skm_dbg_level <= SKM_DBG_LEVEL_MAX) {
p_sk = skm_get_supper_kmalloc();
supper_kmalloc_destory(p_sk);
}
#ifdef CONFIG_SKMALLOC_REBOOT_NOTIFY
unregister_reboot_notifier(&skm_reboot_notifier);
#endif
pr_info("<<<Destory Supper kmalloc end\n");
}
module_exit(skmalloc_exit);
MODULE_AUTHOR("Jialing Fu");
MODULE_DESCRIPTION("Supper Kmalloc APIs");
MODULE_LICENSE("GPL");
module_param(skm_dbg_level, int, 0);
MODULE_PARM_DESC(skm_dbg_level,
"0: Use kmalloc 1: Use skmalloc; 2: Use skmalloc with highest dbg level");