blob: 478d1106defb0ed283686bf4de6d6cace21f5d38 [file] [log] [blame]
/*******************************************************************************
* Copyright (C) Marvell International Ltd. and its affiliates
*
* Marvell GPL License Option
*
* If you received this File from Marvell, you may opt to use, redistribute and/or
* modify this File in accordance with the terms and conditions of the General
* Public License Version 2, June 1991 (the "GPL License"), a copy of which is
* available along with the File in the license.txt file or by writing to the Free
* Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 or
* on the worldwide web at http://www.gnu.org/licenses/gpl.txt.
*
* THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE IMPLIED
* WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE ARE EXPRESSLY
* DISCLAIMED. The GPL License provides additional details about this warranty
* disclaimer.
********************************************************************************/
/*******************************************************************************
System head files
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h> /* for seq_file */
#include <linux/sched.h> /* for tasklist_lock */
#include <linux/cdev.h>
#include <linux/dma-mapping.h>
#include <asm/cacheflush.h>
#include <asm/uaccess.h>
#include <linux/of.h>
#include <linux/of_address.h>
/*******************************************************************************
Local head files
*/
#include <mach/galois_generic.h>
#include "shm_type.h"
#include "shm_device.h"
#include "shm_api_kernel.h"
/*******************************************************************************
Module Descrption
*/
MODULE_AUTHOR("Fang Bao");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Shared Memory Driver");
/*******************************************************************************
Module API defined
*/
/*******************************************************************************
Separate cache and non-cache driver interface
*/
static int shm_driver_open(struct inode *inode, struct file *filp);
static int shm_driver_open_noncache(struct inode *inode, struct file *filp);
static int shm_driver_release(struct inode *inode, struct file *filp);
static int shm_driver_mmap_noncache(struct file *filp,
struct vm_area_struct *vma);
static int shm_driver_mmap_cache(struct file *filp, struct vm_area_struct *vma);
static long shm_driver_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg);
/*******************************************************************************
Module Parameter
*/
uint shm_base_cache ;//= 0x12000000;//CONFIG_MV88DE3100_SHMMEM_START;
static uint shm_size_cache ;//= 0x0C000000; //CONFIG_MV88DE3100_SHMMEM_CACHE_SIZE;
static uint shm_size_noncache; //= 0x2000000;
static uint shm_base_noncache;//;= 0x1A000000;
uint shm_lowmem_debug_level = 2;
module_param_named(debug_level, shm_lowmem_debug_level, uint, S_IRUGO | S_IWUSR);
static uint shm_lowmem_shrink_threshold = 90;
module_param_named(shrink_threshold, shm_lowmem_shrink_threshold, uint, S_IRUGO | S_IWUSR);
static int shm_lowmem_start_oom_adj = 1;
module_param_named(start_oom_adj, shm_lowmem_start_oom_adj, int, S_IRUGO | S_IWUSR);
static int shm_lowmem_min_adj = 1;
module_param_named(min_adj, shm_lowmem_min_adj, int, S_IRUGO | S_IWUSR);
static int shm_lowmem_min_release_kbytes = 4096;
module_param_named(min_release_kbytes, shm_lowmem_min_release_kbytes, int, S_IRUGO | S_IWUSR);
/*******************************************************************************
Module Variable
*/
static shm_device_t *shm_device = NULL;
static shm_device_t *shm_device_noncache = NULL;
static struct cdev shm_dev_noncache;
static struct cdev shm_dev_cache;
static struct class *shm_dev_class;
static struct file_operations shm_ops_noncache = {
.open = shm_driver_open_noncache,
.release = shm_driver_release,
.mmap = shm_driver_mmap_noncache,
.unlocked_ioctl = shm_driver_ioctl,
.owner = THIS_MODULE,
};
static struct file_operations shm_ops_cache = {
.open = shm_driver_open,
.release = shm_driver_release,
.mmap = shm_driver_mmap_cache,
.unlocked_ioctl = shm_driver_ioctl,
.owner = THIS_MODULE,
};
static shm_driver_cdev_t shm_driver_dev_list[] = {
{GALOIS_SHM0_MINOR, SHM_DEVICE_NAME_NONCACHE, &shm_dev_noncache,
&shm_ops_noncache},
{GALOIS_SHM1_MINOR, SHM_DEVICE_NAME_CACHE, &shm_dev_cache,
&shm_ops_cache},
};
static struct proc_dir_entry *shm_driver_procdir;
static struct task_struct* shm_lowmem_deathpending;
static unsigned long shm_lowmem_deathpending_timeout;
static int shm_lowmem_going;
static struct shm_stat_info shm_stat;
static DEFINE_SEMAPHORE(shm_lowmem_mutex);
/*********************** shm lowmem killer *****************************/
#define shm_lowmem_print(level, x...) \
do { \
if (shm_lowmem_debug_level >= (level)) \
printk(x); \
}while(0)
static int shm_task_notify_func(struct notifier_block* self, unsigned long val, void *data)
{
struct task_struct *task = data;
if (task == shm_lowmem_deathpending)
shm_lowmem_deathpending = NULL;
return NOTIFY_OK;
}
static void shm_shrink_dump_usage(struct shm_stat_info *stat_info,int print_level)
{
int i = 0 ;
if (NULL == stat_info)
return;
shm_lowmem_print(print_level,"total size : %d \nused : %d \ntask count : %d\n",
stat_info->m_size,stat_info->m_used,stat_info->m_count);
shm_lowmem_print(print_level," No | task id( name ) | oom_adj | alloc | use |\n");
shm_lowmem_print(print_level,"-----------------------------------------------------------------------\n");
for (i = 0 ; i < stat_info->m_count ; i++)
shm_lowmem_print(print_level," %3d | %8d(%16s) | %3d | %10d | %10d |\n",
i, stat_info->m_nodes[i].m_taskid,
stat_info->m_nodes[i].m_taskname, stat_info->m_nodes[i].m_oom_adj,
stat_info->m_nodes[i].m_size_alloc, stat_info->m_nodes[i].m_size_use);
}
static size_t shm_shrink_find_task_usage(struct shm_stat_info *stat_info, pid_t taskid)
{
int i = 0 ;
if (NULL == stat_info)
return 0;
for (i = 0 ; i < stat_info->m_count ; i++){
struct shm_usr_node *usr_node = &(stat_info->m_nodes[i]);
if (taskid == usr_node->m_taskid)
return usr_node->m_size_use;
}
return 0;
}
/**
* find the candidate task and release it
* a)the oom-adj of task is more than shm_lowmem_start_oom_adj
* b)the shm used size is more than shm_lowmem_min_release_kbyte * 1024
* c)when meet the above two cond, then select the largest oom-adj task
*/
static int shm_lowmem_shrink_killer(int allocsize)
{
int res = 0;
struct task_struct *p;
struct task_struct *selected = NULL;
int selected_shmsize = 0;
int selected_oom_adj = 0;
/* check whether in shrinking or exits pending task */
down(&shm_lowmem_mutex);
if (shm_lowmem_going) {
/* another shrinking is executing */
up(&shm_lowmem_mutex);
shm_lowmem_print(2,"shm_lowmem shrink is under going\n");
return 0;
} else {
if (shm_lowmem_deathpending &&
time_before_eq(jiffies,shm_lowmem_deathpending_timeout)) {
/* no shrinking is executing, but exists pending task*/
up(&shm_lowmem_mutex);
shm_lowmem_print(2,"a pending task exists pid(%d)\n",shm_lowmem_deathpending->pid);
return 0;
}
shm_lowmem_going = 1;
up(&shm_lowmem_mutex);
}
/* the following is only access by one thread at one time */
/* query current shm stat*/
memset(&shm_stat, 0, sizeof(struct shm_stat_info));
if (res != shm_device_get_stat(shm_device,&shm_stat)) {
shm_lowmem_print(2,"shm_lowmemkiller fail to get shm stat info\n");
goto _exit_entry;
}
/* find task whose oom-adj larger than
* and use largest shm memory size than allocsize
*/
read_lock(&tasklist_lock);
for_each_process(p) {
struct mm_struct *mm;
struct signal_struct *sig;
int oom_adj;
size_t shmsize = 0;
int tasksize;
task_lock(p);
mm = p->mm;
sig = p->signal;
if (!mm || !sig) {
task_unlock(p);
continue;
}
/* ignore task whose pid less than shm_lowmem_start_oom_adj */
oom_adj = sig->oom_score_adj;
if (oom_adj < shm_lowmem_start_oom_adj) {
task_unlock(p);
continue;
}
tasksize = get_mm_rss(mm);
task_unlock(p);
shmsize = shm_shrink_find_task_usage(&shm_stat, p->pid);
/* ignore precess has no tasksize */
if (tasksize <= 0)
continue;
/* ignore process has little shmsize */
if (shmsize <= (shm_lowmem_min_release_kbytes * 1024))
continue;
/* if shmsize larger than shm_lowmem_min_free_kbytes
* select large oom-adj to release*/
if (selected && (oom_adj <= selected_oom_adj))
continue;
selected = p;
selected_shmsize = shmsize;
selected_oom_adj = oom_adj;
shm_lowmem_print(3, "shm_lowmem_killer select %d (%s),adj %d, shmsize %d to kill\n",
p->pid,p->comm,oom_adj,shmsize);
}
if (selected) {
shm_lowmem_print(2,"shm_lowmem_killer send sigkill to %d (%s), adj %d, shmsize %d\n",
selected->pid, selected->comm,
selected_oom_adj, selected_shmsize);
force_sig(SIGTERM, selected);
} else {
shm_lowmem_print(2,"shm_lowmem_killer alert!!!! no task selected to release\n");
shm_shrink_dump_usage(&shm_stat, 2);
}
read_unlock(&tasklist_lock);
res = 0;
_exit_entry:
down(&shm_lowmem_mutex);
shm_lowmem_going = 0;
if (selected) {
shm_lowmem_deathpending = selected;
shm_lowmem_deathpending_timeout = jiffies + HZ;
}else
shm_lowmem_deathpending = NULL;
up(&shm_lowmem_mutex);
return res;
}
static struct notifier_block shm_task_nb = {
.notifier_call = shm_task_notify_func,
};
/*******************************************************************************
Module API
*/
static int shm_driver_open(struct inode *inode, struct file *filp)
{
/*save the device and opening taskid to private_data,
* then from user space we will depend on the specified device
* since there are cache and non-cache device */
struct shm_device_priv_data *priv_data
= kzalloc(sizeof(struct shm_device_priv_data), GFP_KERNEL);
if (NULL == priv_data) {
shm_error("shm_driver_open fail to allocate private data\n");
return -ENOMEM;
}
priv_data->m_taskid = task_tgid_vnr(current);
priv_data->m_device = shm_device;
filp->private_data = priv_data;
shm_debug("shm_driver_open ok\n");
return 0;
}
static int shm_driver_open_noncache(struct inode *inode, struct file *filp)
{
/*save the device and opening task id to private_data
* then from user space we will depend on the specified device
* since there are cache and non-cache device */
struct shm_device_priv_data *priv_data
= kzalloc(sizeof(struct shm_device_priv_data), GFP_KERNEL);
if (NULL == priv_data) {
shm_error("shm_driver_open_noncache fail to allocate memory\n");
return -ENOMEM;
}
priv_data->m_taskid = task_tgid_vnr(current);
priv_data->m_device = shm_device_noncache;
filp->private_data = priv_data;
shm_debug("shm_driver_open_noncache ok\n");
return 0;
}
static int shm_driver_release(struct inode *inode, struct file *filp)
{
shm_device_t *pDevice;
pid_t taskid;
struct shm_device_priv_data *priv_data
= (struct shm_device_priv_data*)filp->private_data;
if (NULL == priv_data) {
shm_error("shm_driver_release private data is NULL\n");
return 0;
}
pDevice = priv_data->m_device;
taskid = priv_data->m_taskid;
/* free private data*/
kfree(priv_data);
filp->private_data = NULL;
shm_debug("shm_driver_release for pid:%d\n",taskid);
if (NULL == pDevice) {
shm_error("shm_driver_release device NULL\n");
return 0;
}
/* once the fd released, we should release allocated cache and noncache
* memory device accordingly to avoid shm leak */
if ((pDevice == shm_device) || (pDevice == shm_device_noncache))
{
shm_device_release_by_taskid(pDevice, taskid);
}
shm_debug("after shm_driver_release OK for pid:%d\n",taskid);
return 0;
}
static int shm_driver_mmap_noncache(struct file *filp, struct vm_area_struct *vma)
{
shm_device_t *pDevice;
unsigned long pfn, vsize;
struct shm_device_priv_data *priv_data
= (struct shm_device_priv_data*)filp->private_data;
if (NULL == priv_data) {
shm_error("shm_driver_mmap_noncache NULL private data\n");
return -ENOTTY;
}
pDevice = priv_data->m_device;
if (NULL == pDevice) {
shm_error("shm_driver_mmap_noncache NULL shm device\n");
return -ENOTTY;
}
pfn = pDevice->m_base >> PAGE_SHIFT;
vsize = vma->vm_end - vma->vm_start;
shm_debug("shm_driver_mmap_nocache size = 0x%08lX(0x%x, 0x%x), base:0x%x\n",
vsize, shm_size_noncache, pDevice->m_size, pDevice->m_base);
if (vsize > shm_size_noncache)
return -EINVAL;
vma->vm_pgoff = 0; // skip offset
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); // set memory non-cache
if (remap_pfn_range(vma, vma->vm_start, pfn, vsize, vma->vm_page_prot))
return -EAGAIN;
return 0;
}
static int shm_driver_mmap_cache(struct file *filp, struct vm_area_struct *vma)
{
unsigned long pfn, vsize;
shm_device_t *pDevice;
struct shm_device_priv_data *priv_data
= (struct shm_device_priv_data*)filp->private_data;
if (NULL == priv_data) {
shm_error("shm_driver_mmap_cache NULL private data\n");
return -ENOTTY;
}
pDevice = (shm_device_t*)priv_data->m_device;
if (NULL == pDevice) {
shm_error("shm_driver_mmap_cache NULL shm device\n");
return -ENOTTY;
}
pfn = pDevice->m_base >> PAGE_SHIFT;
vsize = vma->vm_end - vma->vm_start;
shm_debug("shm_driver_mmap_nocache size = 0x%08lX(0x%x, 0x%x), base:0x%x\n",
vsize, shm_size_cache, pDevice->m_size, pDevice->m_base);
if (vsize > shm_size_cache)
return -EINVAL;
vma->vm_pgoff = 0; // skip offset
if (remap_pfn_range(vma, vma->vm_start, pfn, vsize, vma->vm_page_prot))
return -EAGAIN;
return 0;
}
static long shm_driver_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
int res = 0;
shm_driver_operation_t op;
shm_device_t *shm_dev;
MV_SHM_MemInfo_t meminfo;
MV_SHM_BaseInfo_t baseinfo;
struct shm_device_priv_data *priv_data
= (struct shm_device_priv_data*)filp->private_data;
if (NULL == priv_data) {
shm_error("shm_driver_ioctl NULL private data\n");
return -ENOTTY;
}
shm_dev = (shm_device_t*)priv_data->m_device;
if (NULL == shm_dev) {
shm_error("shm_driver_ioctl NULL shm device\n");
return -ENOTTY;
}
shm_debug("shm_driver_ioctl cmd = 0x%08x\n, base:0x%08X size:0x%08X\n",
cmd, shm_dev->m_base, shm_dev->m_size);
switch (cmd) {
case SHM_DEVICE_CMD_GET_MEMINFO:
{
res = shm_device_get_meminfo(shm_dev, &meminfo);
if (res == 0)
res =
copy_to_user((void __user *)arg, &meminfo,
sizeof(meminfo));
break;
}
case SHM_DEVICE_CMD_GET_DEVINFO:
{
res = shm_device_get_baseinfo(shm_dev, &baseinfo);
if (res == 0)
res =
copy_to_user((void __user *)arg, &baseinfo,
sizeof(baseinfo));
break;
}
case SHM_DEVICE_CMD_ALLOCATE:
{
res =
copy_from_user(&op, (int __user *)arg, sizeof(op));
if (res != 0)
break;
op.m_param1 =
shm_device_allocate(shm_dev, op.m_param1,
op.m_param2);
res = copy_to_user((void __user *)arg, &op, sizeof(op));
break;
}
case SHM_DEVICE_CMD_FREE:
{
res =
copy_from_user(&op, (int __user *)arg, sizeof(op));
if (res != 0)
break;
op.m_param1 =
shm_device_free(shm_dev, op.m_param1);
res = copy_to_user((void __user *)arg, &op, sizeof(op));
break;
}
case SHM_DEVICE_CMD_INVALIDATE:
case SHM_DEVICE_CMD_CLEAN:
case SHM_DEVICE_CMD_CLEANANDINVALIDATE:
{
res =
copy_from_user(&op, (int __user *)arg, sizeof(op));
if (res == 0) {
res = shm_device_cache(shm_dev, cmd, op);
}
break;
}
default:
res = -ENOTTY;
}
shm_debug("shm_driver_ioctl res = %d\n", res);
return res;
}
static int meminfo_proc_show(struct seq_file *m, void *v)
{
int res;
MV_SHM_MemInfo_t meminfo;
res = shm_device_get_meminfo(shm_device, &meminfo);
if (res != 0) {
shm_error("shm_driver_read_proc_meminfo failed. (%d)\n", res);
return 0;
}
seq_printf(m, "cache memory information:\n"
"%20s : %10u Bytes\n"
"%20s : %10u Bytes\n"
"%20s : %10u Bytes\n"
"%20s : %10u Bytes\n"
"%20s : %10u Blocks\n"
"%20s : %10u Blocks\n",
"total mem", meminfo.m_totalmem,
"free mem", meminfo.m_freemem,
"used mem", meminfo.m_usedmem,
"peak used mem", meminfo.m_peak_usedmem,
"num free block", meminfo.m_num_freeblock,
"num used block", meminfo.m_num_usedblock);
res = shm_device_get_meminfo(shm_device_noncache, &meminfo);
if (res != 0) {
shm_error("shm_driver_read_proc_meminfo failed. (%d)\n", res);
return 0;
}
seq_printf(m, "non-cache memory information:\n"
"%20s : %10u Bytes\n"
"%20s : %10u Bytes\n"
"%20s : %10u Bytes\n"
"%20s : %10u Bytes\n"
"%20s : %10u Blocks\n"
"%20s : %10u Blocks\n",
"total mem", meminfo.m_totalmem,
"free mem", meminfo.m_freemem,
"used mem", meminfo.m_usedmem,
"peak used mem", meminfo.m_peak_usedmem,
"num free block", meminfo.m_num_freeblock,
"num used block", meminfo.m_num_usedblock);
return 0;
}
static int meminfo_proc_open(struct inode *inode, struct file *file)
{
return single_open(file, meminfo_proc_show, NULL);
}
static const struct file_operations meminfo_proc_fops = {
.open = meminfo_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int baseinfo_proc_show(struct seq_file *m, void *v)
{
int res;
MV_SHM_BaseInfo_t baseinfo;
res = shm_device_get_baseinfo(shm_device, &baseinfo);
if (res != 0) {
shm_error("shm_device_get_baseinfo failed. (%d)\n", res);
return 0;
}
seq_printf(m, "cache device base information:\n"
"%20s : %10u Bytes\n"
"%20s : %10u Bytes\n"
"------------ physical address:0x%08X\n",
"memory size", baseinfo.m_size,
"threshold", baseinfo.m_threshold,
baseinfo.m_base_physaddr);
res = shm_device_get_baseinfo(shm_device_noncache, &baseinfo);
if (res != 0) {
shm_error("shm_device_get_baseinfo failed. (%d)\n", res);
return 0;
}
seq_printf(m, "non-cache device base information:\n"
"%20s : %10u Bytes\n"
"%20s : %10u Bytes\n"
"------------ physical address:0x%08X\n",
"memory size", baseinfo.m_size,
"threshold", baseinfo.m_threshold,
baseinfo.m_base_physaddr);
return 0;
}
static int baseinfo_proc_open(struct inode *inode, struct file *file)
{
return single_open(file, baseinfo_proc_show, NULL);
}
static const struct file_operations baseinfo_proc_fops = {
.open = baseinfo_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int detail_seq_show(struct seq_file *file, void *data)
{
shm_device_t *pShmdev = (shm_device_t *) data;
if (pShmdev)
shm_device_show_detail(pShmdev, file);
return 0;
}
static void detail_seq_stop(struct seq_file *file, void *data)
{
}
static void *detail_seq_start(struct seq_file *file, loff_t * pos)
{
if (*pos == 0)
return (void *)shm_device;
/* return non cache device for the second time,
* first start will return cache device.
*/
else if (*pos == 1)
return (void *)shm_device_noncache;
return NULL;
}
static void *detail_seq_next(struct seq_file *file, void *data, loff_t * pos)
{
(*pos)++;
/* return non cache device for the second time,
* first start will return cache device.
*/
if (*pos == 1)
return (void *)shm_device_noncache;
return NULL;
}
static struct seq_operations detail_seq_ops = {
.start = detail_seq_start,
.next = detail_seq_next,
.stop = detail_seq_stop,
.show = detail_seq_show
};
static int detail_proc_open(struct inode *inode, struct file *file)
{
return seq_open(file, &detail_seq_ops);
}
static struct file_operations detail_proc_ops = {
.open = detail_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release
};
static int shm_stat_seq_show(struct seq_file *file, void *data)
{
shm_device_t *pShmdev = (shm_device_t *) data;
if (pShmdev)
shm_device_show_stat(pShmdev, file);
return 0;
}
static void shm_stat_seq_stop(struct seq_file *file, void *data)
{
}
static void *shm_stat_seq_start(struct seq_file *file, loff_t * pos)
{
if (*pos == 0)
return (void *)shm_device;
return NULL;
}
static void *shm_stat_seq_next(struct seq_file *file, void *data, loff_t * pos)
{
(*pos)++;
/* return non cache device for the second time,
* first start will return cache device.
*/
if (*pos == 0)
return (void *)shm_device;
return NULL;
}
static struct seq_operations shm_stat_seq_ops = {
.start = shm_stat_seq_start,
.next = shm_stat_seq_next,
.stop = shm_stat_seq_stop,
.show = shm_stat_seq_show
};
static int shm_stat_proc_open(struct inode *inode, struct file *file)
{
return seq_open(file, &shm_stat_seq_ops);
}
static struct file_operations shm_stat_file_ops = {
.owner = THIS_MODULE,
.open = shm_stat_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release
};
static int shm_driver_setup_cdev(struct cdev *dev, int major, int minor,
struct file_operations *fops)
{
cdev_init(dev, fops);
dev->owner = THIS_MODULE;
dev->ops = fops;
return cdev_add(dev, MKDEV(major, minor), 1);
}
static int __init shm_driver_init(void)
{
int res, i;
struct device_node *np;
struct resource r;
struct proc_dir_entry *pent = NULL;
struct proc_dir_entry *pstat = NULL;
np = of_find_compatible_node(NULL, NULL, "mrvl,berlin-shm");
if (!np)
goto err_node;
res = of_address_to_resource(np, 0, &r);
if (res)
goto err_reg_device;
shm_base_cache = r.start;
shm_size_cache = resource_size(&r);
res = of_address_to_resource(np, 1, &r);
if (res)
goto err_reg_device;
shm_base_noncache = r.start;
shm_size_noncache = resource_size(&r);
of_node_put(np);
/* Figure out our device number. */
res =
register_chrdev_region(MKDEV(GALOIS_SHM_MAJOR, 0),
GALOIS_SHM_MINORS, SHM_DEVICE_NAME);
if (res < 0) {
shm_error("unable to get shm device major [%d]\n",
GALOIS_SHM_MAJOR);
goto err_reg_device;
}
shm_debug("register cdev device major [%d]\n", GALOIS_SHM_MAJOR);
/* Now setup cdevs. */
for (i = 0; i < ARRAY_SIZE(shm_driver_dev_list); i++) {
res = shm_driver_setup_cdev(shm_driver_dev_list[i].cdev,
GALOIS_SHM_MAJOR,
shm_driver_dev_list[i].minor,
shm_driver_dev_list[i].fops);
if (res) {
shm_error("shm_driver_setup_cdev failed in [%d].\n", i);
goto err_add_device;
}
shm_debug("setup cdevs device minor [%d]\n",
shm_driver_dev_list[i].minor);
}
/* add shm devices to sysfs */
shm_dev_class = class_create(THIS_MODULE, SHM_DEVICE_NAME);
if (IS_ERR(shm_dev_class)) {
shm_error("class_create failed.\n");
res = -ENODEV;
goto err_add_device;
}
for (i = 0; i < ARRAY_SIZE(shm_driver_dev_list); i++) {
device_create(shm_dev_class, NULL,
MKDEV(GALOIS_SHM_MAJOR,
shm_driver_dev_list[i].minor), NULL,
shm_driver_dev_list[i].name);
shm_debug("create device sysfs [%s]\n",
shm_driver_dev_list[i].name);
}
/* create shm cache device */
res =
shm_device_create(&shm_device, shm_base_cache, shm_size_cache,
SHM_DEVICE_THRESHOLD);
if (res != 0) {
shm_error("shm_device_create failed.\n");
goto err_add_device;
}
/* init shrinker */
shm_device->m_shrinker = shm_lowmem_shrink_killer;
/* create shm cache device */
res = shm_device_create(&shm_device_noncache, shm_base_noncache,
shm_size_noncache, SHM_DEVICE_THRESHOLD);
if (res != 0) {
shm_error("shm_device_create failed.\n");
goto err_add_device;
}
/* init shrinker */
shm_device_noncache->m_shrinker = NULL;
/* create shm kernel API, need map for noncache and cache device!!! */
res = MV_SHM_Init(shm_device_noncache, shm_device);
if (res != 0) {
shm_error("MV_SHM_Init failed !!!\n");
goto err_SHM_Init;
}
/* create shm device proc file */
shm_driver_procdir = proc_mkdir(SHM_DEVICE_NAME, NULL);
if (!shm_driver_procdir) {
shm_error(KERN_WARNING "Failed to mkdir /proc/%s\n", SHM_DEVICE_NAME);
return 0;
}
proc_create("meminfo", 0, shm_driver_procdir, &meminfo_proc_fops);
proc_create("baseinfo", 0, shm_driver_procdir, &baseinfo_proc_fops);
pent = create_proc_entry("detail", 0, shm_driver_procdir);
if (pent)
pent->proc_fops = &detail_proc_ops;
pstat = create_proc_entry("stat", 0, shm_driver_procdir);
if (pstat)
pstat->proc_fops = &shm_stat_file_ops;
task_free_register(&shm_task_nb);
shm_trace("shm_driver_init OK\n");
return 0;
err_SHM_Init:
shm_trace("shm_driver_init Undo ...\n");
shm_device_destroy(&shm_device);
shm_device_destroy(&shm_device_noncache);
/* del sysfs entries */
for (i = 0; i < ARRAY_SIZE(shm_driver_dev_list); i++) {
device_destroy(shm_dev_class,
MKDEV(GALOIS_SHM_MAJOR,
shm_driver_dev_list[i].minor));
shm_debug("delete device sysfs [%s]\n",
shm_driver_dev_list[i].name);
}
class_destroy(shm_dev_class);
err_add_device:
for (i = 0; i < ARRAY_SIZE(shm_driver_dev_list); i++) {
cdev_del(shm_driver_dev_list[i].cdev);
}
unregister_chrdev_region(MKDEV(GALOIS_SHM_MAJOR, 0), GALOIS_SHM_MINORS);
err_reg_device:
of_node_put(np);
err_node:
shm_trace("shm_driver_init failed !!! (%d)\n", res);
return res;
}
static void __exit shm_driver_exit(void)
{
int res, i;
task_free_unregister(&shm_task_nb);
/* destroy shm kernel API */
res = MV_SHM_Exit();
if (res != 0)
shm_error("MV_SHM_Exit failed !!!\n");
/* remove shm device proc file */
remove_proc_entry("meminfo", shm_driver_procdir);
remove_proc_entry("baseinfo", shm_driver_procdir);
remove_proc_entry("detail", shm_driver_procdir);
remove_proc_entry("stat", shm_driver_procdir);
remove_proc_entry(SHM_DEVICE_NAME, NULL);
if (shm_device_destroy(&shm_device) != 0)
shm_error("shm_device_destroy cache mem failed.\n");
if (shm_device_destroy(&shm_device_noncache) != 0)
shm_error("shm_device_destroy non-cache mem failed.\n");
/* del sysfs entries */
for (i = 0; i < ARRAY_SIZE(shm_driver_dev_list); i++) {
device_destroy(shm_dev_class,
MKDEV(GALOIS_SHM_MAJOR,
shm_driver_dev_list[i].minor));
shm_debug("delete device sysfs [%s]\n",
shm_driver_dev_list[i].name);
}
class_destroy(shm_dev_class);
/* del cdev */
for (i = 0; i < ARRAY_SIZE(shm_driver_dev_list); i++) {
cdev_del(shm_driver_dev_list[i].cdev);
shm_debug("delete cdevs device minor [%d]\n",
shm_driver_dev_list[i].minor);
}
unregister_chrdev_region(MKDEV(GALOIS_SHM_MAJOR, 0), GALOIS_SHM_MINORS);
shm_debug("unregister cdev device major [%d]\n", GALOIS_SHM_MAJOR);
shm_trace("shm_driver_exit OK\n");
}
module_init(shm_driver_init);
module_exit(shm_driver_exit);