783c2fb1b8
The fmc-chardev module was over-locking, by calling misc register/unregister while holding its spinlock. This reported a "scheduling while atomic" error. Since the misc driver already serializes operations internally, this commit downgrades the fmc-chardev lock to just cover its own list. Reported-by: Sasha Levin <sasha.levin@oracle.com> Reported-by: Fengguang Wu <fengguang.wu@intel.com> Signed-off-by: Alessandro Rubini <rubini@gnudd.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
202 lines
4.1 KiB
C
202 lines
4.1 KiB
C
/*
|
|
* Copyright (C) 2012 CERN (www.cern.ch)
|
|
* Author: Alessandro Rubini <rubini@gnudd.com>
|
|
*
|
|
* Released according to the GNU GPL, version 2 or any later version.
|
|
*
|
|
* This work is part of the White Rabbit project, a research effort led
|
|
* by CERN, the European Institute for Nuclear Research.
|
|
*/
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/list.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/fmc.h>
|
|
#include <linux/uaccess.h>
|
|
|
|
static LIST_HEAD(fc_devices);
|
|
static DEFINE_SPINLOCK(fc_lock);
|
|
|
|
struct fc_instance {
|
|
struct list_head list;
|
|
struct fmc_device *fmc;
|
|
struct miscdevice misc;
|
|
};
|
|
|
|
/* at open time, we must identify our device */
|
|
static int fc_open(struct inode *ino, struct file *f)
|
|
{
|
|
struct fmc_device *fmc;
|
|
struct fc_instance *fc;
|
|
int minor = iminor(ino);
|
|
|
|
list_for_each_entry(fc, &fc_devices, list)
|
|
if (fc->misc.minor == minor)
|
|
break;
|
|
if (fc->misc.minor != minor)
|
|
return -ENODEV;
|
|
fmc = fc->fmc;
|
|
if (try_module_get(fmc->owner) == 0)
|
|
return -ENODEV;
|
|
|
|
f->private_data = fmc;
|
|
return 0;
|
|
}
|
|
|
|
static int fc_release(struct inode *ino, struct file *f)
|
|
{
|
|
struct fmc_device *fmc = f->private_data;
|
|
module_put(fmc->owner);
|
|
return 0;
|
|
}
|
|
|
|
/* read and write are simple after the default llseek has been used */
|
|
static ssize_t fc_read(struct file *f, char __user *buf, size_t count,
|
|
loff_t *offp)
|
|
{
|
|
struct fmc_device *fmc = f->private_data;
|
|
unsigned long addr;
|
|
uint32_t val;
|
|
|
|
if (count < sizeof(val))
|
|
return -EINVAL;
|
|
count = sizeof(val);
|
|
|
|
addr = *offp;
|
|
if (addr > fmc->memlen)
|
|
return -ESPIPE; /* Illegal seek */
|
|
val = fmc_readl(fmc, addr);
|
|
if (copy_to_user(buf, &val, count))
|
|
return -EFAULT;
|
|
*offp += count;
|
|
return count;
|
|
}
|
|
|
|
static ssize_t fc_write(struct file *f, const char __user *buf, size_t count,
|
|
loff_t *offp)
|
|
{
|
|
struct fmc_device *fmc = f->private_data;
|
|
unsigned long addr;
|
|
uint32_t val;
|
|
|
|
if (count < sizeof(val))
|
|
return -EINVAL;
|
|
count = sizeof(val);
|
|
|
|
addr = *offp;
|
|
if (addr > fmc->memlen)
|
|
return -ESPIPE; /* Illegal seek */
|
|
if (copy_from_user(&val, buf, count))
|
|
return -EFAULT;
|
|
fmc_writel(fmc, val, addr);
|
|
*offp += count;
|
|
return count;
|
|
}
|
|
|
|
static const struct file_operations fc_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = fc_open,
|
|
.release = fc_release,
|
|
.llseek = generic_file_llseek,
|
|
.read = fc_read,
|
|
.write = fc_write,
|
|
};
|
|
|
|
|
|
/* Device part .. */
|
|
static int fc_probe(struct fmc_device *fmc);
|
|
static int fc_remove(struct fmc_device *fmc);
|
|
|
|
static struct fmc_driver fc_drv = {
|
|
.version = FMC_VERSION,
|
|
.driver.name = KBUILD_MODNAME,
|
|
.probe = fc_probe,
|
|
.remove = fc_remove,
|
|
/* no table: we want to match everything */
|
|
};
|
|
|
|
/* We accept the generic busid parameter */
|
|
FMC_PARAM_BUSID(fc_drv);
|
|
|
|
/* probe and remove must allocate and release a misc device */
|
|
static int fc_probe(struct fmc_device *fmc)
|
|
{
|
|
int ret;
|
|
int index = 0;
|
|
|
|
struct fc_instance *fc;
|
|
|
|
if (fmc->op->validate)
|
|
index = fmc->op->validate(fmc, &fc_drv);
|
|
if (index < 0)
|
|
return -EINVAL; /* not our device: invalid */
|
|
|
|
/* Create a char device: we want to create it anew */
|
|
fc = kzalloc(sizeof(*fc), GFP_KERNEL);
|
|
if (!fc)
|
|
return -ENOMEM;
|
|
fc->fmc = fmc;
|
|
fc->misc.minor = MISC_DYNAMIC_MINOR;
|
|
fc->misc.fops = &fc_fops;
|
|
fc->misc.name = kstrdup(dev_name(&fmc->dev), GFP_KERNEL);
|
|
|
|
ret = misc_register(&fc->misc);
|
|
if (ret < 0)
|
|
goto out;
|
|
spin_lock(&fc_lock);
|
|
list_add(&fc->list, &fc_devices);
|
|
spin_unlock(&fc_lock);
|
|
dev_info(&fc->fmc->dev, "Created misc device \"%s\"\n",
|
|
fc->misc.name);
|
|
return 0;
|
|
|
|
out:
|
|
kfree(fc->misc.name);
|
|
kfree(fc);
|
|
return ret;
|
|
}
|
|
|
|
static int fc_remove(struct fmc_device *fmc)
|
|
{
|
|
struct fc_instance *fc;
|
|
|
|
list_for_each_entry(fc, &fc_devices, list)
|
|
if (fc->fmc == fmc)
|
|
break;
|
|
if (fc->fmc != fmc) {
|
|
dev_err(&fmc->dev, "remove called but not found\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
spin_lock(&fc_lock);
|
|
list_del(&fc->list);
|
|
spin_unlock(&fc_lock);
|
|
misc_deregister(&fc->misc);
|
|
kfree(fc->misc.name);
|
|
kfree(fc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int fc_init(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = fmc_driver_register(&fc_drv);
|
|
return ret;
|
|
}
|
|
|
|
static void fc_exit(void)
|
|
{
|
|
fmc_driver_unregister(&fc_drv);
|
|
}
|
|
|
|
module_init(fc_init);
|
|
module_exit(fc_exit);
|
|
|
|
MODULE_LICENSE("GPL");
|