diff --git a/include/net/bluetooth/rfcomm.h b/include/net/bluetooth/rfcomm.h index 99d26879b02a..a92799fc5e74 100644 --- a/include/net/bluetooth/rfcomm.h +++ b/include/net/bluetooth/rfcomm.h @@ -171,6 +171,7 @@ struct rfcomm_dlc { struct rfcomm_session *session; struct sk_buff_head tx_queue; struct timer_list timer; + struct work_struct state_change_work; struct mutex lock; unsigned long state; @@ -186,6 +187,7 @@ struct rfcomm_dlc { u8 sec_level; u8 role_switch; u32 defer_setup; + int err; uint mtu; uint cfc; @@ -310,6 +312,7 @@ struct rfcomm_pinfo { u8 role_switch; }; +void __rfcomm_sk_state_change(struct work_struct *work); int rfcomm_init_sockets(void); void rfcomm_cleanup_sockets(void); diff --git a/net/bluetooth/rfcomm/core.c b/net/bluetooth/rfcomm/core.c index 7324764384b6..c6494e85cd68 100644 --- a/net/bluetooth/rfcomm/core.c +++ b/net/bluetooth/rfcomm/core.c @@ -289,6 +289,7 @@ static void rfcomm_dlc_clear_state(struct rfcomm_dlc *d) d->flags = 0; d->mscex = 0; d->sec_level = BT_SECURITY_LOW; + d->err = 0; d->mtu = RFCOMM_DEFAULT_MTU; d->v24_sig = RFCOMM_V24_RTC | RFCOMM_V24_RTR | RFCOMM_V24_DV; @@ -306,6 +307,7 @@ struct rfcomm_dlc *rfcomm_dlc_alloc(gfp_t prio) timer_setup(&d->timer, rfcomm_dlc_timeout, 0); skb_queue_head_init(&d->tx_queue); + INIT_WORK(&d->state_change_work, __rfcomm_sk_state_change); mutex_init(&d->lock); refcount_set(&d->refcnt, 1); diff --git a/net/bluetooth/rfcomm/sock.c b/net/bluetooth/rfcomm/sock.c index 4bf4ea6cbb5e..4850dafbaa05 100644 --- a/net/bluetooth/rfcomm/sock.c +++ b/net/bluetooth/rfcomm/sock.c @@ -61,19 +61,22 @@ static void rfcomm_sk_data_ready(struct rfcomm_dlc *d, struct sk_buff *skb) rfcomm_dlc_throttle(d); } -static void rfcomm_sk_state_change(struct rfcomm_dlc *d, int err) +void __rfcomm_sk_state_change(struct work_struct *work) { + struct rfcomm_dlc *d = container_of(work, struct rfcomm_dlc, + state_change_work); struct sock *sk = d->owner, *parent; if (!sk) return; - BT_DBG("dlc %p state %ld err %d", d, d->state, err); - lock_sock(sk); + rfcomm_dlc_lock(d); - if (err) - sk->sk_err = err; + BT_DBG("dlc %p state %ld err %d", d, d->state, d->err); + + if (d->err) + sk->sk_err = d->err; sk->sk_state = d->state; @@ -91,15 +94,22 @@ static void rfcomm_sk_state_change(struct rfcomm_dlc *d, int err) sk->sk_state_change(sk); } + rfcomm_dlc_unlock(d); release_sock(sk); + sock_put(sk); +} - if (parent && sock_flag(sk, SOCK_ZAPPED)) { - /* We have to drop DLC lock here, otherwise - * rfcomm_sock_destruct() will dead lock. */ - rfcomm_dlc_unlock(d); - rfcomm_sock_kill(sk); - rfcomm_dlc_lock(d); - } +static void rfcomm_sk_state_change(struct rfcomm_dlc *d, int err) +{ + struct sock *sk = d->owner; + + if (!sk) + return; + + d->err = err; + sock_hold(sk); + if (!schedule_work(&d->state_change_work)) + sock_put(sk); } /* ---- Socket functions ---- */