drbd: mutex_unlock "... must no be used in interrupt context"
authorLars Ellenberg <lars.ellenberg@linbit.com>
Wed, 22 Aug 2012 14:15:26 +0000 (16:15 +0200)
committerPhilipp Reisner <philipp.reisner@linbit.com>
Fri, 9 Nov 2012 13:08:21 +0000 (14:08 +0100)
Documentation of mutex_unlock says
we must not use it in interrupt context.
So do not call it while holding the spin_lock_irq,
but give up the spinlock temporarily.

Signed-off-by: Philipp Reisner <philipp.reisner@linbit.com>
Signed-off-by: Lars Ellenberg <lars.ellenberg@linbit.com>
drivers/block/drbd/drbd_state.c

index ce14951..755425a 100644 (file)
@@ -1721,38 +1721,6 @@ _conn_rq_cond(struct drbd_tconn *tconn, union drbd_state mask, union drbd_state
        return rv;
 }
 
-static enum drbd_state_rv
-conn_cl_wide(struct drbd_tconn *tconn, union drbd_state mask, union drbd_state val,
-            enum chg_state_flags f)
-{
-       enum drbd_state_rv rv;
-
-       spin_unlock_irq(&tconn->req_lock);
-       mutex_lock(&tconn->cstate_mutex);
-
-       set_bit(CONN_WD_ST_CHG_REQ, &tconn->flags);
-       if (conn_send_state_req(tconn, mask, val)) {
-               clear_bit(CONN_WD_ST_CHG_REQ, &tconn->flags);
-               /* if (f & CS_VERBOSE)
-                  print_st_err(mdev, os, ns, rv); */
-               mutex_unlock(&tconn->cstate_mutex);
-               spin_lock_irq(&tconn->req_lock);
-               return SS_CW_FAILED_BY_PEER;
-       }
-
-       if (val.conn == C_DISCONNECTING)
-               set_bit(DISCONNECT_SENT, &tconn->flags);
-
-       spin_lock_irq(&tconn->req_lock);
-
-       wait_event_lock_irq(tconn->ping_wait, (rv = _conn_rq_cond(tconn, mask, val)), tconn->req_lock,);
-       clear_bit(CONN_WD_ST_CHG_REQ, &tconn->flags);
-
-       mutex_unlock(&tconn->cstate_mutex);
-
-       return rv;
-}
-
 enum drbd_state_rv
 _conn_request_state(struct drbd_tconn *tconn, union drbd_state mask, union drbd_state val,
                    enum chg_state_flags flags)
@@ -1761,6 +1729,7 @@ _conn_request_state(struct drbd_tconn *tconn, union drbd_state mask, union drbd_
        struct after_conn_state_chg_work *acscw;
        enum drbd_conns oc = tconn->cstate;
        union drbd_state ns_max, ns_min, os;
+       bool have_mutex = false;
 
        rv = is_valid_conn_transition(oc, val.conn);
        if (rv < SS_SUCCESS)
@@ -1772,7 +1741,35 @@ _conn_request_state(struct drbd_tconn *tconn, union drbd_state mask, union drbd_
 
        if (oc == C_WF_REPORT_PARAMS && val.conn == C_DISCONNECTING &&
            !(flags & (CS_LOCAL_ONLY | CS_HARD))) {
-               rv = conn_cl_wide(tconn, mask, val, flags);
+
+               /* This will be a cluster-wide state change.
+                * Need to give up the spinlock, grab the mutex,
+                * then send the state change request, ... */
+               spin_unlock_irq(&tconn->req_lock);
+               mutex_lock(&tconn->cstate_mutex);
+               have_mutex = true;
+
+               set_bit(CONN_WD_ST_CHG_REQ, &tconn->flags);
+               if (conn_send_state_req(tconn, mask, val)) {
+                       /* sending failed. */
+                       clear_bit(CONN_WD_ST_CHG_REQ, &tconn->flags);
+                       rv = SS_CW_FAILED_BY_PEER;
+                       /* need to re-aquire the spin lock, though */
+                       goto abort_unlocked;
+               }
+
+               if (val.conn == C_DISCONNECTING)
+                       set_bit(DISCONNECT_SENT, &tconn->flags);
+
+               /* ... and re-aquire the spinlock.
+                * If _conn_rq_cond() returned >= SS_SUCCESS, we must call
+                * conn_set_state() within the same spinlock. */
+               spin_lock_irq(&tconn->req_lock);
+               wait_event_lock_irq(tconn->ping_wait,
+                               (rv = _conn_rq_cond(tconn, mask, val)),
+                               tconn->req_lock,
+                               );
+               clear_bit(CONN_WD_ST_CHG_REQ, &tconn->flags);
                if (rv < SS_SUCCESS)
                        goto abort;
        }
@@ -1796,9 +1793,16 @@ _conn_request_state(struct drbd_tconn *tconn, union drbd_state mask, union drbd_
                conn_err(tconn, "Could not kmalloc an acscw\n");
        }
 
-       return rv;
  abort:
-       if (flags & CS_VERBOSE) {
+       if (have_mutex) {
+               /* mutex_unlock() "... must not be used in interrupt context.",
+                * so give up the spinlock, then re-aquire it */
+               spin_unlock_irq(&tconn->req_lock);
+ abort_unlocked:
+               mutex_unlock(&tconn->cstate_mutex);
+               spin_lock_irq(&tconn->req_lock);
+       }
+       if (rv < SS_SUCCESS && flags & CS_VERBOSE) {
                conn_err(tconn, "State change failed: %s\n", drbd_set_st_err_str(rv));
                conn_err(tconn, " state = { cs:%s }\n", drbd_conn_str(oc));
                conn_err(tconn, "wanted = { cs:%s }\n", drbd_conn_str(val.conn));