IB/cm: Fix timewait crash after module unload
authorSean Hefty <sean.hefty@intel.com>
Wed, 4 Oct 2006 18:29:59 +0000 (11:29 -0700)
committerRoland Dreier <rolandd@cisco.com>
Tue, 10 Oct 2006 19:50:38 +0000 (12:50 -0700)
If the ib_cm module is unloaded while id's are still in timewait, the
CM will destroy the work queue used to process timewait.  Once the
id's exit timewait, their timers will fire, leading to a crash trying
to access the destroyed work queue.

We need to track id's that are in timewait, and cancel their deferred
work on module unload.

Signed-off-by: Sean Hefty <sean.hefty@intel.com>
Signed-off-by: Roland Dreier <rolandd@cisco.com>
drivers/infiniband/core/cm.c

index f35fcc4..470c482 100644 (file)
@@ -75,6 +75,7 @@ static struct ib_cm {
        struct rb_root remote_sidr_table;
        struct idr local_id_table;
        __be32 random_id_operand;
+       struct list_head timewait_list;
        struct workqueue_struct *wq;
 } cm;
 
@@ -112,6 +113,7 @@ struct cm_work {
 
 struct cm_timewait_info {
        struct cm_work work;                    /* Must be first. */
+       struct list_head list;
        struct rb_node remote_qp_node;
        struct rb_node remote_id_node;
        __be64 remote_ca_guid;
@@ -647,13 +649,6 @@ static inline int cm_convert_to_ms(int iba_time)
 
 static void cm_cleanup_timewait(struct cm_timewait_info *timewait_info)
 {
-       unsigned long flags;
-
-       if (!timewait_info->inserted_remote_id &&
-           !timewait_info->inserted_remote_qp)
-           return;
-
-       spin_lock_irqsave(&cm.lock, flags);
        if (timewait_info->inserted_remote_id) {
                rb_erase(&timewait_info->remote_id_node, &cm.remote_id_table);
                timewait_info->inserted_remote_id = 0;
@@ -663,7 +658,6 @@ static void cm_cleanup_timewait(struct cm_timewait_info *timewait_info)
                rb_erase(&timewait_info->remote_qp_node, &cm.remote_qp_table);
                timewait_info->inserted_remote_qp = 0;
        }
-       spin_unlock_irqrestore(&cm.lock, flags);
 }
 
 static struct cm_timewait_info * cm_create_timewait_info(__be32 local_id)
@@ -684,8 +678,12 @@ static struct cm_timewait_info * cm_create_timewait_info(__be32 local_id)
 static void cm_enter_timewait(struct cm_id_private *cm_id_priv)
 {
        int wait_time;
+       unsigned long flags;
 
+       spin_lock_irqsave(&cm.lock, flags);
        cm_cleanup_timewait(cm_id_priv->timewait_info);
+       list_add_tail(&cm_id_priv->timewait_info->list, &cm.timewait_list);
+       spin_unlock_irqrestore(&cm.lock, flags);
 
        /*
         * The cm_id could be destroyed by the user before we exit timewait.
@@ -701,9 +699,13 @@ static void cm_enter_timewait(struct cm_id_private *cm_id_priv)
 
 static void cm_reset_to_idle(struct cm_id_private *cm_id_priv)
 {
+       unsigned long flags;
+
        cm_id_priv->id.state = IB_CM_IDLE;
        if (cm_id_priv->timewait_info) {
+               spin_lock_irqsave(&cm.lock, flags);
                cm_cleanup_timewait(cm_id_priv->timewait_info);
+               spin_unlock_irqrestore(&cm.lock, flags);
                kfree(cm_id_priv->timewait_info);
                cm_id_priv->timewait_info = NULL;
        }
@@ -1307,6 +1309,7 @@ static struct cm_id_private * cm_match_req(struct cm_work *work,
        if (timewait_info) {
                cur_cm_id_priv = cm_get_id(timewait_info->work.local_id,
                                           timewait_info->work.remote_id);
+               cm_cleanup_timewait(cm_id_priv->timewait_info);
                spin_unlock_irqrestore(&cm.lock, flags);
                if (cur_cm_id_priv) {
                        cm_dup_req_handler(work, cur_cm_id_priv);
@@ -1315,7 +1318,8 @@ static struct cm_id_private * cm_match_req(struct cm_work *work,
                        cm_issue_rej(work->port, work->mad_recv_wc,
                                     IB_CM_REJ_STALE_CONN, CM_MSG_RESPONSE_REQ,
                                     NULL, 0);
-               goto error;
+               listen_cm_id_priv = NULL;
+               goto out;
        }
 
        /* Find matching listen request. */
@@ -1323,21 +1327,20 @@ static struct cm_id_private * cm_match_req(struct cm_work *work,
                                           req_msg->service_id,
                                           req_msg->private_data);
        if (!listen_cm_id_priv) {
+               cm_cleanup_timewait(cm_id_priv->timewait_info);
                spin_unlock_irqrestore(&cm.lock, flags);
                cm_issue_rej(work->port, work->mad_recv_wc,
                             IB_CM_REJ_INVALID_SERVICE_ID, CM_MSG_RESPONSE_REQ,
                             NULL, 0);
-               goto error;
+               goto out;
        }
        atomic_inc(&listen_cm_id_priv->refcount);
        atomic_inc(&cm_id_priv->refcount);
        cm_id_priv->id.state = IB_CM_REQ_RCVD;
        atomic_inc(&cm_id_priv->work_count);
        spin_unlock_irqrestore(&cm.lock, flags);
+out:
        return listen_cm_id_priv;
-
-error: cm_cleanup_timewait(cm_id_priv->timewait_info);
-       return NULL;
 }
 
 static int cm_req_handler(struct cm_work *work)
@@ -2601,28 +2604,29 @@ static int cm_timewait_handler(struct cm_work *work)
 {
        struct cm_timewait_info *timewait_info;
        struct cm_id_private *cm_id_priv;
-       unsigned long flags;
        int ret;
 
        timewait_info = (struct cm_timewait_info *)work;
-       cm_cleanup_timewait(timewait_info);
+       spin_lock_irq(&cm.lock);
+       list_del(&timewait_info->list);
+       spin_unlock_irq(&cm.lock);
 
        cm_id_priv = cm_acquire_id(timewait_info->work.local_id,
                                   timewait_info->work.remote_id);
        if (!cm_id_priv)
                return -EINVAL;
 
-       spin_lock_irqsave(&cm_id_priv->lock, flags);
+       spin_lock_irq(&cm_id_priv->lock);
        if (cm_id_priv->id.state != IB_CM_TIMEWAIT ||
            cm_id_priv->remote_qpn != timewait_info->remote_qpn) {
-               spin_unlock_irqrestore(&cm_id_priv->lock, flags);
+               spin_unlock_irq(&cm_id_priv->lock);
                goto out;
        }
        cm_id_priv->id.state = IB_CM_IDLE;
        ret = atomic_inc_and_test(&cm_id_priv->work_count);
        if (!ret)
                list_add_tail(&work->list, &cm_id_priv->work_list);
-       spin_unlock_irqrestore(&cm_id_priv->lock, flags);
+       spin_unlock_irq(&cm_id_priv->lock);
 
        if (ret)
                cm_process_work(cm_id_priv, work);
@@ -3374,6 +3378,7 @@ static int __init ib_cm_init(void)
        idr_init(&cm.local_id_table);
        get_random_bytes(&cm.random_id_operand, sizeof cm.random_id_operand);
        idr_pre_get(&cm.local_id_table, GFP_KERNEL);
+       INIT_LIST_HEAD(&cm.timewait_list);
 
        cm.wq = create_workqueue("ib_cm");
        if (!cm.wq)
@@ -3391,7 +3396,20 @@ error:
 
 static void __exit ib_cm_cleanup(void)
 {
+       struct cm_timewait_info *timewait_info, *tmp;
+
+       spin_lock_irq(&cm.lock);
+       list_for_each_entry(timewait_info, &cm.timewait_list, list)
+               cancel_delayed_work(&timewait_info->work.work);
+       spin_unlock_irq(&cm.lock);
+
        destroy_workqueue(cm.wq);
+
+       list_for_each_entry_safe(timewait_info, tmp, &cm.timewait_list, list) {
+               list_del(&timewait_info->list);
+               kfree(timewait_info);
+       }
+
        ib_unregister_client(&cm_client);
        idr_destroy(&cm.local_id_table);
 }