diff --git a/drivers/block/loop.c b/drivers/block/loop.c index f0cdff0c5fbf..5e60d39be693 100644 --- a/drivers/block/loop.c +++ b/drivers/block/loop.c @@ -86,8 +86,15 @@ #define LOOP_IDLE_WORKER_TIMEOUT (60 * HZ) +/* + * Used for avoiding -EEXIST error at bdi_register() which happens when id is reused + * before bdi_unregister() completes, by preserving specific id for loop_index_idr. + */ +#define HIDDEN_LOOP_DEVICE ((struct loop_device *) -1) + static DEFINE_IDR(loop_index_idr); -static DEFINE_MUTEX(loop_ctl_mutex); +static DEFINE_SPINLOCK(loop_idr_spinlock); +static DEFINE_MUTEX(loop_removal_mutex); static DEFINE_MUTEX(loop_validate_mutex); /** @@ -2113,28 +2120,37 @@ int loop_register_transfer(struct loop_func_table *funcs) return 0; } -static int unregister_transfer_cb(int id, void *ptr, void *data) -{ - struct loop_device *lo = ptr; - struct loop_func_table *xfer = data; - - mutex_lock(&lo->lo_mutex); - if (lo->lo_encryption == xfer) - loop_release_xfer(lo); - mutex_unlock(&lo->lo_mutex); - return 0; -} - int loop_unregister_transfer(int number) { unsigned int n = number; struct loop_func_table *xfer; + struct loop_device *lo; + int id; if (n == 0 || n >= MAX_LO_CRYPT || (xfer = xfer_funcs[n]) == NULL) return -EINVAL; xfer_funcs[n] = NULL; - idr_for_each(&loop_index_idr, &unregister_transfer_cb, xfer); + + /* + * Use loop_removal_mutex in order to make sure that + * loop_control_remove() won't call loop_remove(). + */ + mutex_lock(&loop_removal_mutex); + spin_lock(&loop_idr_spinlock); + idr_for_each_entry(&loop_index_idr, lo, id) { + if (lo == HIDDEN_LOOP_DEVICE) + continue; + spin_unlock(&loop_idr_spinlock); + mutex_lock(&lo->lo_mutex); + if (lo->lo_encryption == xfer) + loop_release_xfer(lo); + mutex_unlock(&lo->lo_mutex); + spin_lock(&loop_idr_spinlock); + } + spin_unlock(&loop_idr_spinlock); + mutex_unlock(&loop_removal_mutex); + return 0; } @@ -2313,20 +2329,20 @@ static int loop_add(int i) goto out; lo->lo_state = Lo_unbound; - err = mutex_lock_killable(&loop_ctl_mutex); - if (err) - goto out_free_dev; - /* allocate id, if @id >= 0, we're requesting that specific id */ + idr_preload(GFP_KERNEL); + spin_lock(&loop_idr_spinlock); if (i >= 0) { - err = idr_alloc(&loop_index_idr, lo, i, i + 1, GFP_KERNEL); + err = idr_alloc(&loop_index_idr, HIDDEN_LOOP_DEVICE, i, i + 1, GFP_ATOMIC); if (err == -ENOSPC) err = -EEXIST; } else { - err = idr_alloc(&loop_index_idr, lo, 0, 0, GFP_KERNEL); + err = idr_alloc(&loop_index_idr, HIDDEN_LOOP_DEVICE, 0, 0, GFP_ATOMIC); } + spin_unlock(&loop_idr_spinlock); + idr_preload_end(); if (err < 0) - goto out_unlock; + goto out_free_dev; i = err; err = -ENOMEM; @@ -2393,15 +2409,19 @@ static int loop_add(int i) disk->queue = lo->lo_queue; sprintf(disk->disk_name, "loop%d", i); add_disk(disk); - mutex_unlock(&loop_ctl_mutex); + /* Show this loop device. */ + spin_lock(&loop_idr_spinlock); + idr_replace(&loop_index_idr, lo, i); + spin_unlock(&loop_idr_spinlock); return i; out_cleanup_tags: blk_mq_free_tag_set(&lo->tag_set); out_free_idr: + /* This loop device was never shown. */ + spin_lock(&loop_idr_spinlock); idr_remove(&loop_index_idr, i); -out_unlock: - mutex_unlock(&loop_ctl_mutex); + spin_unlock(&loop_idr_spinlock); out_free_dev: kfree(lo); out: @@ -2413,6 +2433,10 @@ static void loop_remove(struct loop_device *lo) del_gendisk(lo->lo_disk); blk_cleanup_disk(lo->lo_disk); blk_mq_free_tag_set(&lo->tag_set); + /* Make this loop device unreachable from loop_index_idr. */ + spin_lock(&loop_idr_spinlock); + idr_remove(&loop_index_idr, lo->lo_number); + spin_unlock(&loop_idr_spinlock); mutex_destroy(&lo->lo_mutex); kfree(lo); } @@ -2435,52 +2459,59 @@ static int loop_control_remove(int idx) pr_warn("deleting an unspecified loop device is not supported.\n"); return -EINVAL; } - - ret = mutex_lock_killable(&loop_ctl_mutex); + + /* Serialize concurrent loop_control_remove() and loop_unregister_transfer(). */ + ret = mutex_lock_killable(&loop_removal_mutex); if (ret) return ret; + spin_lock(&loop_idr_spinlock); lo = idr_find(&loop_index_idr, idx); if (!lo) { + spin_unlock(&loop_idr_spinlock); ret = -ENODEV; - goto out_unlock_ctrl; + goto out_unlock; } + spin_unlock(&loop_idr_spinlock); ret = mutex_lock_killable(&lo->lo_mutex); if (ret) - goto out_unlock_ctrl; + goto out_unlock; if (lo->lo_state != Lo_unbound || atomic_read(&lo->lo_refcnt) > 0) { mutex_unlock(&lo->lo_mutex); ret = -EBUSY; - goto out_unlock_ctrl; + goto out_unlock; } lo->lo_state = Lo_deleting; mutex_unlock(&lo->lo_mutex); - idr_remove(&loop_index_idr, lo->lo_number); + /* Hide this loop device. */ + spin_lock(&loop_idr_spinlock); + idr_replace(&loop_index_idr, HIDDEN_LOOP_DEVICE, lo->lo_number); + spin_unlock(&loop_idr_spinlock); loop_remove(lo); -out_unlock_ctrl: - mutex_unlock(&loop_ctl_mutex); +out_unlock: + mutex_unlock(&loop_removal_mutex); return ret; } static int loop_control_get_free(int idx) { struct loop_device *lo; - int id, ret; + int id; - ret = mutex_lock_killable(&loop_ctl_mutex); - if (ret) - return ret; + spin_lock(&loop_idr_spinlock); idr_for_each_entry(&loop_index_idr, lo, id) { + if (lo == HIDDEN_LOOP_DEVICE) + continue; if (lo->lo_state == Lo_unbound) goto found; } - mutex_unlock(&loop_ctl_mutex); + spin_unlock(&loop_idr_spinlock); return loop_add(-1); found: - mutex_unlock(&loop_ctl_mutex); + spin_unlock(&loop_idr_spinlock); return id; } @@ -2590,10 +2621,16 @@ static void __exit loop_exit(void) unregister_blkdev(LOOP_MAJOR, "loop"); misc_deregister(&loop_misc); - mutex_lock(&loop_ctl_mutex); + mutex_lock(&loop_removal_mutex); + /* + * There can't be hidden loop device on loop_index_idr, for loop_add() can't be + * in progress when this module is unloading. Also, there is no need to use + * loop_idr_spinlock here, for nobody else can access loop_index_idr when + * this module is unloading. + */ idr_for_each_entry(&loop_index_idr, lo, id) loop_remove(lo); - mutex_unlock(&loop_ctl_mutex); + mutex_unlock(&loop_removal_mutex); idr_destroy(&loop_index_idr); }