diff --git a/drivers/md/dm-table.c b/drivers/md/dm-table.c index a483f49dd4ce..f64db28aa15c 100644 --- a/drivers/md/dm-table.c +++ b/drivers/md/dm-table.c @@ -163,6 +163,24 @@ int dm_table_create(struct dm_table **result, blk_mode_t mode, return 0; } +struct dm_dev_old { + struct list_head list; + struct dm_dev *dm_dev; +}; + +static void put_table_devices(struct dm_dev_internal *dd, + struct mapped_device *md) +{ + struct dm_dev_old *old, *tmp; + + list_for_each_entry_safe(old, tmp, &dd->old_devices, list) { + dm_put_table_device(md, old->dm_dev); + list_del(&old->list); + kfree(old); + } + dm_put_table_device(md, dd->dm_dev); +} + static void free_devices(struct list_head *devices, struct mapped_device *md) { struct list_head *tmp, *next; @@ -172,7 +190,7 @@ static void free_devices(struct list_head *devices, struct mapped_device *md) list_entry(tmp, struct dm_dev_internal, list); DMWARN("%s: dm_table_destroy: dm_put_device call missing for %s", dm_device_name(md), dd->dm_dev->name); - dm_put_table_device(md, dd->dm_dev); + put_table_devices(dd, md); kfree(dd); } } @@ -313,17 +331,23 @@ static int upgrade_mode(struct dm_dev_internal *dd, blk_mode_t new_mode, struct mapped_device *md) { int r; - struct dm_dev *old_dev, *new_dev; + struct dm_dev *new_dev; + struct dm_dev_old *old; - old_dev = dd->dm_dev; + old = kmalloc_obj(*old); + if (!old) + return -ENOMEM; r = dm_get_table_device(md, dd->dm_dev->bdev->bd_dev, dd->dm_dev->mode | new_mode, &new_dev); - if (r) + if (r) { + kfree(old); return r; + } + old->dm_dev = dd->dm_dev; + list_add_tail(&old->list, &dd->old_devices); dd->dm_dev = new_dev; - dm_put_table_device(md, old_dev); return 0; } @@ -392,6 +416,7 @@ int dm_get_device(struct dm_target *ti, const char *path, blk_mode_t mode, } refcount_set(&dd->count, 1); + INIT_LIST_HEAD(&dd->old_devices); list_add(&dd->list, &t->devices); goto out; @@ -451,6 +476,20 @@ static int dm_set_device_limits(struct dm_target *ti, struct dm_dev *dev, /* * Decrement a device's use count and remove it if necessary. */ +static bool device_matches(struct dm_dev_internal *dd, struct dm_dev *d) +{ + struct dm_dev_old *old; + + if (dd->dm_dev == d) + return true; + + list_for_each_entry(old, &dd->old_devices, list) + if (old->dm_dev == d) + return true; + + return false; +} + void dm_put_device(struct dm_target *ti, struct dm_dev *d) { int found = 0; @@ -458,7 +497,7 @@ void dm_put_device(struct dm_target *ti, struct dm_dev *d) struct dm_dev_internal *dd; list_for_each_entry(dd, devices, list) { - if (dd->dm_dev == d) { + if (device_matches(dd, d)) { found = 1; break; } @@ -469,7 +508,7 @@ void dm_put_device(struct dm_target *ti, struct dm_dev *d) return; } if (refcount_dec_and_test(&dd->count)) { - dm_put_table_device(ti->table->md, d); + put_table_devices(dd, ti->table->md); list_del(&dd->list); kfree(dd); } diff --git a/drivers/md/dm.h b/drivers/md/dm.h index 7a795979ec72..037d6cdf2f1c 100644 --- a/drivers/md/dm.h +++ b/drivers/md/dm.h @@ -42,6 +42,7 @@ struct dm_dev_internal { struct list_head list; refcount_t count; struct dm_dev *dm_dev; + struct list_head old_devices; }; struct dm_table;