diff options
author | Haggai Eran <haggaie@mellanox.com> | 2015-07-30 10:50:14 -0400 |
---|---|---|
committer | Doug Ledford <dledford@redhat.com> | 2015-08-30 15:48:21 -0400 |
commit | 7c1eb45a22d76bb99236e7485958f87ef7c449cf (patch) | |
tree | 9c4d29b17d17e99f6cfa7ccc1a542d13f9bb4269 /drivers/infiniband/core | |
parent | 5aa44bb90f047662c12c44be1b6de454658632d0 (diff) |
IB/core: lock client data with lists_rwsem
An ib_client callback that is called with the lists_rwsem locked only for
read is protected from changes to the IB client lists, but not from
ib_unregister_device() freeing its client data. This is because
ib_unregister_device() will remove the device from the device list with
lists_rwsem locked for write, but perform the rest of the cleanup,
including the call to remove() without that lock.
Mark client data that is undergoing de-registration with a new going_down
flag in the client data context. Lock the client data list with lists_rwsem
for write in addition to using the spinlock, so that functions calling the
callback would be able to lock only lists_rwsem for read and let callbacks
sleep.
Since ib_unregister_client() now marks the client data context, no need for
remove() to search the context again, so pass the client data directly to
remove() callbacks.
Reviewed-by: Jason Gunthorpe <jgunthorpe@obsidianresearch.com>
Signed-off-by: Haggai Eran <haggaie@mellanox.com>
Signed-off-by: Doug Ledford <dledford@redhat.com>
Diffstat (limited to 'drivers/infiniband/core')
-rw-r--r-- | drivers/infiniband/core/cache.c | 2 | ||||
-rw-r--r-- | drivers/infiniband/core/cm.c | 7 | ||||
-rw-r--r-- | drivers/infiniband/core/cma.c | 7 | ||||
-rw-r--r-- | drivers/infiniband/core/device.c | 53 | ||||
-rw-r--r-- | drivers/infiniband/core/mad.c | 2 | ||||
-rw-r--r-- | drivers/infiniband/core/multicast.c | 7 | ||||
-rw-r--r-- | drivers/infiniband/core/sa_query.c | 6 | ||||
-rw-r--r-- | drivers/infiniband/core/ucm.c | 6 | ||||
-rw-r--r-- | drivers/infiniband/core/user_mad.c | 6 | ||||
-rw-r--r-- | drivers/infiniband/core/uverbs_main.c | 6 |
10 files changed, 67 insertions, 35 deletions
diff --git a/drivers/infiniband/core/cache.c b/drivers/infiniband/core/cache.c index 871da832d016..c93af66cc091 100644 --- a/drivers/infiniband/core/cache.c +++ b/drivers/infiniband/core/cache.c | |||
@@ -394,7 +394,7 @@ err: | |||
394 | kfree(device->cache.lmc_cache); | 394 | kfree(device->cache.lmc_cache); |
395 | } | 395 | } |
396 | 396 | ||
397 | static void ib_cache_cleanup_one(struct ib_device *device) | 397 | static void ib_cache_cleanup_one(struct ib_device *device, void *client_data) |
398 | { | 398 | { |
399 | int p; | 399 | int p; |
400 | 400 | ||
diff --git a/drivers/infiniband/core/cm.c b/drivers/infiniband/core/cm.c index 3a972ebf3c0d..82d5c4362aa8 100644 --- a/drivers/infiniband/core/cm.c +++ b/drivers/infiniband/core/cm.c | |||
@@ -58,7 +58,7 @@ MODULE_DESCRIPTION("InfiniBand CM"); | |||
58 | MODULE_LICENSE("Dual BSD/GPL"); | 58 | MODULE_LICENSE("Dual BSD/GPL"); |
59 | 59 | ||
60 | static void cm_add_one(struct ib_device *device); | 60 | static void cm_add_one(struct ib_device *device); |
61 | static void cm_remove_one(struct ib_device *device); | 61 | static void cm_remove_one(struct ib_device *device, void *client_data); |
62 | 62 | ||
63 | static struct ib_client cm_client = { | 63 | static struct ib_client cm_client = { |
64 | .name = "cm", | 64 | .name = "cm", |
@@ -3886,9 +3886,9 @@ free: | |||
3886 | kfree(cm_dev); | 3886 | kfree(cm_dev); |
3887 | } | 3887 | } |
3888 | 3888 | ||
3889 | static void cm_remove_one(struct ib_device *ib_device) | 3889 | static void cm_remove_one(struct ib_device *ib_device, void *client_data) |
3890 | { | 3890 | { |
3891 | struct cm_device *cm_dev; | 3891 | struct cm_device *cm_dev = client_data; |
3892 | struct cm_port *port; | 3892 | struct cm_port *port; |
3893 | struct ib_port_modify port_modify = { | 3893 | struct ib_port_modify port_modify = { |
3894 | .clr_port_cap_mask = IB_PORT_CM_SUP | 3894 | .clr_port_cap_mask = IB_PORT_CM_SUP |
@@ -3896,7 +3896,6 @@ static void cm_remove_one(struct ib_device *ib_device) | |||
3896 | unsigned long flags; | 3896 | unsigned long flags; |
3897 | int i; | 3897 | int i; |
3898 | 3898 | ||
3899 | cm_dev = ib_get_client_data(ib_device, &cm_client); | ||
3900 | if (!cm_dev) | 3899 | if (!cm_dev) |
3901 | return; | 3900 | return; |
3902 | 3901 | ||
diff --git a/drivers/infiniband/core/cma.c b/drivers/infiniband/core/cma.c index 4e72e4c16cfe..9664131c4eeb 100644 --- a/drivers/infiniband/core/cma.c +++ b/drivers/infiniband/core/cma.c | |||
@@ -94,7 +94,7 @@ const char *rdma_event_msg(enum rdma_cm_event_type event) | |||
94 | EXPORT_SYMBOL(rdma_event_msg); | 94 | EXPORT_SYMBOL(rdma_event_msg); |
95 | 95 | ||
96 | static void cma_add_one(struct ib_device *device); | 96 | static void cma_add_one(struct ib_device *device); |
97 | static void cma_remove_one(struct ib_device *device); | 97 | static void cma_remove_one(struct ib_device *device, void *client_data); |
98 | 98 | ||
99 | static struct ib_client cma_client = { | 99 | static struct ib_client cma_client = { |
100 | .name = "cma", | 100 | .name = "cma", |
@@ -3554,11 +3554,10 @@ static void cma_process_remove(struct cma_device *cma_dev) | |||
3554 | wait_for_completion(&cma_dev->comp); | 3554 | wait_for_completion(&cma_dev->comp); |
3555 | } | 3555 | } |
3556 | 3556 | ||
3557 | static void cma_remove_one(struct ib_device *device) | 3557 | static void cma_remove_one(struct ib_device *device, void *client_data) |
3558 | { | 3558 | { |
3559 | struct cma_device *cma_dev; | 3559 | struct cma_device *cma_dev = client_data; |
3560 | 3560 | ||
3561 | cma_dev = ib_get_client_data(device, &cma_client); | ||
3562 | if (!cma_dev) | 3561 | if (!cma_dev) |
3563 | return; | 3562 | return; |
3564 | 3563 | ||
diff --git a/drivers/infiniband/core/device.c b/drivers/infiniband/core/device.c index 0c8fa781538b..ce317e623862 100644 --- a/drivers/infiniband/core/device.c +++ b/drivers/infiniband/core/device.c | |||
@@ -50,6 +50,9 @@ struct ib_client_data { | |||
50 | struct list_head list; | 50 | struct list_head list; |
51 | struct ib_client *client; | 51 | struct ib_client *client; |
52 | void * data; | 52 | void * data; |
53 | /* The device or client is going down. Do not call client or device | ||
54 | * callbacks other than remove(). */ | ||
55 | bool going_down; | ||
53 | }; | 56 | }; |
54 | 57 | ||
55 | struct workqueue_struct *ib_wq; | 58 | struct workqueue_struct *ib_wq; |
@@ -69,6 +72,8 @@ static LIST_HEAD(client_list); | |||
69 | * to the lists must be done with a write lock. A special case is when the | 72 | * to the lists must be done with a write lock. A special case is when the |
70 | * device_mutex is locked. In this case locking the lists for read access is | 73 | * device_mutex is locked. In this case locking the lists for read access is |
71 | * not necessary as the device_mutex implies it. | 74 | * not necessary as the device_mutex implies it. |
75 | * | ||
76 | * lists_rwsem also protects access to the client data list. | ||
72 | */ | 77 | */ |
73 | static DEFINE_MUTEX(device_mutex); | 78 | static DEFINE_MUTEX(device_mutex); |
74 | static DECLARE_RWSEM(lists_rwsem); | 79 | static DECLARE_RWSEM(lists_rwsem); |
@@ -210,10 +215,13 @@ static int add_client_context(struct ib_device *device, struct ib_client *client | |||
210 | 215 | ||
211 | context->client = client; | 216 | context->client = client; |
212 | context->data = NULL; | 217 | context->data = NULL; |
218 | context->going_down = false; | ||
213 | 219 | ||
220 | down_write(&lists_rwsem); | ||
214 | spin_lock_irqsave(&device->client_data_lock, flags); | 221 | spin_lock_irqsave(&device->client_data_lock, flags); |
215 | list_add(&context->list, &device->client_data_list); | 222 | list_add(&context->list, &device->client_data_list); |
216 | spin_unlock_irqrestore(&device->client_data_lock, flags); | 223 | spin_unlock_irqrestore(&device->client_data_lock, flags); |
224 | up_write(&lists_rwsem); | ||
217 | 225 | ||
218 | return 0; | 226 | return 0; |
219 | } | 227 | } |
@@ -339,7 +347,6 @@ EXPORT_SYMBOL(ib_register_device); | |||
339 | */ | 347 | */ |
340 | void ib_unregister_device(struct ib_device *device) | 348 | void ib_unregister_device(struct ib_device *device) |
341 | { | 349 | { |
342 | struct ib_client *client; | ||
343 | struct ib_client_data *context, *tmp; | 350 | struct ib_client_data *context, *tmp; |
344 | unsigned long flags; | 351 | unsigned long flags; |
345 | 352 | ||
@@ -347,20 +354,29 @@ void ib_unregister_device(struct ib_device *device) | |||
347 | 354 | ||
348 | down_write(&lists_rwsem); | 355 | down_write(&lists_rwsem); |
349 | list_del(&device->core_list); | 356 | list_del(&device->core_list); |
350 | up_write(&lists_rwsem); | 357 | spin_lock_irqsave(&device->client_data_lock, flags); |
358 | list_for_each_entry_safe(context, tmp, &device->client_data_list, list) | ||
359 | context->going_down = true; | ||
360 | spin_unlock_irqrestore(&device->client_data_lock, flags); | ||
361 | downgrade_write(&lists_rwsem); | ||
351 | 362 | ||
352 | list_for_each_entry_reverse(client, &client_list, list) | 363 | list_for_each_entry_safe(context, tmp, &device->client_data_list, |
353 | if (client->remove) | 364 | list) { |
354 | client->remove(device); | 365 | if (context->client->remove) |
366 | context->client->remove(device, context->data); | ||
367 | } | ||
368 | up_read(&lists_rwsem); | ||
355 | 369 | ||
356 | mutex_unlock(&device_mutex); | 370 | mutex_unlock(&device_mutex); |
357 | 371 | ||
358 | ib_device_unregister_sysfs(device); | 372 | ib_device_unregister_sysfs(device); |
359 | 373 | ||
374 | down_write(&lists_rwsem); | ||
360 | spin_lock_irqsave(&device->client_data_lock, flags); | 375 | spin_lock_irqsave(&device->client_data_lock, flags); |
361 | list_for_each_entry_safe(context, tmp, &device->client_data_list, list) | 376 | list_for_each_entry_safe(context, tmp, &device->client_data_list, list) |
362 | kfree(context); | 377 | kfree(context); |
363 | spin_unlock_irqrestore(&device->client_data_lock, flags); | 378 | spin_unlock_irqrestore(&device->client_data_lock, flags); |
379 | up_write(&lists_rwsem); | ||
364 | 380 | ||
365 | device->reg_state = IB_DEV_UNREGISTERED; | 381 | device->reg_state = IB_DEV_UNREGISTERED; |
366 | } | 382 | } |
@@ -420,16 +436,35 @@ void ib_unregister_client(struct ib_client *client) | |||
420 | up_write(&lists_rwsem); | 436 | up_write(&lists_rwsem); |
421 | 437 | ||
422 | list_for_each_entry(device, &device_list, core_list) { | 438 | list_for_each_entry(device, &device_list, core_list) { |
423 | if (client->remove) | 439 | struct ib_client_data *found_context = NULL; |
424 | client->remove(device); | ||
425 | 440 | ||
441 | down_write(&lists_rwsem); | ||
426 | spin_lock_irqsave(&device->client_data_lock, flags); | 442 | spin_lock_irqsave(&device->client_data_lock, flags); |
427 | list_for_each_entry_safe(context, tmp, &device->client_data_list, list) | 443 | list_for_each_entry_safe(context, tmp, &device->client_data_list, list) |
428 | if (context->client == client) { | 444 | if (context->client == client) { |
429 | list_del(&context->list); | 445 | context->going_down = true; |
430 | kfree(context); | 446 | found_context = context; |
447 | break; | ||
431 | } | 448 | } |
432 | spin_unlock_irqrestore(&device->client_data_lock, flags); | 449 | spin_unlock_irqrestore(&device->client_data_lock, flags); |
450 | up_write(&lists_rwsem); | ||
451 | |||
452 | if (client->remove) | ||
453 | client->remove(device, found_context ? | ||
454 | found_context->data : NULL); | ||
455 | |||
456 | if (!found_context) { | ||
457 | pr_warn("No client context found for %s/%s\n", | ||
458 | device->name, client->name); | ||
459 | continue; | ||
460 | } | ||
461 | |||
462 | down_write(&lists_rwsem); | ||
463 | spin_lock_irqsave(&device->client_data_lock, flags); | ||
464 | list_del(&found_context->list); | ||
465 | kfree(found_context); | ||
466 | spin_unlock_irqrestore(&device->client_data_lock, flags); | ||
467 | up_write(&lists_rwsem); | ||
433 | } | 468 | } |
434 | 469 | ||
435 | mutex_unlock(&device_mutex); | 470 | mutex_unlock(&device_mutex); |
diff --git a/drivers/infiniband/core/mad.c b/drivers/infiniband/core/mad.c index 786fc51bf04b..66b4b3eb8f67 100644 --- a/drivers/infiniband/core/mad.c +++ b/drivers/infiniband/core/mad.c | |||
@@ -3335,7 +3335,7 @@ error: | |||
3335 | } | 3335 | } |
3336 | } | 3336 | } |
3337 | 3337 | ||
3338 | static void ib_mad_remove_device(struct ib_device *device) | 3338 | static void ib_mad_remove_device(struct ib_device *device, void *client_data) |
3339 | { | 3339 | { |
3340 | int i; | 3340 | int i; |
3341 | 3341 | ||
diff --git a/drivers/infiniband/core/multicast.c b/drivers/infiniband/core/multicast.c index 2cb865c7ce7a..d38d8b2b2979 100644 --- a/drivers/infiniband/core/multicast.c +++ b/drivers/infiniband/core/multicast.c | |||
@@ -43,7 +43,7 @@ | |||
43 | #include "sa.h" | 43 | #include "sa.h" |
44 | 44 | ||
45 | static void mcast_add_one(struct ib_device *device); | 45 | static void mcast_add_one(struct ib_device *device); |
46 | static void mcast_remove_one(struct ib_device *device); | 46 | static void mcast_remove_one(struct ib_device *device, void *client_data); |
47 | 47 | ||
48 | static struct ib_client mcast_client = { | 48 | static struct ib_client mcast_client = { |
49 | .name = "ib_multicast", | 49 | .name = "ib_multicast", |
@@ -840,13 +840,12 @@ static void mcast_add_one(struct ib_device *device) | |||
840 | ib_register_event_handler(&dev->event_handler); | 840 | ib_register_event_handler(&dev->event_handler); |
841 | } | 841 | } |
842 | 842 | ||
843 | static void mcast_remove_one(struct ib_device *device) | 843 | static void mcast_remove_one(struct ib_device *device, void *client_data) |
844 | { | 844 | { |
845 | struct mcast_device *dev; | 845 | struct mcast_device *dev = client_data; |
846 | struct mcast_port *port; | 846 | struct mcast_port *port; |
847 | int i; | 847 | int i; |
848 | 848 | ||
849 | dev = ib_get_client_data(device, &mcast_client); | ||
850 | if (!dev) | 849 | if (!dev) |
851 | return; | 850 | return; |
852 | 851 | ||
diff --git a/drivers/infiniband/core/sa_query.c b/drivers/infiniband/core/sa_query.c index ca919f429666..d40be3673b79 100644 --- a/drivers/infiniband/core/sa_query.c +++ b/drivers/infiniband/core/sa_query.c | |||
@@ -107,7 +107,7 @@ struct ib_sa_mcmember_query { | |||
107 | }; | 107 | }; |
108 | 108 | ||
109 | static void ib_sa_add_one(struct ib_device *device); | 109 | static void ib_sa_add_one(struct ib_device *device); |
110 | static void ib_sa_remove_one(struct ib_device *device); | 110 | static void ib_sa_remove_one(struct ib_device *device, void *client_data); |
111 | 111 | ||
112 | static struct ib_client sa_client = { | 112 | static struct ib_client sa_client = { |
113 | .name = "sa", | 113 | .name = "sa", |
@@ -1221,9 +1221,9 @@ free: | |||
1221 | return; | 1221 | return; |
1222 | } | 1222 | } |
1223 | 1223 | ||
1224 | static void ib_sa_remove_one(struct ib_device *device) | 1224 | static void ib_sa_remove_one(struct ib_device *device, void *client_data) |
1225 | { | 1225 | { |
1226 | struct ib_sa_device *sa_dev = ib_get_client_data(device, &sa_client); | 1226 | struct ib_sa_device *sa_dev = client_data; |
1227 | int i; | 1227 | int i; |
1228 | 1228 | ||
1229 | if (!sa_dev) | 1229 | if (!sa_dev) |
diff --git a/drivers/infiniband/core/ucm.c b/drivers/infiniband/core/ucm.c index 009481073644..8cde48b96f19 100644 --- a/drivers/infiniband/core/ucm.c +++ b/drivers/infiniband/core/ucm.c | |||
@@ -109,7 +109,7 @@ enum { | |||
109 | #define IB_UCM_BASE_DEV MKDEV(IB_UCM_MAJOR, IB_UCM_BASE_MINOR) | 109 | #define IB_UCM_BASE_DEV MKDEV(IB_UCM_MAJOR, IB_UCM_BASE_MINOR) |
110 | 110 | ||
111 | static void ib_ucm_add_one(struct ib_device *device); | 111 | static void ib_ucm_add_one(struct ib_device *device); |
112 | static void ib_ucm_remove_one(struct ib_device *device); | 112 | static void ib_ucm_remove_one(struct ib_device *device, void *client_data); |
113 | 113 | ||
114 | static struct ib_client ucm_client = { | 114 | static struct ib_client ucm_client = { |
115 | .name = "ucm", | 115 | .name = "ucm", |
@@ -1310,9 +1310,9 @@ err: | |||
1310 | return; | 1310 | return; |
1311 | } | 1311 | } |
1312 | 1312 | ||
1313 | static void ib_ucm_remove_one(struct ib_device *device) | 1313 | static void ib_ucm_remove_one(struct ib_device *device, void *client_data) |
1314 | { | 1314 | { |
1315 | struct ib_ucm_device *ucm_dev = ib_get_client_data(device, &ucm_client); | 1315 | struct ib_ucm_device *ucm_dev = client_data; |
1316 | 1316 | ||
1317 | if (!ucm_dev) | 1317 | if (!ucm_dev) |
1318 | return; | 1318 | return; |
diff --git a/drivers/infiniband/core/user_mad.c b/drivers/infiniband/core/user_mad.c index 35567fffaa4e..57f281f8d686 100644 --- a/drivers/infiniband/core/user_mad.c +++ b/drivers/infiniband/core/user_mad.c | |||
@@ -133,7 +133,7 @@ static DEFINE_SPINLOCK(port_lock); | |||
133 | static DECLARE_BITMAP(dev_map, IB_UMAD_MAX_PORTS); | 133 | static DECLARE_BITMAP(dev_map, IB_UMAD_MAX_PORTS); |
134 | 134 | ||
135 | static void ib_umad_add_one(struct ib_device *device); | 135 | static void ib_umad_add_one(struct ib_device *device); |
136 | static void ib_umad_remove_one(struct ib_device *device); | 136 | static void ib_umad_remove_one(struct ib_device *device, void *client_data); |
137 | 137 | ||
138 | static void ib_umad_release_dev(struct kobject *kobj) | 138 | static void ib_umad_release_dev(struct kobject *kobj) |
139 | { | 139 | { |
@@ -1322,9 +1322,9 @@ free: | |||
1322 | kobject_put(&umad_dev->kobj); | 1322 | kobject_put(&umad_dev->kobj); |
1323 | } | 1323 | } |
1324 | 1324 | ||
1325 | static void ib_umad_remove_one(struct ib_device *device) | 1325 | static void ib_umad_remove_one(struct ib_device *device, void *client_data) |
1326 | { | 1326 | { |
1327 | struct ib_umad_device *umad_dev = ib_get_client_data(device, &umad_client); | 1327 | struct ib_umad_device *umad_dev = client_data; |
1328 | int i; | 1328 | int i; |
1329 | 1329 | ||
1330 | if (!umad_dev) | 1330 | if (!umad_dev) |
diff --git a/drivers/infiniband/core/uverbs_main.c b/drivers/infiniband/core/uverbs_main.c index f6eef2da7097..46c92294afa5 100644 --- a/drivers/infiniband/core/uverbs_main.c +++ b/drivers/infiniband/core/uverbs_main.c | |||
@@ -128,7 +128,7 @@ static int (*uverbs_ex_cmd_table[])(struct ib_uverbs_file *file, | |||
128 | }; | 128 | }; |
129 | 129 | ||
130 | static void ib_uverbs_add_one(struct ib_device *device); | 130 | static void ib_uverbs_add_one(struct ib_device *device); |
131 | static void ib_uverbs_remove_one(struct ib_device *device); | 131 | static void ib_uverbs_remove_one(struct ib_device *device, void *client_data); |
132 | 132 | ||
133 | static void ib_uverbs_release_dev(struct kref *ref) | 133 | static void ib_uverbs_release_dev(struct kref *ref) |
134 | { | 134 | { |
@@ -948,9 +948,9 @@ err: | |||
948 | return; | 948 | return; |
949 | } | 949 | } |
950 | 950 | ||
951 | static void ib_uverbs_remove_one(struct ib_device *device) | 951 | static void ib_uverbs_remove_one(struct ib_device *device, void *client_data) |
952 | { | 952 | { |
953 | struct ib_uverbs_device *uverbs_dev = ib_get_client_data(device, &uverbs_client); | 953 | struct ib_uverbs_device *uverbs_dev = client_data; |
954 | 954 | ||
955 | if (!uverbs_dev) | 955 | if (!uverbs_dev) |
956 | return; | 956 | return; |