Skip to content

Commit

Permalink
IB/cm: Fix timewait crash after module unload
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Sean Hefty authored and Roland Dreier committed Oct 10, 2006
1 parent a8bf4e7 commit 8575329
Showing 1 changed file with 36 additions and 18 deletions.
54 changes: 36 additions & 18 deletions drivers/infiniband/core/cm.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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)
Expand All @@ -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.
Expand All @@ -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;
}
Expand Down Expand Up @@ -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);
Expand All @@ -1315,29 +1318,29 @@ 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. */
listen_cm_id_priv = cm_find_listen(cm_id_priv->id.device,
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)
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
Expand All @@ -3391,7 +3396,20 @@ static int __init ib_cm_init(void)

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);
}
Expand Down

0 comments on commit 8575329

Please sign in to comment.