diff --git a/drivers/mtd/devices/block2mtd.c b/drivers/mtd/devices/block2mtd.c index 03e80b2c4..4ab4c31a2 100644 --- a/drivers/mtd/devices/block2mtd.c +++ b/drivers/mtd/devices/block2mtd.c @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include #include #include @@ -45,6 +47,7 @@ struct block2mtd_dev { /* Static info about the MTD, used in cleanup_module */ static LIST_HEAD(blkmtd_device_list); +static DEFINE_MUTEX(block2mtd_mutex); static struct page *page_read(struct address_space *mapping, pgoff_t index) @@ -329,7 +332,9 @@ static struct block2mtd_dev *add_device(char *devname, int erase_size, goto err_destroy_mutex; } + mutex_lock(&block2mtd_mutex); list_add(&dev->list, &blkmtd_device_list); + mutex_unlock(&block2mtd_mutex); pr_info("mtd%d: [%s] erase_size = %dKiB [%d]\n", dev->mtd.index, label ? label : dev->mtd.name + strlen("block2mtd: "), @@ -462,30 +467,77 @@ static int block2mtd_setup2(const char *val) } -static int block2mtd_setup(const char *val, const struct kernel_param *kp) + +struct block2mtd_setup_work { + struct work_struct work; + char *val; + struct completion done; + int ret; +}; + +static void block2mtd_setup_workfn(struct work_struct *work) { -#ifdef MODULE - return block2mtd_setup2(val); -#else - /* If more parameters are later passed in via - /sys/module/block2mtd/parameters/block2mtd - and block2mtd_init() has already been called, - we can parse the argument now. */ + struct block2mtd_setup_work *w = + container_of(work, struct block2mtd_setup_work, work); + + w->ret = block2mtd_setup2(w->val); + complete(&w->done); +} + +/* + * Run device setup outside the module-parameter / kernfs write path. + * Those paths hold param_lock and the kernfs inode mutex (and, when the + * write arrives via splice, a pipe mutex). Opening a block device does + * VFS lookups and must not nest under that stack. + */ +static int block2mtd_setup_defer(const char *val) +{ + struct block2mtd_setup_work w = { + .ret = 0, + }; + + w.val = kstrdup(val, GFP_KERNEL); + if (!w.val) + return -ENOMEM; + + init_completion(&w.done); + INIT_WORK(&w.work, block2mtd_setup_workfn); + schedule_work(&w.work); + wait_for_completion(&w.done); + kfree(w.val); + return w.ret; +} - if (block2mtd_init_called) - return block2mtd_setup2(val); +static int block2mtd_setup(const char *val, const struct kernel_param *kp) +{ + int ret = 0; - /* During early boot stage, we only save the parameters - here. We must parse them later: if the param passed - from kernel boot command line, block2mtd_setup() is - called so early that it is not possible to resolve - the device (even kmalloc() fails). Deter that work to - block2mtd_setup2(). */ + if (!try_module_get(kp->mod)) + return -ENODEV; - strscpy(block2mtd_paramline, val, sizeof(block2mtd_paramline)); + /* + * Drop param_lock before scheduling. The actual open runs on a + * workqueue so it is also outside kernfs_fop_write_iter's inode + * mutex (and any pipe lock from splice). + */ + kernel_param_unlock(kp->mod); - return 0; +#ifdef MODULE + ret = block2mtd_setup_defer(val); +#else + if (block2mtd_init_called) { + ret = block2mtd_setup_defer(val); + } else { + mutex_lock(&block2mtd_mutex); + strscpy(block2mtd_paramline, val, sizeof(block2mtd_paramline)); + mutex_unlock(&block2mtd_mutex); + } #endif + + kernel_param_lock(kp->mod); + module_put(kp->mod); + + return ret; } @@ -497,9 +549,18 @@ static int __init block2mtd_init(void) int ret = 0; #ifndef MODULE - if (strlen(block2mtd_paramline)) - ret = block2mtd_setup2(block2mtd_paramline); + mutex_lock(&block2mtd_mutex); + if (strlen(block2mtd_paramline)) { + char buf[sizeof(block2mtd_paramline)]; + + strscpy(buf, block2mtd_paramline, sizeof(buf)); + mutex_unlock(&block2mtd_mutex); + /* init context: no kernfs/param locks held */ + ret = block2mtd_setup2(buf); + mutex_lock(&block2mtd_mutex); + } block2mtd_init_called = 1; + mutex_unlock(&block2mtd_mutex); #endif return ret; @@ -510,6 +571,7 @@ static void block2mtd_exit(void) { struct list_head *pos, *next; + mutex_lock(&block2mtd_mutex); /* Remove the MTD devices */ list_for_each_safe(pos, next, &blkmtd_device_list) { struct block2mtd_dev *dev = list_entry(pos, typeof(*dev), list); @@ -522,6 +584,7 @@ static void block2mtd_exit(void) list_del(&dev->list); block2mtd_free_device(dev); } + mutex_unlock(&block2mtd_mutex); } late_initcall(block2mtd_init);