diff --git a/block/blk-cgroup.c b/block/blk-cgroup.c index a06547fe6f6b461eb9c148fe630e123520184b1a..e592167449aa3e562bd1368c51a68355688e0888 100644 --- a/block/blk-cgroup.c +++ b/block/blk-cgroup.c @@ -473,8 +473,11 @@ static int blkcg_reset_stats(struct cgroup_subsys_state *css, const char *blkg_dev_name(struct blkcg_gq *blkg) { /* some drivers (floppy) instantiate a queue w/o disk registered */ - if (blkg->q->backing_dev_info->dev) - return dev_name(blkg->q->backing_dev_info->dev); + struct rcu_device *rcu_dev; + + rcu_dev = rcu_dereference(blkg->q->backing_dev_info->rcu_dev); + if (rcu_dev) + return dev_name(&rcu_dev->dev); return NULL; } EXPORT_SYMBOL_GPL(blkg_dev_name); diff --git a/include/linux/backing-dev-defs.h b/include/linux/backing-dev-defs.h index 3cfe7de124034cc4d4cd43aca9d198cb43aed3e1..73fb6bc0d40cf69fdf5569d119478bc77841b122 100644 --- a/include/linux/backing-dev-defs.h +++ b/include/linux/backing-dev-defs.h @@ -201,7 +201,10 @@ struct backing_dev_info { #endif wait_queue_head_t wb_waitq; - struct device *dev; + union { + struct rcu_device *rcu_dev; + struct device *dev; + }; struct device *owner; struct timer_list laptop_mode_wb_timer; diff --git a/include/linux/device.h b/include/linux/device.h index 23a152984cb970ce43733c02a6dcabeed8b6dcc0..0d6960c6b0dbc9fb2e4c16150593b177f55a0a80 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -1073,6 +1073,11 @@ struct device { KABI_RESERVE(16) }; +struct rcu_device { + struct device dev; + struct rcu_head rcu_head; +}; + static inline struct device *kobj_to_dev(struct kobject *kobj) { return container_of(kobj, struct device, kobj); diff --git a/mm/backing-dev.c b/mm/backing-dev.c index 00277c8855c68a3d28d57e2926d90cee8772fbc3..040d778db5d025ca17144aa628afa5c23d6f7287 100644 --- a/mm/backing-dev.c +++ b/mm/backing-dev.c @@ -872,19 +872,43 @@ struct backing_dev_info *bdi_alloc_node(gfp_t gfp_mask, int node_id) } EXPORT_SYMBOL(bdi_alloc_node); +static void bdi_device_release(struct device *dev) +{ + struct rcu_device *rcu_dev = container_of(dev, + struct rcu_device, dev); + kfree(rcu_dev); +} + int bdi_register_va(struct backing_dev_info *bdi, const char *fmt, va_list args) { struct device *dev; + struct rcu_device *rcu_dev; + int retval = -ENODEV; if (bdi->dev) /* The driver needs to use separate queues per device */ return 0; - dev = device_create_vargs(bdi_class, NULL, MKDEV(0, 0), bdi, fmt, args); - if (IS_ERR(dev)) - return PTR_ERR(dev); + rcu_dev = kzalloc(sizeof(struct rcu_device), GFP_KERNEL); + if (!rcu_dev) + return -ENOMEM; + + /* initialize device */ + dev = &rcu_dev->dev; + device_initialize(dev); + dev->class = bdi_class; + dev->release = bdi_device_release; + dev->devt = MKDEV(0, 0); + dev_set_drvdata(dev, (void *)bdi); + retval = kobject_set_name_vargs(&dev->kobj, fmt, args); + if (retval) + goto error; + + retval = device_add(dev); + if (retval) + goto error; cgwb_bdi_register(bdi); - bdi->dev = dev; + bdi->rcu_dev = rcu_dev; bdi_debug_register(bdi, dev_name(dev)); set_bit(WB_registered, &bdi->wb.state); @@ -895,6 +919,10 @@ int bdi_register_va(struct backing_dev_info *bdi, const char *fmt, va_list args) trace_writeback_bdi_register(bdi); return 0; + +error: + kfree(rcu_dev); + return retval; } EXPORT_SYMBOL(bdi_register_va); @@ -937,17 +965,28 @@ static void bdi_remove_from_list(struct backing_dev_info *bdi) synchronize_rcu_expedited(); } +static void bdi_put_device_rcu(struct rcu_head *rcu) +{ + struct rcu_device *rcu_dev = container_of(rcu, struct rcu_device, + rcu_head); + put_device(&rcu_dev->dev); +} void bdi_unregister(struct backing_dev_info *bdi) { /* make sure nobody finds us on the bdi_list anymore */ + struct rcu_device *rcu_dev = bdi->rcu_dev; bdi_remove_from_list(bdi); wb_shutdown(&bdi->wb); cgwb_bdi_unregister(bdi); if (bdi->dev) { bdi_debug_unregister(bdi); + get_device(bdi->dev); device_unregister(bdi->dev); - bdi->dev = NULL; + rcu_assign_pointer(bdi->dev, NULL); + + /* wait all rcu reader of bdi->dev before free dev */ + call_rcu(&rcu_dev->rcu_head, bdi_put_device_rcu); } if (bdi->owner) {