diff --git a/drivers/mtd/devices/block2mtd.c b/drivers/mtd/devices/block2mtd.c index 03e80b2..a540089 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,9 @@ struct block2mtd_dev { /* Static info about the MTD, used in cleanup_module */ static LIST_HEAD(blkmtd_device_list); +/* Protects blkmtd_device_list and early-boot paramline updates */ +static DEFINE_MUTEX(list_mutex); +static struct workqueue_struct *setup_wq; static struct page *page_read(struct address_space *mapping, pgoff_t index) @@ -461,31 +466,85 @@ static int block2mtd_setup2(const char *val) return 0; } +struct block2mtd_setup_work { + struct work_struct work; + struct completion done; + char *val; + int ret; +}; + +static void block2mtd_setup_workfn(struct work_struct *work) +{ + struct block2mtd_setup_work *w = + container_of(work, struct block2mtd_setup_work, work); + + mutex_lock(&list_mutex); + w->ret = block2mtd_setup2(w->val); + mutex_unlock(&list_mutex); + complete(&w->done); +} + +/* Runs block2mtd_setup2() on setup_wq, blocking until it completes */ +static int block2mtd_setup_defer(const char *val) +{ + struct block2mtd_setup_work *w; + int ret; + + w = kzalloc(sizeof(*w), GFP_KERNEL); + if (!w) + return -ENOMEM; + + w->val = kstrdup(val, GFP_KERNEL); + if (!w->val) { + kfree(w); + return -ENOMEM; + } + + init_completion(&w->done); + INIT_WORK(&w->work, block2mtd_setup_workfn); + queue_work(setup_wq, &w->work); + wait_for_completion(&w->done); + + ret = w->ret; + kfree(w->val); + kfree(w); + return ret; +} static int block2mtd_setup(const char *val, const struct kernel_param *kp) { -#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. */ - - if (block2mtd_init_called) - return block2mtd_setup2(val); - - /* 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(). */ + int ret = 0; - strscpy(block2mtd_paramline, val, sizeof(block2mtd_paramline)); + if (!try_module_get(kp->mod)) + return -ENODEV; - return 0; + kernel_param_unlock(kp->mod); + +#ifndef MODULE + mutex_lock(&list_mutex); + if (!block2mtd_init_called) { + /* Cannot resolve block devices this early */ + strscpy(block2mtd_paramline, val, sizeof(block2mtd_paramline)); + mutex_unlock(&list_mutex); + kernel_param_lock(kp->mod); + module_put(kp->mod); + return 0; + } + mutex_unlock(&list_mutex); #endif + + if (setup_wq) { + ret = block2mtd_setup_defer(val); + } else { + /* Not yet deferred to setup_wq; safe to call setup2 directly */ + mutex_lock(&list_mutex); + ret = block2mtd_setup2(val); + mutex_unlock(&list_mutex); + } + + kernel_param_lock(kp->mod); + module_put(kp->mod); + return ret; } @@ -496,10 +555,17 @@ static int __init block2mtd_init(void) { int ret = 0; + setup_wq = alloc_ordered_workqueue("block2mtd", 0); + if (!setup_wq) + return -ENOMEM; + #ifndef MODULE + mutex_lock(&list_mutex); if (strlen(block2mtd_paramline)) ret = block2mtd_setup2(block2mtd_paramline); + /* Avoid racing sysfs with the early paramline */ block2mtd_init_called = 1; + mutex_unlock(&list_mutex); #endif return ret; @@ -510,9 +576,16 @@ static void block2mtd_exit(void) { struct list_head *pos, *next; - /* Remove the MTD devices */ + if (setup_wq) { + flush_workqueue(setup_wq); + destroy_workqueue(setup_wq); + setup_wq = NULL; + } + + mutex_lock(&list_mutex); list_for_each_safe(pos, next, &blkmtd_device_list) { struct block2mtd_dev *dev = list_entry(pos, typeof(*dev), list); + block2mtd_sync(&dev->mtd); mtd_device_unregister(&dev->mtd); mutex_destroy(&dev->write_mutex); @@ -522,6 +595,7 @@ static void block2mtd_exit(void) list_del(&dev->list); block2mtd_free_device(dev); } + mutex_unlock(&list_mutex); } late_initcall(block2mtd_init);