Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jikos/hid...
[firefly-linux-kernel-4.4.55.git] / drivers / block / drbd / drbd_nl.c
index 526414bc2cab18db5fb290189cccfa8951af4050..1b35c45c92b75d7f01fd3fcda7a861ae099b4c7e 100644 (file)
@@ -34,7 +34,6 @@
 #include "drbd_int.h"
 #include "drbd_protocol.h"
 #include "drbd_req.h"
-#include "drbd_wrappers.h"
 #include <asm/unaligned.h>
 #include <linux/drbd_limits.h>
 #include <linux/kthread.h>
@@ -82,32 +81,6 @@ int drbd_adm_get_status_all(struct sk_buff *skb, struct netlink_callback *cb);
 /* used blkdev_get_by_path, to claim our meta data device(s) */
 static char *drbd_m_holder = "Hands off! this is DRBD's meta data device.";
 
-/* Configuration is strictly serialized, because generic netlink message
- * processing is strictly serialized by the genl_lock().
- * Which means we can use one static global drbd_config_context struct.
- */
-static struct drbd_config_context {
-       /* assigned from drbd_genlmsghdr */
-       unsigned int minor;
-       /* assigned from request attributes, if present */
-       unsigned int volume;
-#define VOLUME_UNSPECIFIED             (-1U)
-       /* pointer into the request skb,
-        * limited lifetime! */
-       char *resource_name;
-       struct nlattr *my_addr;
-       struct nlattr *peer_addr;
-
-       /* reply buffer */
-       struct sk_buff *reply_skb;
-       /* pointer into reply buffer */
-       struct drbd_genlmsghdr *reply_dh;
-       /* resolved from attributes, if possible */
-       struct drbd_device *device;
-       struct drbd_resource *resource;
-       struct drbd_connection *connection;
-} adm_ctx;
-
 static void drbd_adm_send_reply(struct sk_buff *skb, struct genl_info *info)
 {
        genlmsg_end(skb, genlmsg_data(nlmsg_data(nlmsg_hdr(skb))));
@@ -117,9 +90,8 @@ static void drbd_adm_send_reply(struct sk_buff *skb, struct genl_info *info)
 
 /* Used on a fresh "drbd_adm_prepare"d reply_skb, this cannot fail: The only
  * reason it could fail was no space in skb, and there are 4k available. */
-int drbd_msg_put_info(const char *info)
+int drbd_msg_put_info(struct sk_buff *skb, const char *info)
 {
-       struct sk_buff *skb = adm_ctx.reply_skb;
        struct nlattr *nla;
        int err = -EMSGSIZE;
 
@@ -143,42 +115,46 @@ int drbd_msg_put_info(const char *info)
  * and per-family private info->pointers.
  * But we need to stay compatible with older kernels.
  * If it returns successfully, adm_ctx members are valid.
+ *
+ * At this point, we still rely on the global genl_lock().
+ * If we want to avoid that, and allow "genl_family.parallel_ops", we may need
+ * to add additional synchronization against object destruction/modification.
  */
 #define DRBD_ADM_NEED_MINOR    1
 #define DRBD_ADM_NEED_RESOURCE 2
 #define DRBD_ADM_NEED_CONNECTION 4
-static int drbd_adm_prepare(struct sk_buff *skb, struct genl_info *info,
-               unsigned flags)
+static int drbd_adm_prepare(struct drbd_config_context *adm_ctx,
+       struct sk_buff *skb, struct genl_info *info, unsigned flags)
 {
        struct drbd_genlmsghdr *d_in = info->userhdr;
        const u8 cmd = info->genlhdr->cmd;
        int err;
 
-       memset(&adm_ctx, 0, sizeof(adm_ctx));
+       memset(adm_ctx, 0, sizeof(*adm_ctx));
 
        /* genl_rcv_msg only checks for CAP_NET_ADMIN on "GENL_ADMIN_PERM" :( */
        if (cmd != DRBD_ADM_GET_STATUS && !capable(CAP_NET_ADMIN))
               return -EPERM;
 
-       adm_ctx.reply_skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
-       if (!adm_ctx.reply_skb) {
+       adm_ctx->reply_skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+       if (!adm_ctx->reply_skb) {
                err = -ENOMEM;
                goto fail;
        }
 
-       adm_ctx.reply_dh = genlmsg_put_reply(adm_ctx.reply_skb,
+       adm_ctx->reply_dh = genlmsg_put_reply(adm_ctx->reply_skb,
                                        info, &drbd_genl_family, 0, cmd);
        /* put of a few bytes into a fresh skb of >= 4k will always succeed.
         * but anyways */
-       if (!adm_ctx.reply_dh) {
+       if (!adm_ctx->reply_dh) {
                err = -ENOMEM;
                goto fail;
        }
 
-       adm_ctx.reply_dh->minor = d_in->minor;
-       adm_ctx.reply_dh->ret_code = NO_ERROR;
+       adm_ctx->reply_dh->minor = d_in->minor;
+       adm_ctx->reply_dh->ret_code = NO_ERROR;
 
-       adm_ctx.volume = VOLUME_UNSPECIFIED;
+       adm_ctx->volume = VOLUME_UNSPECIFIED;
        if (info->attrs[DRBD_NLA_CFG_CONTEXT]) {
                struct nlattr *nla;
                /* parse and validate only */
@@ -188,111 +164,131 @@ static int drbd_adm_prepare(struct sk_buff *skb, struct genl_info *info,
 
                /* It was present, and valid,
                 * copy it over to the reply skb. */
-               err = nla_put_nohdr(adm_ctx.reply_skb,
+               err = nla_put_nohdr(adm_ctx->reply_skb,
                                info->attrs[DRBD_NLA_CFG_CONTEXT]->nla_len,
                                info->attrs[DRBD_NLA_CFG_CONTEXT]);
                if (err)
                        goto fail;
 
-               /* and assign stuff to the global adm_ctx */
+               /* and assign stuff to the adm_ctx */
                nla = nested_attr_tb[__nla_type(T_ctx_volume)];
                if (nla)
-                       adm_ctx.volume = nla_get_u32(nla);
+                       adm_ctx->volume = nla_get_u32(nla);
                nla = nested_attr_tb[__nla_type(T_ctx_resource_name)];
                if (nla)
-                       adm_ctx.resource_name = nla_data(nla);
-               adm_ctx.my_addr = nested_attr_tb[__nla_type(T_ctx_my_addr)];
-               adm_ctx.peer_addr = nested_attr_tb[__nla_type(T_ctx_peer_addr)];
-               if ((adm_ctx.my_addr &&
-                    nla_len(adm_ctx.my_addr) > sizeof(adm_ctx.connection->my_addr)) ||
-                   (adm_ctx.peer_addr &&
-                    nla_len(adm_ctx.peer_addr) > sizeof(adm_ctx.connection->peer_addr))) {
+                       adm_ctx->resource_name = nla_data(nla);
+               adm_ctx->my_addr = nested_attr_tb[__nla_type(T_ctx_my_addr)];
+               adm_ctx->peer_addr = nested_attr_tb[__nla_type(T_ctx_peer_addr)];
+               if ((adm_ctx->my_addr &&
+                    nla_len(adm_ctx->my_addr) > sizeof(adm_ctx->connection->my_addr)) ||
+                   (adm_ctx->peer_addr &&
+                    nla_len(adm_ctx->peer_addr) > sizeof(adm_ctx->connection->peer_addr))) {
                        err = -EINVAL;
                        goto fail;
                }
        }
 
-       adm_ctx.minor = d_in->minor;
-       adm_ctx.device = minor_to_device(d_in->minor);
-       if (adm_ctx.resource_name) {
-               adm_ctx.resource = drbd_find_resource(adm_ctx.resource_name);
+       adm_ctx->minor = d_in->minor;
+       adm_ctx->device = minor_to_device(d_in->minor);
+
+       /* We are protected by the global genl_lock().
+        * But we may explicitly drop it/retake it in drbd_adm_set_role(),
+        * so make sure this object stays around. */
+       if (adm_ctx->device)
+               kref_get(&adm_ctx->device->kref);
+
+       if (adm_ctx->resource_name) {
+               adm_ctx->resource = drbd_find_resource(adm_ctx->resource_name);
        }
 
-       if (!adm_ctx.device && (flags & DRBD_ADM_NEED_MINOR)) {
-               drbd_msg_put_info("unknown minor");
+       if (!adm_ctx->device && (flags & DRBD_ADM_NEED_MINOR)) {
+               drbd_msg_put_info(adm_ctx->reply_skb, "unknown minor");
                return ERR_MINOR_INVALID;
        }
-       if (!adm_ctx.resource && (flags & DRBD_ADM_NEED_RESOURCE)) {
-               drbd_msg_put_info("unknown resource");
-               if (adm_ctx.resource_name)
+       if (!adm_ctx->resource && (flags & DRBD_ADM_NEED_RESOURCE)) {
+               drbd_msg_put_info(adm_ctx->reply_skb, "unknown resource");
+               if (adm_ctx->resource_name)
                        return ERR_RES_NOT_KNOWN;
                return ERR_INVALID_REQUEST;
        }
 
        if (flags & DRBD_ADM_NEED_CONNECTION) {
-               if (adm_ctx.resource) {
-                       drbd_msg_put_info("no resource name expected");
+               if (adm_ctx->resource) {
+                       drbd_msg_put_info(adm_ctx->reply_skb, "no resource name expected");
                        return ERR_INVALID_REQUEST;
                }
-               if (adm_ctx.device) {
-                       drbd_msg_put_info("no minor number expected");
+               if (adm_ctx->device) {
+                       drbd_msg_put_info(adm_ctx->reply_skb, "no minor number expected");
                        return ERR_INVALID_REQUEST;
                }
-               if (adm_ctx.my_addr && adm_ctx.peer_addr)
-                       adm_ctx.connection = conn_get_by_addrs(nla_data(adm_ctx.my_addr),
-                                                         nla_len(adm_ctx.my_addr),
-                                                         nla_data(adm_ctx.peer_addr),
-                                                         nla_len(adm_ctx.peer_addr));
-               if (!adm_ctx.connection) {
-                       drbd_msg_put_info("unknown connection");
+               if (adm_ctx->my_addr && adm_ctx->peer_addr)
+                       adm_ctx->connection = conn_get_by_addrs(nla_data(adm_ctx->my_addr),
+                                                         nla_len(adm_ctx->my_addr),
+                                                         nla_data(adm_ctx->peer_addr),
+                                                         nla_len(adm_ctx->peer_addr));
+               if (!adm_ctx->connection) {
+                       drbd_msg_put_info(adm_ctx->reply_skb, "unknown connection");
                        return ERR_INVALID_REQUEST;
                }
        }
 
        /* some more paranoia, if the request was over-determined */
-       if (adm_ctx.device && adm_ctx.resource &&
-           adm_ctx.device->resource != adm_ctx.resource) {
+       if (adm_ctx->device && adm_ctx->resource &&
+           adm_ctx->device->resource != adm_ctx->resource) {
                pr_warning("request: minor=%u, resource=%s; but that minor belongs to resource %s\n",
-                               adm_ctx.minor, adm_ctx.resource->name,
-                               adm_ctx.device->resource->name);
-               drbd_msg_put_info("minor exists in different resource");
+                               adm_ctx->minor, adm_ctx->resource->name,
+                               adm_ctx->device->resource->name);
+               drbd_msg_put_info(adm_ctx->reply_skb, "minor exists in different resource");
                return ERR_INVALID_REQUEST;
        }
-       if (adm_ctx.device &&
-           adm_ctx.volume != VOLUME_UNSPECIFIED &&
-           adm_ctx.volume != adm_ctx.device->vnr) {
+       if (adm_ctx->device &&
+           adm_ctx->volume != VOLUME_UNSPECIFIED &&
+           adm_ctx->volume != adm_ctx->device->vnr) {
                pr_warning("request: minor=%u, volume=%u; but that minor is volume %u in %s\n",
-                               adm_ctx.minor, adm_ctx.volume,
-                               adm_ctx.device->vnr,
-                               adm_ctx.device->resource->name);
-               drbd_msg_put_info("minor exists as different volume");
+                               adm_ctx->minor, adm_ctx->volume,
+                               adm_ctx->device->vnr,
+                               adm_ctx->device->resource->name);
+               drbd_msg_put_info(adm_ctx->reply_skb, "minor exists as different volume");
                return ERR_INVALID_REQUEST;
        }
 
+       /* still, provide adm_ctx->resource always, if possible. */
+       if (!adm_ctx->resource) {
+               adm_ctx->resource = adm_ctx->device ? adm_ctx->device->resource
+                       : adm_ctx->connection ? adm_ctx->connection->resource : NULL;
+               if (adm_ctx->resource)
+                       kref_get(&adm_ctx->resource->kref);
+       }
+
        return NO_ERROR;
 
 fail:
-       nlmsg_free(adm_ctx.reply_skb);
-       adm_ctx.reply_skb = NULL;
+       nlmsg_free(adm_ctx->reply_skb);
+       adm_ctx->reply_skb = NULL;
        return err;
 }
 
-static int drbd_adm_finish(struct genl_info *info, int retcode)
+static int drbd_adm_finish(struct drbd_config_context *adm_ctx,
+       struct genl_info *info, int retcode)
 {
-       if (adm_ctx.connection) {
-               kref_put(&adm_ctx.connection->kref, drbd_destroy_connection);
-               adm_ctx.connection = NULL;
+       if (adm_ctx->device) {
+               kref_put(&adm_ctx->device->kref, drbd_destroy_device);
+               adm_ctx->device = NULL;
        }
-       if (adm_ctx.resource) {
-               kref_put(&adm_ctx.resource->kref, drbd_destroy_resource);
-               adm_ctx.resource = NULL;
+       if (adm_ctx->connection) {
+               kref_put(&adm_ctx->connection->kref, &drbd_destroy_connection);
+               adm_ctx->connection = NULL;
+       }
+       if (adm_ctx->resource) {
+               kref_put(&adm_ctx->resource->kref, drbd_destroy_resource);
+               adm_ctx->resource = NULL;
        }
 
-       if (!adm_ctx.reply_skb)
+       if (!adm_ctx->reply_skb)
                return -ENOMEM;
 
-       adm_ctx.reply_dh->ret_code = retcode;
-       drbd_adm_send_reply(adm_ctx.reply_skb, info);
+       adm_ctx->reply_dh->ret_code = retcode;
+       drbd_adm_send_reply(adm_ctx->reply_skb, info);
        return 0;
 }
 
@@ -426,6 +422,14 @@ static enum drbd_fencing_p highest_fencing_policy(struct drbd_connection *connec
        }
        rcu_read_unlock();
 
+       if (fp == FP_NOT_AVAIL) {
+               /* IO Suspending works on the whole resource.
+                  Do it only for one device. */
+               vnr = 0;
+               peer_device = idr_get_next(&connection->peer_devices, &vnr);
+               drbd_change_state(peer_device->device, CS_VERBOSE | CS_HARD, NS(susp_fen, 0));
+       }
+
        return fp;
 }
 
@@ -438,12 +442,13 @@ bool conn_try_outdate_peer(struct drbd_connection *connection)
        char *ex_to_string;
        int r;
 
+       spin_lock_irq(&connection->resource->req_lock);
        if (connection->cstate >= C_WF_REPORT_PARAMS) {
                drbd_err(connection, "Expected cstate < C_WF_REPORT_PARAMS\n");
+               spin_unlock_irq(&connection->resource->req_lock);
                return false;
        }
 
-       spin_lock_irq(&connection->resource->req_lock);
        connect_cnt = connection->connect_cnt;
        spin_unlock_irq(&connection->resource->req_lock);
 
@@ -654,11 +659,11 @@ drbd_set_role(struct drbd_device *device, enum drbd_role new_role, int force)
                        put_ldev(device);
                }
        } else {
-               mutex_lock(&device->resource->conf_update);
+               /* Called from drbd_adm_set_role only.
+                * We are still holding the conf_update mutex. */
                nc = first_peer_device(device)->connection->net_conf;
                if (nc)
                        nc->discard_my_data = 0; /* without copy; single bit op is atomic */
-               mutex_unlock(&device->resource->conf_update);
 
                set_disk_ro(device->vdisk, false);
                if (get_ldev(device)) {
@@ -700,11 +705,12 @@ static const char *from_attrs_err_to_txt(int err)
 
 int drbd_adm_set_role(struct sk_buff *skb, struct genl_info *info)
 {
+       struct drbd_config_context adm_ctx;
        struct set_role_parms parms;
        int err;
        enum drbd_ret_code retcode;
 
-       retcode = drbd_adm_prepare(skb, info, DRBD_ADM_NEED_MINOR);
+       retcode = drbd_adm_prepare(&adm_ctx, skb, info, DRBD_ADM_NEED_MINOR);
        if (!adm_ctx.reply_skb)
                return retcode;
        if (retcode != NO_ERROR)
@@ -715,17 +721,22 @@ int drbd_adm_set_role(struct sk_buff *skb, struct genl_info *info)
                err = set_role_parms_from_attrs(&parms, info);
                if (err) {
                        retcode = ERR_MANDATORY_TAG;
-                       drbd_msg_put_info(from_attrs_err_to_txt(err));
+                       drbd_msg_put_info(adm_ctx.reply_skb, from_attrs_err_to_txt(err));
                        goto out;
                }
        }
+       genl_unlock();
+       mutex_lock(&adm_ctx.resource->adm_mutex);
 
        if (info->genlhdr->cmd == DRBD_ADM_PRIMARY)
                retcode = drbd_set_role(adm_ctx.device, R_PRIMARY, parms.assume_uptodate);
        else
                retcode = drbd_set_role(adm_ctx.device, R_SECONDARY, 0);
+
+       mutex_unlock(&adm_ctx.resource->adm_mutex);
+       genl_lock();
 out:
-       drbd_adm_finish(info, retcode);
+       drbd_adm_finish(&adm_ctx, info, retcode);
        return 0;
 }
 
@@ -1104,15 +1115,18 @@ static void drbd_setup_queue_param(struct drbd_device *device, unsigned int max_
        struct request_queue * const q = device->rq_queue;
        unsigned int max_hw_sectors = max_bio_size >> 9;
        unsigned int max_segments = 0;
+       struct request_queue *b = NULL;
 
        if (get_ldev_if_state(device, D_ATTACHING)) {
-               struct request_queue * const b = device->ldev->backing_bdev->bd_disk->queue;
+               b = device->ldev->backing_bdev->bd_disk->queue;
 
                max_hw_sectors = min(queue_max_hw_sectors(b), max_bio_size >> 9);
                rcu_read_lock();
                max_segments = rcu_dereference(device->ldev->disk_conf)->max_bio_bvecs;
                rcu_read_unlock();
-               put_ldev(device);
+
+               blk_set_stacking_limits(&q->limits);
+               blk_queue_max_write_same_sectors(q, 0);
        }
 
        blk_queue_logical_block_size(q, 512);
@@ -1121,8 +1135,25 @@ static void drbd_setup_queue_param(struct drbd_device *device, unsigned int max_
        blk_queue_max_segments(q, max_segments ? max_segments : BLK_MAX_SEGMENTS);
        blk_queue_segment_boundary(q, PAGE_CACHE_SIZE-1);
 
-       if (get_ldev_if_state(device, D_ATTACHING)) {
-               struct request_queue * const b = device->ldev->backing_bdev->bd_disk->queue;
+       if (b) {
+               struct drbd_connection *connection = first_peer_device(device)->connection;
+
+               if (blk_queue_discard(b) &&
+                   (connection->cstate < C_CONNECTED || connection->agreed_features & FF_TRIM)) {
+                       /* For now, don't allow more than one activity log extent worth of data
+                        * to be discarded in one go. We may need to rework drbd_al_begin_io()
+                        * to allow for even larger discard ranges */
+                       q->limits.max_discard_sectors = DRBD_MAX_DISCARD_SECTORS;
+
+                       queue_flag_set_unlocked(QUEUE_FLAG_DISCARD, q);
+                       /* REALLY? Is stacking secdiscard "legal"? */
+                       if (blk_queue_secdiscard(b))
+                               queue_flag_set_unlocked(QUEUE_FLAG_SECDISCARD, q);
+               } else {
+                       q->limits.max_discard_sectors = 0;
+                       queue_flag_clear_unlocked(QUEUE_FLAG_DISCARD, q);
+                       queue_flag_clear_unlocked(QUEUE_FLAG_SECDISCARD, q);
+               }
 
                blk_queue_stack_limits(q, b);
 
@@ -1164,8 +1195,14 @@ void drbd_reconsider_max_bio_size(struct drbd_device *device)
                        peer = DRBD_MAX_BIO_SIZE_P95;  /* drbd 8.3.8 onwards, before 8.4.0 */
                else
                        peer = DRBD_MAX_BIO_SIZE;
-       }
 
+               /* We may later detach and re-attach on a disconnected Primary.
+                * Avoid this setting to jump back in that case.
+                * We want to store what we know the peer DRBD can handle,
+                * not what the peer IO backend can handle. */
+               if (peer > device->peer_max_bio_size)
+                       device->peer_max_bio_size = peer;
+       }
        new = min(local, peer);
 
        if (device->state.role == R_PRIMARY && new < now)
@@ -1258,19 +1295,21 @@ static unsigned int drbd_al_extents_max(struct drbd_backing_dev *bdev)
 
 int drbd_adm_disk_opts(struct sk_buff *skb, struct genl_info *info)
 {
+       struct drbd_config_context adm_ctx;
        enum drbd_ret_code retcode;
        struct drbd_device *device;
        struct disk_conf *new_disk_conf, *old_disk_conf;
        struct fifo_buffer *old_plan = NULL, *new_plan = NULL;
        int err, fifo_size;
 
-       retcode = drbd_adm_prepare(skb, info, DRBD_ADM_NEED_MINOR);
+       retcode = drbd_adm_prepare(&adm_ctx, skb, info, DRBD_ADM_NEED_MINOR);
        if (!adm_ctx.reply_skb)
                return retcode;
        if (retcode != NO_ERROR)
-               goto out;
+               goto finish;
 
        device = adm_ctx.device;
+       mutex_lock(&adm_ctx.resource->adm_mutex);
 
        /* we also need a disk
         * to change the options on */
@@ -1294,7 +1333,7 @@ int drbd_adm_disk_opts(struct sk_buff *skb, struct genl_info *info)
        err = disk_conf_from_attrs_for_change(new_disk_conf, info);
        if (err && err != -ENOMSG) {
                retcode = ERR_MANDATORY_TAG;
-               drbd_msg_put_info(from_attrs_err_to_txt(err));
+               drbd_msg_put_info(adm_ctx.reply_skb, from_attrs_err_to_txt(err));
                goto fail_unlock;
        }
 
@@ -1385,12 +1424,15 @@ fail_unlock:
 success:
        put_ldev(device);
  out:
-       drbd_adm_finish(info, retcode);
+       mutex_unlock(&adm_ctx.resource->adm_mutex);
+ finish:
+       drbd_adm_finish(&adm_ctx, info, retcode);
        return 0;
 }
 
 int drbd_adm_attach(struct sk_buff *skb, struct genl_info *info)
 {
+       struct drbd_config_context adm_ctx;
        struct drbd_device *device;
        int err;
        enum drbd_ret_code retcode;
@@ -1406,13 +1448,14 @@ int drbd_adm_attach(struct sk_buff *skb, struct genl_info *info)
        enum drbd_state_rv rv;
        struct net_conf *nc;
 
-       retcode = drbd_adm_prepare(skb, info, DRBD_ADM_NEED_MINOR);
+       retcode = drbd_adm_prepare(&adm_ctx, skb, info, DRBD_ADM_NEED_MINOR);
        if (!adm_ctx.reply_skb)
                return retcode;
        if (retcode != NO_ERROR)
                goto finish;
 
        device = adm_ctx.device;
+       mutex_lock(&adm_ctx.resource->adm_mutex);
        conn_reconfig_start(first_peer_device(device)->connection);
 
        /* if you want to reconfigure, please tear down first */
@@ -1455,7 +1498,7 @@ int drbd_adm_attach(struct sk_buff *skb, struct genl_info *info)
        err = disk_conf_from_attrs(new_disk_conf, info);
        if (err) {
                retcode = ERR_MANDATORY_TAG;
-               drbd_msg_put_info(from_attrs_err_to_txt(err));
+               drbd_msg_put_info(adm_ctx.reply_skb, from_attrs_err_to_txt(err));
                goto fail;
        }
 
@@ -1619,7 +1662,7 @@ int drbd_adm_attach(struct sk_buff *skb, struct genl_info *info)
        }
 
        if (device->state.conn < C_CONNECTED &&
-           device->state.role == R_PRIMARY &&
+           device->state.role == R_PRIMARY && device->ed_uuid &&
            (device->ed_uuid & ~((u64)1)) != (nbc->md.uuid[UI_CURRENT] & ~((u64)1))) {
                drbd_err(device, "Can only attach to data with current UUID=%016llX\n",
                    (unsigned long long)device->ed_uuid);
@@ -1797,7 +1840,8 @@ int drbd_adm_attach(struct sk_buff *skb, struct genl_info *info)
        kobject_uevent(&disk_to_dev(device->vdisk)->kobj, KOBJ_CHANGE);
        put_ldev(device);
        conn_reconfig_done(first_peer_device(device)->connection);
-       drbd_adm_finish(info, retcode);
+       mutex_unlock(&adm_ctx.resource->adm_mutex);
+       drbd_adm_finish(&adm_ctx, info, retcode);
        return 0;
 
  force_diskless_dec:
@@ -1819,9 +1863,9 @@ int drbd_adm_attach(struct sk_buff *skb, struct genl_info *info)
        kfree(new_disk_conf);
        lc_destroy(resync_lru);
        kfree(new_plan);
-
+       mutex_unlock(&adm_ctx.resource->adm_mutex);
  finish:
-       drbd_adm_finish(info, retcode);
+       drbd_adm_finish(&adm_ctx, info, retcode);
        return 0;
 }
 
@@ -1860,11 +1904,12 @@ out:
  * Only then we have finally detached. */
 int drbd_adm_detach(struct sk_buff *skb, struct genl_info *info)
 {
+       struct drbd_config_context adm_ctx;
        enum drbd_ret_code retcode;
        struct detach_parms parms = { };
        int err;
 
-       retcode = drbd_adm_prepare(skb, info, DRBD_ADM_NEED_MINOR);
+       retcode = drbd_adm_prepare(&adm_ctx, skb, info, DRBD_ADM_NEED_MINOR);
        if (!adm_ctx.reply_skb)
                return retcode;
        if (retcode != NO_ERROR)
@@ -1874,14 +1919,16 @@ int drbd_adm_detach(struct sk_buff *skb, struct genl_info *info)
                err = detach_parms_from_attrs(&parms, info);
                if (err) {
                        retcode = ERR_MANDATORY_TAG;
-                       drbd_msg_put_info(from_attrs_err_to_txt(err));
+                       drbd_msg_put_info(adm_ctx.reply_skb, from_attrs_err_to_txt(err));
                        goto out;
                }
        }
 
+       mutex_lock(&adm_ctx.resource->adm_mutex);
        retcode = adm_detach(adm_ctx.device, parms.force_detach);
+       mutex_unlock(&adm_ctx.resource->adm_mutex);
 out:
-       drbd_adm_finish(info, retcode);
+       drbd_adm_finish(&adm_ctx, info, retcode);
        return 0;
 }
 
@@ -2055,6 +2102,7 @@ static void free_crypto(struct crypto *crypto)
 
 int drbd_adm_net_opts(struct sk_buff *skb, struct genl_info *info)
 {
+       struct drbd_config_context adm_ctx;
        enum drbd_ret_code retcode;
        struct drbd_connection *connection;
        struct net_conf *old_net_conf, *new_net_conf = NULL;
@@ -2063,13 +2111,14 @@ int drbd_adm_net_opts(struct sk_buff *skb, struct genl_info *info)
        int rsr; /* re-sync running */
        struct crypto crypto = { };
 
-       retcode = drbd_adm_prepare(skb, info, DRBD_ADM_NEED_CONNECTION);
+       retcode = drbd_adm_prepare(&adm_ctx, skb, info, DRBD_ADM_NEED_CONNECTION);
        if (!adm_ctx.reply_skb)
                return retcode;
        if (retcode != NO_ERROR)
-               goto out;
+               goto finish;
 
        connection = adm_ctx.connection;
+       mutex_lock(&adm_ctx.resource->adm_mutex);
 
        new_net_conf = kzalloc(sizeof(struct net_conf), GFP_KERNEL);
        if (!new_net_conf) {
@@ -2084,7 +2133,7 @@ int drbd_adm_net_opts(struct sk_buff *skb, struct genl_info *info)
        old_net_conf = connection->net_conf;
 
        if (!old_net_conf) {
-               drbd_msg_put_info("net conf missing, try connect");
+               drbd_msg_put_info(adm_ctx.reply_skb, "net conf missing, try connect");
                retcode = ERR_INVALID_REQUEST;
                goto fail;
        }
@@ -2096,7 +2145,7 @@ int drbd_adm_net_opts(struct sk_buff *skb, struct genl_info *info)
        err = net_conf_from_attrs_for_change(new_net_conf, info);
        if (err && err != -ENOMSG) {
                retcode = ERR_MANDATORY_TAG;
-               drbd_msg_put_info(from_attrs_err_to_txt(err));
+               drbd_msg_put_info(adm_ctx.reply_skb, from_attrs_err_to_txt(err));
                goto fail;
        }
 
@@ -2167,12 +2216,15 @@ int drbd_adm_net_opts(struct sk_buff *skb, struct genl_info *info)
  done:
        conn_reconfig_done(connection);
  out:
-       drbd_adm_finish(info, retcode);
+       mutex_unlock(&adm_ctx.resource->adm_mutex);
+ finish:
+       drbd_adm_finish(&adm_ctx, info, retcode);
        return 0;
 }
 
 int drbd_adm_connect(struct sk_buff *skb, struct genl_info *info)
 {
+       struct drbd_config_context adm_ctx;
        struct drbd_peer_device *peer_device;
        struct net_conf *old_net_conf, *new_net_conf = NULL;
        struct crypto crypto = { };
@@ -2182,14 +2234,14 @@ int drbd_adm_connect(struct sk_buff *skb, struct genl_info *info)
        int i;
        int err;
 
-       retcode = drbd_adm_prepare(skb, info, DRBD_ADM_NEED_RESOURCE);
+       retcode = drbd_adm_prepare(&adm_ctx, skb, info, DRBD_ADM_NEED_RESOURCE);
 
        if (!adm_ctx.reply_skb)
                return retcode;
        if (retcode != NO_ERROR)
                goto out;
        if (!(adm_ctx.my_addr && adm_ctx.peer_addr)) {
-               drbd_msg_put_info("connection endpoint(s) missing");
+               drbd_msg_put_info(adm_ctx.reply_skb, "connection endpoint(s) missing");
                retcode = ERR_INVALID_REQUEST;
                goto out;
        }
@@ -2215,6 +2267,7 @@ int drbd_adm_connect(struct sk_buff *skb, struct genl_info *info)
                }
        }
 
+       mutex_lock(&adm_ctx.resource->adm_mutex);
        connection = first_connection(adm_ctx.resource);
        conn_reconfig_start(connection);
 
@@ -2235,7 +2288,7 @@ int drbd_adm_connect(struct sk_buff *skb, struct genl_info *info)
        err = net_conf_from_attrs(new_net_conf, info);
        if (err && err != -ENOMSG) {
                retcode = ERR_MANDATORY_TAG;
-               drbd_msg_put_info(from_attrs_err_to_txt(err));
+               drbd_msg_put_info(adm_ctx.reply_skb, from_attrs_err_to_txt(err));
                goto fail;
        }
 
@@ -2284,7 +2337,8 @@ int drbd_adm_connect(struct sk_buff *skb, struct genl_info *info)
        retcode = conn_request_state(connection, NS(conn, C_UNCONNECTED), CS_VERBOSE);
 
        conn_reconfig_done(connection);
-       drbd_adm_finish(info, retcode);
+       mutex_unlock(&adm_ctx.resource->adm_mutex);
+       drbd_adm_finish(&adm_ctx, info, retcode);
        return 0;
 
 fail:
@@ -2292,8 +2346,9 @@ fail:
        kfree(new_net_conf);
 
        conn_reconfig_done(connection);
+       mutex_unlock(&adm_ctx.resource->adm_mutex);
 out:
-       drbd_adm_finish(info, retcode);
+       drbd_adm_finish(&adm_ctx, info, retcode);
        return 0;
 }
 
@@ -2356,13 +2411,14 @@ static enum drbd_state_rv conn_try_disconnect(struct drbd_connection *connection
 
 int drbd_adm_disconnect(struct sk_buff *skb, struct genl_info *info)
 {
+       struct drbd_config_context adm_ctx;
        struct disconnect_parms parms;
        struct drbd_connection *connection;
        enum drbd_state_rv rv;
        enum drbd_ret_code retcode;
        int err;
 
-       retcode = drbd_adm_prepare(skb, info, DRBD_ADM_NEED_CONNECTION);
+       retcode = drbd_adm_prepare(&adm_ctx, skb, info, DRBD_ADM_NEED_CONNECTION);
        if (!adm_ctx.reply_skb)
                return retcode;
        if (retcode != NO_ERROR)
@@ -2374,18 +2430,20 @@ int drbd_adm_disconnect(struct sk_buff *skb, struct genl_info *info)
                err = disconnect_parms_from_attrs(&parms, info);
                if (err) {
                        retcode = ERR_MANDATORY_TAG;
-                       drbd_msg_put_info(from_attrs_err_to_txt(err));
+                       drbd_msg_put_info(adm_ctx.reply_skb, from_attrs_err_to_txt(err));
                        goto fail;
                }
        }
 
+       mutex_lock(&adm_ctx.resource->adm_mutex);
        rv = conn_try_disconnect(connection, parms.force_disconnect);
        if (rv < SS_SUCCESS)
                retcode = rv;  /* FIXME: Type mismatch. */
        else
                retcode = NO_ERROR;
+       mutex_unlock(&adm_ctx.resource->adm_mutex);
  fail:
-       drbd_adm_finish(info, retcode);
+       drbd_adm_finish(&adm_ctx, info, retcode);
        return 0;
 }
 
@@ -2407,6 +2465,7 @@ void resync_after_online_grow(struct drbd_device *device)
 
 int drbd_adm_resize(struct sk_buff *skb, struct genl_info *info)
 {
+       struct drbd_config_context adm_ctx;
        struct disk_conf *old_disk_conf, *new_disk_conf = NULL;
        struct resize_parms rs;
        struct drbd_device *device;
@@ -2417,12 +2476,13 @@ int drbd_adm_resize(struct sk_buff *skb, struct genl_info *info)
        sector_t u_size;
        int err;
 
-       retcode = drbd_adm_prepare(skb, info, DRBD_ADM_NEED_MINOR);
+       retcode = drbd_adm_prepare(&adm_ctx, skb, info, DRBD_ADM_NEED_MINOR);
        if (!adm_ctx.reply_skb)
                return retcode;
        if (retcode != NO_ERROR)
-               goto fail;
+               goto finish;
 
+       mutex_lock(&adm_ctx.resource->adm_mutex);
        device = adm_ctx.device;
        if (!get_ldev(device)) {
                retcode = ERR_NO_DISK;
@@ -2436,7 +2496,7 @@ int drbd_adm_resize(struct sk_buff *skb, struct genl_info *info)
                err = resize_parms_from_attrs(&rs, info);
                if (err) {
                        retcode = ERR_MANDATORY_TAG;
-                       drbd_msg_put_info(from_attrs_err_to_txt(err));
+                       drbd_msg_put_info(adm_ctx.reply_skb, from_attrs_err_to_txt(err));
                        goto fail_ldev;
                }
        }
@@ -2482,7 +2542,7 @@ int drbd_adm_resize(struct sk_buff *skb, struct genl_info *info)
                        goto fail_ldev;
                }
 
-               if (device->state.conn != C_CONNECTED) {
+               if (device->state.conn != C_CONNECTED && !rs.resize_force) {
                        retcode = ERR_MD_LAYOUT_CONNECTED;
                        goto fail_ldev;
                }
@@ -2528,7 +2588,9 @@ int drbd_adm_resize(struct sk_buff *skb, struct genl_info *info)
        }
 
  fail:
-       drbd_adm_finish(info, retcode);
+       mutex_unlock(&adm_ctx.resource->adm_mutex);
+ finish:
+       drbd_adm_finish(&adm_ctx, info, retcode);
        return 0;
 
  fail_ldev:
@@ -2538,11 +2600,12 @@ int drbd_adm_resize(struct sk_buff *skb, struct genl_info *info)
 
 int drbd_adm_resource_opts(struct sk_buff *skb, struct genl_info *info)
 {
+       struct drbd_config_context adm_ctx;
        enum drbd_ret_code retcode;
        struct res_opts res_opts;
        int err;
 
-       retcode = drbd_adm_prepare(skb, info, DRBD_ADM_NEED_RESOURCE);
+       retcode = drbd_adm_prepare(&adm_ctx, skb, info, DRBD_ADM_NEED_RESOURCE);
        if (!adm_ctx.reply_skb)
                return retcode;
        if (retcode != NO_ERROR)
@@ -2555,33 +2618,37 @@ int drbd_adm_resource_opts(struct sk_buff *skb, struct genl_info *info)
        err = res_opts_from_attrs(&res_opts, info);
        if (err && err != -ENOMSG) {
                retcode = ERR_MANDATORY_TAG;
-               drbd_msg_put_info(from_attrs_err_to_txt(err));
+               drbd_msg_put_info(adm_ctx.reply_skb, from_attrs_err_to_txt(err));
                goto fail;
        }
 
+       mutex_lock(&adm_ctx.resource->adm_mutex);
        err = set_resource_options(adm_ctx.resource, &res_opts);
        if (err) {
                retcode = ERR_INVALID_REQUEST;
                if (err == -ENOMEM)
                        retcode = ERR_NOMEM;
        }
+       mutex_unlock(&adm_ctx.resource->adm_mutex);
 
 fail:
-       drbd_adm_finish(info, retcode);
+       drbd_adm_finish(&adm_ctx, info, retcode);
        return 0;
 }
 
 int drbd_adm_invalidate(struct sk_buff *skb, struct genl_info *info)
 {
+       struct drbd_config_context adm_ctx;
        struct drbd_device *device;
        int retcode; /* enum drbd_ret_code rsp. enum drbd_state_rv */
 
-       retcode = drbd_adm_prepare(skb, info, DRBD_ADM_NEED_MINOR);
+       retcode = drbd_adm_prepare(&adm_ctx, skb, info, DRBD_ADM_NEED_MINOR);
        if (!adm_ctx.reply_skb)
                return retcode;
        if (retcode != NO_ERROR)
                goto out;
 
+       mutex_lock(&adm_ctx.resource->adm_mutex);
        device = adm_ctx.device;
 
        /* If there is still bitmap IO pending, probably because of a previous
@@ -2605,26 +2672,29 @@ int drbd_adm_invalidate(struct sk_buff *skb, struct genl_info *info)
        } else
                retcode = drbd_request_state(device, NS(conn, C_STARTING_SYNC_T));
        drbd_resume_io(device);
-
+       mutex_unlock(&adm_ctx.resource->adm_mutex);
 out:
-       drbd_adm_finish(info, retcode);
+       drbd_adm_finish(&adm_ctx, info, retcode);
        return 0;
 }
 
 static int drbd_adm_simple_request_state(struct sk_buff *skb, struct genl_info *info,
                union drbd_state mask, union drbd_state val)
 {
+       struct drbd_config_context adm_ctx;
        enum drbd_ret_code retcode;
 
-       retcode = drbd_adm_prepare(skb, info, DRBD_ADM_NEED_MINOR);
+       retcode = drbd_adm_prepare(&adm_ctx, skb, info, DRBD_ADM_NEED_MINOR);
        if (!adm_ctx.reply_skb)
                return retcode;
        if (retcode != NO_ERROR)
                goto out;
 
+       mutex_lock(&adm_ctx.resource->adm_mutex);
        retcode = drbd_request_state(adm_ctx.device, mask, val);
+       mutex_unlock(&adm_ctx.resource->adm_mutex);
 out:
-       drbd_adm_finish(info, retcode);
+       drbd_adm_finish(&adm_ctx, info, retcode);
        return 0;
 }
 
@@ -2639,15 +2709,17 @@ static int drbd_bmio_set_susp_al(struct drbd_device *device)
 
 int drbd_adm_invalidate_peer(struct sk_buff *skb, struct genl_info *info)
 {
+       struct drbd_config_context adm_ctx;
        int retcode; /* drbd_ret_code, drbd_state_rv */
        struct drbd_device *device;
 
-       retcode = drbd_adm_prepare(skb, info, DRBD_ADM_NEED_MINOR);
+       retcode = drbd_adm_prepare(&adm_ctx, skb, info, DRBD_ADM_NEED_MINOR);
        if (!adm_ctx.reply_skb)
                return retcode;
        if (retcode != NO_ERROR)
                goto out;
 
+       mutex_lock(&adm_ctx.resource->adm_mutex);
        device = adm_ctx.device;
 
        /* If there is still bitmap IO pending, probably because of a previous
@@ -2674,40 +2746,45 @@ int drbd_adm_invalidate_peer(struct sk_buff *skb, struct genl_info *info)
        } else
                retcode = drbd_request_state(device, NS(conn, C_STARTING_SYNC_S));
        drbd_resume_io(device);
-
+       mutex_unlock(&adm_ctx.resource->adm_mutex);
 out:
-       drbd_adm_finish(info, retcode);
+       drbd_adm_finish(&adm_ctx, info, retcode);
        return 0;
 }
 
 int drbd_adm_pause_sync(struct sk_buff *skb, struct genl_info *info)
 {
+       struct drbd_config_context adm_ctx;
        enum drbd_ret_code retcode;
 
-       retcode = drbd_adm_prepare(skb, info, DRBD_ADM_NEED_MINOR);
+       retcode = drbd_adm_prepare(&adm_ctx, skb, info, DRBD_ADM_NEED_MINOR);
        if (!adm_ctx.reply_skb)
                return retcode;
        if (retcode != NO_ERROR)
                goto out;
 
+       mutex_lock(&adm_ctx.resource->adm_mutex);
        if (drbd_request_state(adm_ctx.device, NS(user_isp, 1)) == SS_NOTHING_TO_DO)
                retcode = ERR_PAUSE_IS_SET;
+       mutex_unlock(&adm_ctx.resource->adm_mutex);
 out:
-       drbd_adm_finish(info, retcode);
+       drbd_adm_finish(&adm_ctx, info, retcode);
        return 0;
 }
 
 int drbd_adm_resume_sync(struct sk_buff *skb, struct genl_info *info)
 {
+       struct drbd_config_context adm_ctx;
        union drbd_dev_state s;
        enum drbd_ret_code retcode;
 
-       retcode = drbd_adm_prepare(skb, info, DRBD_ADM_NEED_MINOR);
+       retcode = drbd_adm_prepare(&adm_ctx, skb, info, DRBD_ADM_NEED_MINOR);
        if (!adm_ctx.reply_skb)
                return retcode;
        if (retcode != NO_ERROR)
                goto out;
 
+       mutex_lock(&adm_ctx.resource->adm_mutex);
        if (drbd_request_state(adm_ctx.device, NS(user_isp, 0)) == SS_NOTHING_TO_DO) {
                s = adm_ctx.device->state;
                if (s.conn == C_PAUSED_SYNC_S || s.conn == C_PAUSED_SYNC_T) {
@@ -2717,9 +2794,9 @@ int drbd_adm_resume_sync(struct sk_buff *skb, struct genl_info *info)
                        retcode = ERR_PAUSE_IS_CLEAR;
                }
        }
-
+       mutex_unlock(&adm_ctx.resource->adm_mutex);
 out:
-       drbd_adm_finish(info, retcode);
+       drbd_adm_finish(&adm_ctx, info, retcode);
        return 0;
 }
 
@@ -2730,15 +2807,17 @@ int drbd_adm_suspend_io(struct sk_buff *skb, struct genl_info *info)
 
 int drbd_adm_resume_io(struct sk_buff *skb, struct genl_info *info)
 {
+       struct drbd_config_context adm_ctx;
        struct drbd_device *device;
        int retcode; /* enum drbd_ret_code rsp. enum drbd_state_rv */
 
-       retcode = drbd_adm_prepare(skb, info, DRBD_ADM_NEED_MINOR);
+       retcode = drbd_adm_prepare(&adm_ctx, skb, info, DRBD_ADM_NEED_MINOR);
        if (!adm_ctx.reply_skb)
                return retcode;
        if (retcode != NO_ERROR)
                goto out;
 
+       mutex_lock(&adm_ctx.resource->adm_mutex);
        device = adm_ctx.device;
        if (test_bit(NEW_CUR_UUID, &device->flags)) {
                drbd_uuid_new_current(device);
@@ -2753,9 +2832,9 @@ int drbd_adm_resume_io(struct sk_buff *skb, struct genl_info *info)
                        tl_restart(first_peer_device(device)->connection, FAIL_FROZEN_DISK_IO);
        }
        drbd_resume_io(device);
-
+       mutex_unlock(&adm_ctx.resource->adm_mutex);
 out:
-       drbd_adm_finish(info, retcode);
+       drbd_adm_finish(&adm_ctx, info, retcode);
        return 0;
 }
 
@@ -2931,10 +3010,11 @@ nla_put_failure:
 
 int drbd_adm_get_status(struct sk_buff *skb, struct genl_info *info)
 {
+       struct drbd_config_context adm_ctx;
        enum drbd_ret_code retcode;
        int err;
 
-       retcode = drbd_adm_prepare(skb, info, DRBD_ADM_NEED_MINOR);
+       retcode = drbd_adm_prepare(&adm_ctx, skb, info, DRBD_ADM_NEED_MINOR);
        if (!adm_ctx.reply_skb)
                return retcode;
        if (retcode != NO_ERROR)
@@ -2946,7 +3026,7 @@ int drbd_adm_get_status(struct sk_buff *skb, struct genl_info *info)
                return err;
        }
 out:
-       drbd_adm_finish(info, retcode);
+       drbd_adm_finish(&adm_ctx, info, retcode);
        return 0;
 }
 
@@ -3133,11 +3213,12 @@ dump:
 
 int drbd_adm_get_timeout_type(struct sk_buff *skb, struct genl_info *info)
 {
+       struct drbd_config_context adm_ctx;
        enum drbd_ret_code retcode;
        struct timeout_parms tp;
        int err;
 
-       retcode = drbd_adm_prepare(skb, info, DRBD_ADM_NEED_MINOR);
+       retcode = drbd_adm_prepare(&adm_ctx, skb, info, DRBD_ADM_NEED_MINOR);
        if (!adm_ctx.reply_skb)
                return retcode;
        if (retcode != NO_ERROR)
@@ -3154,17 +3235,18 @@ int drbd_adm_get_timeout_type(struct sk_buff *skb, struct genl_info *info)
                return err;
        }
 out:
-       drbd_adm_finish(info, retcode);
+       drbd_adm_finish(&adm_ctx, info, retcode);
        return 0;
 }
 
 int drbd_adm_start_ov(struct sk_buff *skb, struct genl_info *info)
 {
+       struct drbd_config_context adm_ctx;
        struct drbd_device *device;
        enum drbd_ret_code retcode;
        struct start_ov_parms parms;
 
-       retcode = drbd_adm_prepare(skb, info, DRBD_ADM_NEED_MINOR);
+       retcode = drbd_adm_prepare(&adm_ctx, skb, info, DRBD_ADM_NEED_MINOR);
        if (!adm_ctx.reply_skb)
                return retcode;
        if (retcode != NO_ERROR)
@@ -3179,10 +3261,12 @@ int drbd_adm_start_ov(struct sk_buff *skb, struct genl_info *info)
                int err = start_ov_parms_from_attrs(&parms, info);
                if (err) {
                        retcode = ERR_MANDATORY_TAG;
-                       drbd_msg_put_info(from_attrs_err_to_txt(err));
+                       drbd_msg_put_info(adm_ctx.reply_skb, from_attrs_err_to_txt(err));
                        goto out;
                }
        }
+       mutex_lock(&adm_ctx.resource->adm_mutex);
+
        /* w_make_ov_request expects position to be aligned */
        device->ov_start_sector = parms.ov_start_sector & ~(BM_SECT_PER_BIT-1);
        device->ov_stop_sector = parms.ov_stop_sector;
@@ -3193,21 +3277,24 @@ int drbd_adm_start_ov(struct sk_buff *skb, struct genl_info *info)
        wait_event(device->misc_wait, !test_bit(BITMAP_IO, &device->flags));
        retcode = drbd_request_state(device, NS(conn, C_VERIFY_S));
        drbd_resume_io(device);
+
+       mutex_unlock(&adm_ctx.resource->adm_mutex);
 out:
-       drbd_adm_finish(info, retcode);
+       drbd_adm_finish(&adm_ctx, info, retcode);
        return 0;
 }
 
 
 int drbd_adm_new_c_uuid(struct sk_buff *skb, struct genl_info *info)
 {
+       struct drbd_config_context adm_ctx;
        struct drbd_device *device;
        enum drbd_ret_code retcode;
        int skip_initial_sync = 0;
        int err;
        struct new_c_uuid_parms args;
 
-       retcode = drbd_adm_prepare(skb, info, DRBD_ADM_NEED_MINOR);
+       retcode = drbd_adm_prepare(&adm_ctx, skb, info, DRBD_ADM_NEED_MINOR);
        if (!adm_ctx.reply_skb)
                return retcode;
        if (retcode != NO_ERROR)
@@ -3219,11 +3306,12 @@ int drbd_adm_new_c_uuid(struct sk_buff *skb, struct genl_info *info)
                err = new_c_uuid_parms_from_attrs(&args, info);
                if (err) {
                        retcode = ERR_MANDATORY_TAG;
-                       drbd_msg_put_info(from_attrs_err_to_txt(err));
+                       drbd_msg_put_info(adm_ctx.reply_skb, from_attrs_err_to_txt(err));
                        goto out_nolock;
                }
        }
 
+       mutex_lock(&adm_ctx.resource->adm_mutex);
        mutex_lock(device->state_mutex); /* Protects us against serialized state changes. */
 
        if (!get_ldev(device)) {
@@ -3268,22 +3356,24 @@ out_dec:
        put_ldev(device);
 out:
        mutex_unlock(device->state_mutex);
+       mutex_unlock(&adm_ctx.resource->adm_mutex);
 out_nolock:
-       drbd_adm_finish(info, retcode);
+       drbd_adm_finish(&adm_ctx, info, retcode);
        return 0;
 }
 
 static enum drbd_ret_code
-drbd_check_resource_name(const char *name)
+drbd_check_resource_name(struct drbd_config_context *adm_ctx)
 {
+       const char *name = adm_ctx->resource_name;
        if (!name || !name[0]) {
-               drbd_msg_put_info("resource name missing");
+               drbd_msg_put_info(adm_ctx->reply_skb, "resource name missing");
                return ERR_MANDATORY_TAG;
        }
        /* if we want to use these in sysfs/configfs/debugfs some day,
         * we must not allow slashes */
        if (strchr(name, '/')) {
-               drbd_msg_put_info("invalid resource name");
+               drbd_msg_put_info(adm_ctx->reply_skb, "invalid resource name");
                return ERR_INVALID_REQUEST;
        }
        return NO_ERROR;
@@ -3291,11 +3381,12 @@ drbd_check_resource_name(const char *name)
 
 int drbd_adm_new_resource(struct sk_buff *skb, struct genl_info *info)
 {
+       struct drbd_config_context adm_ctx;
        enum drbd_ret_code retcode;
        struct res_opts res_opts;
        int err;
 
-       retcode = drbd_adm_prepare(skb, info, 0);
+       retcode = drbd_adm_prepare(&adm_ctx, skb, info, 0);
        if (!adm_ctx.reply_skb)
                return retcode;
        if (retcode != NO_ERROR)
@@ -3305,48 +3396,50 @@ int drbd_adm_new_resource(struct sk_buff *skb, struct genl_info *info)
        err = res_opts_from_attrs(&res_opts, info);
        if (err && err != -ENOMSG) {
                retcode = ERR_MANDATORY_TAG;
-               drbd_msg_put_info(from_attrs_err_to_txt(err));
+               drbd_msg_put_info(adm_ctx.reply_skb, from_attrs_err_to_txt(err));
                goto out;
        }
 
-       retcode = drbd_check_resource_name(adm_ctx.resource_name);
+       retcode = drbd_check_resource_name(&adm_ctx);
        if (retcode != NO_ERROR)
                goto out;
 
        if (adm_ctx.resource) {
                if (info->nlhdr->nlmsg_flags & NLM_F_EXCL) {
                        retcode = ERR_INVALID_REQUEST;
-                       drbd_msg_put_info("resource exists");
+                       drbd_msg_put_info(adm_ctx.reply_skb, "resource exists");
                }
                /* else: still NO_ERROR */
                goto out;
        }
 
+       /* not yet safe for genl_family.parallel_ops */
        if (!conn_create(adm_ctx.resource_name, &res_opts))
                retcode = ERR_NOMEM;
 out:
-       drbd_adm_finish(info, retcode);
+       drbd_adm_finish(&adm_ctx, info, retcode);
        return 0;
 }
 
 int drbd_adm_new_minor(struct sk_buff *skb, struct genl_info *info)
 {
+       struct drbd_config_context adm_ctx;
        struct drbd_genlmsghdr *dh = info->userhdr;
        enum drbd_ret_code retcode;
 
-       retcode = drbd_adm_prepare(skb, info, DRBD_ADM_NEED_RESOURCE);
+       retcode = drbd_adm_prepare(&adm_ctx, skb, info, DRBD_ADM_NEED_RESOURCE);
        if (!adm_ctx.reply_skb)
                return retcode;
        if (retcode != NO_ERROR)
                goto out;
 
        if (dh->minor > MINORMASK) {
-               drbd_msg_put_info("requested minor out of range");
+               drbd_msg_put_info(adm_ctx.reply_skb, "requested minor out of range");
                retcode = ERR_INVALID_REQUEST;
                goto out;
        }
        if (adm_ctx.volume > DRBD_VOLUME_MAX) {
-               drbd_msg_put_info("requested volume id out of range");
+               drbd_msg_put_info(adm_ctx.reply_skb, "requested volume id out of range");
                retcode = ERR_INVALID_REQUEST;
                goto out;
        }
@@ -3360,9 +3453,11 @@ int drbd_adm_new_minor(struct sk_buff *skb, struct genl_info *info)
                goto out;
        }
 
-       retcode = drbd_create_device(adm_ctx.resource, dh->minor, adm_ctx.volume);
+       mutex_lock(&adm_ctx.resource->adm_mutex);
+       retcode = drbd_create_device(&adm_ctx, dh->minor);
+       mutex_unlock(&adm_ctx.resource->adm_mutex);
 out:
-       drbd_adm_finish(info, retcode);
+       drbd_adm_finish(&adm_ctx, info, retcode);
        return 0;
 }
 
@@ -3383,35 +3478,40 @@ static enum drbd_ret_code adm_del_minor(struct drbd_device *device)
 
 int drbd_adm_del_minor(struct sk_buff *skb, struct genl_info *info)
 {
+       struct drbd_config_context adm_ctx;
        enum drbd_ret_code retcode;
 
-       retcode = drbd_adm_prepare(skb, info, DRBD_ADM_NEED_MINOR);
+       retcode = drbd_adm_prepare(&adm_ctx, skb, info, DRBD_ADM_NEED_MINOR);
        if (!adm_ctx.reply_skb)
                return retcode;
        if (retcode != NO_ERROR)
                goto out;
 
+       mutex_lock(&adm_ctx.resource->adm_mutex);
        retcode = adm_del_minor(adm_ctx.device);
+       mutex_unlock(&adm_ctx.resource->adm_mutex);
 out:
-       drbd_adm_finish(info, retcode);
+       drbd_adm_finish(&adm_ctx, info, retcode);
        return 0;
 }
 
 int drbd_adm_down(struct sk_buff *skb, struct genl_info *info)
 {
+       struct drbd_config_context adm_ctx;
        struct drbd_resource *resource;
        struct drbd_connection *connection;
        struct drbd_device *device;
        int retcode; /* enum drbd_ret_code rsp. enum drbd_state_rv */
        unsigned i;
 
-       retcode = drbd_adm_prepare(skb, info, DRBD_ADM_NEED_RESOURCE);
+       retcode = drbd_adm_prepare(&adm_ctx, skb, info, DRBD_ADM_NEED_RESOURCE);
        if (!adm_ctx.reply_skb)
                return retcode;
        if (retcode != NO_ERROR)
-               goto out;
+               goto finish;
 
        resource = adm_ctx.resource;
+       mutex_lock(&resource->adm_mutex);
        /* demote */
        for_each_connection(connection, resource) {
                struct drbd_peer_device *peer_device;
@@ -3419,14 +3519,14 @@ int drbd_adm_down(struct sk_buff *skb, struct genl_info *info)
                idr_for_each_entry(&connection->peer_devices, peer_device, i) {
                        retcode = drbd_set_role(peer_device->device, R_SECONDARY, 0);
                        if (retcode < SS_SUCCESS) {
-                               drbd_msg_put_info("failed to demote");
+                               drbd_msg_put_info(adm_ctx.reply_skb, "failed to demote");
                                goto out;
                        }
                }
 
                retcode = conn_try_disconnect(connection, 0);
                if (retcode < SS_SUCCESS) {
-                       drbd_msg_put_info("failed to disconnect");
+                       drbd_msg_put_info(adm_ctx.reply_skb, "failed to disconnect");
                        goto out;
                }
        }
@@ -3435,7 +3535,7 @@ int drbd_adm_down(struct sk_buff *skb, struct genl_info *info)
        idr_for_each_entry(&resource->devices, device, i) {
                retcode = adm_detach(device, 0);
                if (retcode < SS_SUCCESS || retcode > NO_ERROR) {
-                       drbd_msg_put_info("failed to detach");
+                       drbd_msg_put_info(adm_ctx.reply_skb, "failed to detach");
                        goto out;
                }
        }
@@ -3453,7 +3553,7 @@ int drbd_adm_down(struct sk_buff *skb, struct genl_info *info)
                retcode = adm_del_minor(device);
                if (retcode != NO_ERROR) {
                        /* "can not happen" */
-                       drbd_msg_put_info("failed to delete volume");
+                       drbd_msg_put_info(adm_ctx.reply_skb, "failed to delete volume");
                        goto out;
                }
        }
@@ -3462,25 +3562,28 @@ int drbd_adm_down(struct sk_buff *skb, struct genl_info *info)
        synchronize_rcu();
        drbd_free_resource(resource);
        retcode = NO_ERROR;
-
 out:
-       drbd_adm_finish(info, retcode);
+       mutex_unlock(&resource->adm_mutex);
+finish:
+       drbd_adm_finish(&adm_ctx, info, retcode);
        return 0;
 }
 
 int drbd_adm_del_resource(struct sk_buff *skb, struct genl_info *info)
 {
+       struct drbd_config_context adm_ctx;
        struct drbd_resource *resource;
        struct drbd_connection *connection;
        enum drbd_ret_code retcode;
 
-       retcode = drbd_adm_prepare(skb, info, DRBD_ADM_NEED_RESOURCE);
+       retcode = drbd_adm_prepare(&adm_ctx, skb, info, DRBD_ADM_NEED_RESOURCE);
        if (!adm_ctx.reply_skb)
                return retcode;
        if (retcode != NO_ERROR)
-               goto out;
+               goto finish;
 
        resource = adm_ctx.resource;
+       mutex_lock(&resource->adm_mutex);
        for_each_connection(connection, resource) {
                if (connection->cstate > C_STANDALONE) {
                        retcode = ERR_NET_CONFIGURED;
@@ -3499,7 +3602,9 @@ int drbd_adm_del_resource(struct sk_buff *skb, struct genl_info *info)
        drbd_free_resource(resource);
        retcode = NO_ERROR;
 out:
-       drbd_adm_finish(info, retcode);
+       mutex_unlock(&resource->adm_mutex);
+finish:
+       drbd_adm_finish(&adm_ctx, info, retcode);
        return 0;
 }