diff options
author | Michael Chan <mchan@broadcom.com> | 2009-08-14 11:49:47 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2009-08-15 21:50:47 -0400 |
commit | 7fc1ece40704b150477e548a7a98d285cc418790 (patch) | |
tree | fd7f72c1b92ddde327fb72235a256539ac193b8c /drivers | |
parent | 681dbd710779e8b8d5bae926f6b11f30df70638b (diff) |
cnic: Fix locking in init/exit calls.
The slow path ulp_init and ulp_exit calls to the bnx2i driver
are sleepable calls and therefore should not be protected using
rcu_read_lock. Fix it by using mutex and refcount during these
calls. cnic_unregister_driver() will now wait for the refcount
to go to zero before completing the call.
Signed-off-by: Michael Chan <mchan@broadcom.com>
Reviewed-by: Benjamin Li <benli@broadcom.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/net/cnic.c | 48 | ||||
-rw-r--r-- | drivers/net/cnic_if.h | 1 |
2 files changed, 40 insertions, 9 deletions
diff --git a/drivers/net/cnic.c b/drivers/net/cnic.c index 4ff618a7afd..74c342959b7 100644 --- a/drivers/net/cnic.c +++ b/drivers/net/cnic.c | |||
@@ -138,6 +138,16 @@ static struct cnic_dev *cnic_from_netdev(struct net_device *netdev) | |||
138 | return NULL; | 138 | return NULL; |
139 | } | 139 | } |
140 | 140 | ||
141 | static inline void ulp_get(struct cnic_ulp_ops *ulp_ops) | ||
142 | { | ||
143 | atomic_inc(&ulp_ops->ref_count); | ||
144 | } | ||
145 | |||
146 | static inline void ulp_put(struct cnic_ulp_ops *ulp_ops) | ||
147 | { | ||
148 | atomic_dec(&ulp_ops->ref_count); | ||
149 | } | ||
150 | |||
141 | static void cnic_ctx_wr(struct cnic_dev *dev, u32 cid_addr, u32 off, u32 val) | 151 | static void cnic_ctx_wr(struct cnic_dev *dev, u32 cid_addr, u32 off, u32 val) |
142 | { | 152 | { |
143 | struct cnic_local *cp = dev->cnic_priv; | 153 | struct cnic_local *cp = dev->cnic_priv; |
@@ -358,6 +368,7 @@ int cnic_register_driver(int ulp_type, struct cnic_ulp_ops *ulp_ops) | |||
358 | } | 368 | } |
359 | read_unlock(&cnic_dev_lock); | 369 | read_unlock(&cnic_dev_lock); |
360 | 370 | ||
371 | atomic_set(&ulp_ops->ref_count, 0); | ||
361 | rcu_assign_pointer(cnic_ulp_tbl[ulp_type], ulp_ops); | 372 | rcu_assign_pointer(cnic_ulp_tbl[ulp_type], ulp_ops); |
362 | mutex_unlock(&cnic_lock); | 373 | mutex_unlock(&cnic_lock); |
363 | 374 | ||
@@ -379,6 +390,8 @@ int cnic_register_driver(int ulp_type, struct cnic_ulp_ops *ulp_ops) | |||
379 | int cnic_unregister_driver(int ulp_type) | 390 | int cnic_unregister_driver(int ulp_type) |
380 | { | 391 | { |
381 | struct cnic_dev *dev; | 392 | struct cnic_dev *dev; |
393 | struct cnic_ulp_ops *ulp_ops; | ||
394 | int i = 0; | ||
382 | 395 | ||
383 | if (ulp_type >= MAX_CNIC_ULP_TYPE) { | 396 | if (ulp_type >= MAX_CNIC_ULP_TYPE) { |
384 | printk(KERN_ERR PFX "cnic_unregister_driver: Bad type %d\n", | 397 | printk(KERN_ERR PFX "cnic_unregister_driver: Bad type %d\n", |
@@ -386,7 +399,8 @@ int cnic_unregister_driver(int ulp_type) | |||
386 | return -EINVAL; | 399 | return -EINVAL; |
387 | } | 400 | } |
388 | mutex_lock(&cnic_lock); | 401 | mutex_lock(&cnic_lock); |
389 | if (!cnic_ulp_tbl[ulp_type]) { | 402 | ulp_ops = cnic_ulp_tbl[ulp_type]; |
403 | if (!ulp_ops) { | ||
390 | printk(KERN_ERR PFX "cnic_unregister_driver: Type %d has not " | 404 | printk(KERN_ERR PFX "cnic_unregister_driver: Type %d has not " |
391 | "been registered\n", ulp_type); | 405 | "been registered\n", ulp_type); |
392 | goto out_unlock; | 406 | goto out_unlock; |
@@ -411,6 +425,14 @@ int cnic_unregister_driver(int ulp_type) | |||
411 | 425 | ||
412 | mutex_unlock(&cnic_lock); | 426 | mutex_unlock(&cnic_lock); |
413 | synchronize_rcu(); | 427 | synchronize_rcu(); |
428 | while ((atomic_read(&ulp_ops->ref_count) != 0) && (i < 20)) { | ||
429 | msleep(100); | ||
430 | i++; | ||
431 | } | ||
432 | |||
433 | if (atomic_read(&ulp_ops->ref_count) != 0) | ||
434 | printk(KERN_WARNING PFX "%s: Failed waiting for ref count to go" | ||
435 | " to zero.\n", dev->netdev->name); | ||
414 | return 0; | 436 | return 0; |
415 | 437 | ||
416 | out_unlock: | 438 | out_unlock: |
@@ -1161,19 +1183,23 @@ static void cnic_ulp_init(struct cnic_dev *dev) | |||
1161 | int i; | 1183 | int i; |
1162 | struct cnic_local *cp = dev->cnic_priv; | 1184 | struct cnic_local *cp = dev->cnic_priv; |
1163 | 1185 | ||
1164 | rcu_read_lock(); | ||
1165 | for (i = 0; i < MAX_CNIC_ULP_TYPE_EXT; i++) { | 1186 | for (i = 0; i < MAX_CNIC_ULP_TYPE_EXT; i++) { |
1166 | struct cnic_ulp_ops *ulp_ops; | 1187 | struct cnic_ulp_ops *ulp_ops; |
1167 | 1188 | ||
1168 | ulp_ops = rcu_dereference(cnic_ulp_tbl[i]); | 1189 | mutex_lock(&cnic_lock); |
1169 | if (!ulp_ops || !ulp_ops->cnic_init) | 1190 | ulp_ops = cnic_ulp_tbl[i]; |
1191 | if (!ulp_ops || !ulp_ops->cnic_init) { | ||
1192 | mutex_unlock(&cnic_lock); | ||
1170 | continue; | 1193 | continue; |
1194 | } | ||
1195 | ulp_get(ulp_ops); | ||
1196 | mutex_unlock(&cnic_lock); | ||
1171 | 1197 | ||
1172 | if (!test_and_set_bit(ULP_F_INIT, &cp->ulp_flags[i])) | 1198 | if (!test_and_set_bit(ULP_F_INIT, &cp->ulp_flags[i])) |
1173 | ulp_ops->cnic_init(dev); | 1199 | ulp_ops->cnic_init(dev); |
1174 | 1200 | ||
1201 | ulp_put(ulp_ops); | ||
1175 | } | 1202 | } |
1176 | rcu_read_unlock(); | ||
1177 | } | 1203 | } |
1178 | 1204 | ||
1179 | static void cnic_ulp_exit(struct cnic_dev *dev) | 1205 | static void cnic_ulp_exit(struct cnic_dev *dev) |
@@ -1181,19 +1207,23 @@ static void cnic_ulp_exit(struct cnic_dev *dev) | |||
1181 | int i; | 1207 | int i; |
1182 | struct cnic_local *cp = dev->cnic_priv; | 1208 | struct cnic_local *cp = dev->cnic_priv; |
1183 | 1209 | ||
1184 | rcu_read_lock(); | ||
1185 | for (i = 0; i < MAX_CNIC_ULP_TYPE_EXT; i++) { | 1210 | for (i = 0; i < MAX_CNIC_ULP_TYPE_EXT; i++) { |
1186 | struct cnic_ulp_ops *ulp_ops; | 1211 | struct cnic_ulp_ops *ulp_ops; |
1187 | 1212 | ||
1188 | ulp_ops = rcu_dereference(cnic_ulp_tbl[i]); | 1213 | mutex_lock(&cnic_lock); |
1189 | if (!ulp_ops || !ulp_ops->cnic_exit) | 1214 | ulp_ops = cnic_ulp_tbl[i]; |
1215 | if (!ulp_ops || !ulp_ops->cnic_exit) { | ||
1216 | mutex_unlock(&cnic_lock); | ||
1190 | continue; | 1217 | continue; |
1218 | } | ||
1219 | ulp_get(ulp_ops); | ||
1220 | mutex_unlock(&cnic_lock); | ||
1191 | 1221 | ||
1192 | if (test_and_clear_bit(ULP_F_INIT, &cp->ulp_flags[i])) | 1222 | if (test_and_clear_bit(ULP_F_INIT, &cp->ulp_flags[i])) |
1193 | ulp_ops->cnic_exit(dev); | 1223 | ulp_ops->cnic_exit(dev); |
1194 | 1224 | ||
1225 | ulp_put(ulp_ops); | ||
1195 | } | 1226 | } |
1196 | rcu_read_unlock(); | ||
1197 | } | 1227 | } |
1198 | 1228 | ||
1199 | static int cnic_cm_offload_pg(struct cnic_sock *csk) | 1229 | static int cnic_cm_offload_pg(struct cnic_sock *csk) |
diff --git a/drivers/net/cnic_if.h b/drivers/net/cnic_if.h index d1bce27ee99..a49235739ee 100644 --- a/drivers/net/cnic_if.h +++ b/drivers/net/cnic_if.h | |||
@@ -290,6 +290,7 @@ struct cnic_ulp_ops { | |||
290 | void (*iscsi_nl_send_msg)(struct cnic_dev *dev, u32 msg_type, | 290 | void (*iscsi_nl_send_msg)(struct cnic_dev *dev, u32 msg_type, |
291 | char *data, u16 data_size); | 291 | char *data, u16 data_size); |
292 | struct module *owner; | 292 | struct module *owner; |
293 | atomic_t ref_count; | ||
293 | }; | 294 | }; |
294 | 295 | ||
295 | extern int cnic_register_driver(int ulp_type, struct cnic_ulp_ops *ulp_ops); | 296 | extern int cnic_register_driver(int ulp_type, struct cnic_ulp_ops *ulp_ops); |