diff options
author | Lars Ellenberg <lars.ellenberg@linbit.com> | 2012-08-22 16:15:26 +0200 |
---|---|---|
committer | Philipp Reisner <philipp.reisner@linbit.com> | 2012-11-09 14:08:21 +0100 |
commit | c02abda2b28734a7e97e15db866507b3cb92b7d0 (patch) | |
tree | c85af57b2f3e9689d4374895c125da37d7ae7fad /drivers/block | |
parent | drbd: Fix a race condition that can lead to a BUG() (diff) | |
download | linux-c02abda2b28734a7e97e15db866507b3cb92b7d0.tar.xz linux-c02abda2b28734a7e97e15db866507b3cb92b7d0.zip |
drbd: mutex_unlock "... must no be used in interrupt context"
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>
Diffstat (limited to 'drivers/block')
-rw-r--r-- | drivers/block/drbd/drbd_state.c | 74 |
1 files changed, 39 insertions, 35 deletions
diff --git a/drivers/block/drbd/drbd_state.c b/drivers/block/drbd/drbd_state.c index ce1495187f02..755425a7a99b 100644 --- a/drivers/block/drbd/drbd_state.c +++ b/drivers/block/drbd/drbd_state.c @@ -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)); |