drbd: Removing drbd_cfg_rwsem

 * Updates to all configuration items is done under genl_lock().
   Including removal of mdevs or tconns.
 * All read non sleeping read sides are protected by rcu
 * All sleeping read sides keep reference counts to keep the
   objects alive

Signed-off-by: Philipp Reisner <philipp.reisner@linbit.com>
Signed-off-by: Lars Ellenberg <lars.ellenberg@linbit.com>
diff --git a/drivers/block/drbd/drbd_nl.c b/drivers/block/drbd/drbd_nl.c
index a0cf000..72ce3b0 100644
--- a/drivers/block/drbd/drbd_nl.c
+++ b/drivers/block/drbd/drbd_nl.c
@@ -335,10 +335,15 @@
 	struct drbd_conf *mdev;
 	int vnr;
 
-	down_read(&drbd_cfg_rwsem);
-	idr_for_each_entry(&tconn->volumes, mdev, vnr)
+	rcu_read_lock();
+	idr_for_each_entry(&tconn->volumes, mdev, vnr) {
+		kref_get(&mdev->kref);
+		rcu_read_unlock();
 		drbd_md_sync(mdev);
-	up_read(&drbd_cfg_rwsem);
+		kref_put(&mdev->kref, &drbd_minor_destroy);
+		rcu_read_lock();
+	}
+	rcu_read_unlock();
 }
 
 int conn_khelper(struct drbd_tconn *tconn, char *cmd)
@@ -1193,12 +1198,12 @@
 		rcu_assign_pointer(mdev->rs_plan_s, new_plan);
 	}
 
+	mutex_unlock(&mdev->tconn->conf_update);
 	drbd_md_sync(mdev);
 
 	if (mdev->state.conn >= C_CONNECTED)
 		drbd_send_sync_param(mdev);
 
-	mutex_unlock(&mdev->tconn->conf_update);
 	synchronize_rcu();
 	kfree(old_disk_conf);
 	kfree(old_plan);
@@ -2013,7 +2018,7 @@
 	new_my_addr = (struct sockaddr *)&new_conf->my_addr;
 	new_peer_addr = (struct sockaddr *)&new_conf->peer_addr;
 
-	/* No need to take drbd_cfg_rwsem here.  All reconfiguration is
+	/* No need for _rcu here. All reconfiguration is
 	 * strictly serialized on genl_lock(). We are protected against
 	 * concurrent reconfiguration/addition/deletion */
 	list_for_each_entry(oconn, &drbd_tconns, all_tconn) {
@@ -2672,7 +2677,7 @@
 	 */
 
 	/* synchronize with conn_create()/conn_destroy() */
-	down_read(&drbd_cfg_rwsem);
+	rcu_read_lock();
 	/* revalidate iterator position */
 	list_for_each_entry_rcu(tmp, &drbd_tconns, all_tconn) {
 		if (pos == NULL) {
@@ -2738,7 +2743,7 @@
         }
 
 out:
-	up_read(&drbd_cfg_rwsem);
+	rcu_read_unlock();
 	/* where to start the next iteration */
         cb->args[0] = (long)pos;
         cb->args[1] = (pos == tconn) ? volume + 1 : 0;
@@ -3018,9 +3023,7 @@
 		goto out;
 	}
 
-	down_write(&drbd_cfg_rwsem);
 	retcode = conn_new_minor(adm_ctx.tconn, dh->minor, adm_ctx.volume);
-	up_write(&drbd_cfg_rwsem);
 out:
 	drbd_adm_finish(info, retcode);
 	return 0;
@@ -3053,9 +3056,7 @@
 	if (retcode != NO_ERROR)
 		goto out;
 
-	down_write(&drbd_cfg_rwsem);
 	retcode = adm_delete_minor(adm_ctx.mdev);
-	up_write(&drbd_cfg_rwsem);
 out:
 	drbd_adm_finish(info, retcode);
 	return 0;
@@ -3078,52 +3079,43 @@
 		goto out;
 	}
 
-	down_read(&drbd_cfg_rwsem);
 	/* demote */
 	idr_for_each_entry(&adm_ctx.tconn->volumes, mdev, i) {
 		retcode = drbd_set_role(mdev, R_SECONDARY, 0);
 		if (retcode < SS_SUCCESS) {
 			drbd_msg_put_info("failed to demote");
-			goto out_unlock;
+			goto out;
 		}
 	}
-	up_read(&drbd_cfg_rwsem);
 
-	/* disconnect; may stop the receiver;
-	 * must not hold the drbd_cfg_rwsem */
 	retcode = conn_try_disconnect(adm_ctx.tconn, 0);
 	if (retcode < SS_SUCCESS) {
 		drbd_msg_put_info("failed to disconnect");
 		goto out;
 	}
 
-	down_read(&drbd_cfg_rwsem);
 	/* detach */
 	idr_for_each_entry(&adm_ctx.tconn->volumes, mdev, i) {
 		retcode = adm_detach(mdev);
 		if (retcode < SS_SUCCESS) {
 			drbd_msg_put_info("failed to detach");
-			goto out_unlock;
+			goto out;
 		}
 	}
-	up_read(&drbd_cfg_rwsem);
 
 	/* If we reach this, all volumes (of this tconn) are Secondary,
 	 * Disconnected, Diskless, aka Unconfigured. Make sure all threads have
-	 * actually stopped, state handling only does drbd_thread_stop_nowait().
-	 * This needs to be done without holding drbd_cfg_rwsem. */
+	 * actually stopped, state handling only does drbd_thread_stop_nowait(). */
 	drbd_thread_stop(&adm_ctx.tconn->worker);
 
 	/* Now, nothing can fail anymore */
 
 	/* delete volumes */
-	down_write(&drbd_cfg_rwsem);
 	idr_for_each_entry(&adm_ctx.tconn->volumes, mdev, i) {
 		retcode = adm_delete_minor(mdev);
 		if (retcode != NO_ERROR) {
 			/* "can not happen" */
 			drbd_msg_put_info("failed to delete volume");
-			up_write(&drbd_cfg_rwsem);
 			goto out;
 		}
 	}
@@ -3140,10 +3132,7 @@
 		retcode = ERR_CONN_IN_USE;
 		drbd_msg_put_info("failed to delete connection");
 	}
-	up_write(&drbd_cfg_rwsem);
 	goto out;
-out_unlock:
-	up_read(&drbd_cfg_rwsem);
 out:
 	drbd_adm_finish(info, retcode);
 	return 0;
@@ -3159,7 +3148,6 @@
 	if (retcode != NO_ERROR)
 		goto out;
 
-	down_write(&drbd_cfg_rwsem);
 	if (conn_lowest_minor(adm_ctx.tconn) < 0) {
 		list_del_rcu(&adm_ctx.tconn->all_tconn);
 		synchronize_rcu();
@@ -3169,7 +3157,6 @@
 	} else {
 		retcode = ERR_CONN_IN_USE;
 	}
-	up_write(&drbd_cfg_rwsem);
 
 	if (retcode == NO_ERROR)
 		drbd_thread_stop(&adm_ctx.tconn->worker);