diff options
author | Dan Williams <dan.j.williams@intel.com> | 2016-07-23 02:46:08 -0400 |
---|---|---|
committer | Dan Williams <dan.j.williams@intel.com> | 2016-07-23 14:06:33 -0400 |
commit | 18515942d61bdfd4b31ea13f9fbb9c18650c6818 (patch) | |
tree | 6ae9bcd75578fbe43d3c07df9408acf2c78e16a8 | |
parent | 5bf0b6e1af98a012e8871a89c2082353c3f70fee (diff) |
libnvdimm: register nvdimm_bus devices with an nd_bus driver
A recent effort to add a new nvdimm bus provider attribute highlighted a
race between interrogating nvdimm_bus->nd_desc and nvdimm_bus tear down.
The typical way to handle these races is to take the device_lock() in
the attribute method and validate that the device is still active. In
order for a device to be 'active' it needs to be associated with a
driver. So, we create the small boilerplate for a driver and register
nvdimm_bus devices on the 'nvdimm_bus_type' bus.
A result of this change is that ndbusX devices now appear under
/sys/bus/nd/devices. In fact this makes /sys/class/nd somewhat
redundant, but removing that will need to take a long deprecation period
given its use by ndctl binaries in the field.
This change naturally pulls code from drivers/nvdimm/core.c to
drivers/nvdimm/bus.c, so it is a nice code organization clean-up as
well.
Cc: Vishal Verma <vishal.l.verma@intel.com>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
-rw-r--r-- | drivers/nvdimm/bus.c | 188 | ||||
-rw-r--r-- | drivers/nvdimm/core.c | 127 |
2 files changed, 181 insertions, 134 deletions
diff --git a/drivers/nvdimm/bus.c b/drivers/nvdimm/bus.c index 275dd5c0a301..46d7e555b044 100644 --- a/drivers/nvdimm/bus.c +++ b/drivers/nvdimm/bus.c | |||
@@ -31,6 +31,7 @@ | |||
31 | int nvdimm_major; | 31 | int nvdimm_major; |
32 | static int nvdimm_bus_major; | 32 | static int nvdimm_bus_major; |
33 | static struct class *nd_class; | 33 | static struct class *nd_class; |
34 | static DEFINE_IDA(nd_ida); | ||
34 | 35 | ||
35 | static int to_nd_device_type(struct device *dev) | 36 | static int to_nd_device_type(struct device *dev) |
36 | { | 37 | { |
@@ -60,13 +61,6 @@ static int nvdimm_bus_uevent(struct device *dev, struct kobj_uevent_env *env) | |||
60 | to_nd_device_type(dev)); | 61 | to_nd_device_type(dev)); |
61 | } | 62 | } |
62 | 63 | ||
63 | static int nvdimm_bus_match(struct device *dev, struct device_driver *drv) | ||
64 | { | ||
65 | struct nd_device_driver *nd_drv = to_nd_device_driver(drv); | ||
66 | |||
67 | return !!test_bit(to_nd_device_type(dev), &nd_drv->type); | ||
68 | } | ||
69 | |||
70 | static struct module *to_bus_provider(struct device *dev) | 64 | static struct module *to_bus_provider(struct device *dev) |
71 | { | 65 | { |
72 | /* pin bus providers while regions are enabled */ | 66 | /* pin bus providers while regions are enabled */ |
@@ -223,6 +217,8 @@ long nvdimm_clear_poison(struct device *dev, phys_addr_t phys, | |||
223 | } | 217 | } |
224 | EXPORT_SYMBOL_GPL(nvdimm_clear_poison); | 218 | EXPORT_SYMBOL_GPL(nvdimm_clear_poison); |
225 | 219 | ||
220 | static int nvdimm_bus_match(struct device *dev, struct device_driver *drv); | ||
221 | |||
226 | static struct bus_type nvdimm_bus_type = { | 222 | static struct bus_type nvdimm_bus_type = { |
227 | .name = "nd", | 223 | .name = "nd", |
228 | .uevent = nvdimm_bus_uevent, | 224 | .uevent = nvdimm_bus_uevent, |
@@ -232,6 +228,176 @@ static struct bus_type nvdimm_bus_type = { | |||
232 | .shutdown = nvdimm_bus_shutdown, | 228 | .shutdown = nvdimm_bus_shutdown, |
233 | }; | 229 | }; |
234 | 230 | ||
231 | static void nvdimm_bus_release(struct device *dev) | ||
232 | { | ||
233 | struct nvdimm_bus *nvdimm_bus; | ||
234 | |||
235 | nvdimm_bus = container_of(dev, struct nvdimm_bus, dev); | ||
236 | ida_simple_remove(&nd_ida, nvdimm_bus->id); | ||
237 | kfree(nvdimm_bus); | ||
238 | } | ||
239 | |||
240 | static bool is_nvdimm_bus(struct device *dev) | ||
241 | { | ||
242 | return dev->release == nvdimm_bus_release; | ||
243 | } | ||
244 | |||
245 | struct nvdimm_bus *walk_to_nvdimm_bus(struct device *nd_dev) | ||
246 | { | ||
247 | struct device *dev; | ||
248 | |||
249 | for (dev = nd_dev; dev; dev = dev->parent) | ||
250 | if (is_nvdimm_bus(dev)) | ||
251 | break; | ||
252 | dev_WARN_ONCE(nd_dev, !dev, "invalid dev, not on nd bus\n"); | ||
253 | if (dev) | ||
254 | return to_nvdimm_bus(dev); | ||
255 | return NULL; | ||
256 | } | ||
257 | |||
258 | struct nvdimm_bus *to_nvdimm_bus(struct device *dev) | ||
259 | { | ||
260 | struct nvdimm_bus *nvdimm_bus; | ||
261 | |||
262 | nvdimm_bus = container_of(dev, struct nvdimm_bus, dev); | ||
263 | WARN_ON(!is_nvdimm_bus(dev)); | ||
264 | return nvdimm_bus; | ||
265 | } | ||
266 | EXPORT_SYMBOL_GPL(to_nvdimm_bus); | ||
267 | |||
268 | struct nvdimm_bus *nvdimm_bus_register(struct device *parent, | ||
269 | struct nvdimm_bus_descriptor *nd_desc) | ||
270 | { | ||
271 | struct nvdimm_bus *nvdimm_bus; | ||
272 | int rc; | ||
273 | |||
274 | nvdimm_bus = kzalloc(sizeof(*nvdimm_bus), GFP_KERNEL); | ||
275 | if (!nvdimm_bus) | ||
276 | return NULL; | ||
277 | INIT_LIST_HEAD(&nvdimm_bus->list); | ||
278 | INIT_LIST_HEAD(&nvdimm_bus->mapping_list); | ||
279 | INIT_LIST_HEAD(&nvdimm_bus->poison_list); | ||
280 | init_waitqueue_head(&nvdimm_bus->probe_wait); | ||
281 | nvdimm_bus->id = ida_simple_get(&nd_ida, 0, 0, GFP_KERNEL); | ||
282 | mutex_init(&nvdimm_bus->reconfig_mutex); | ||
283 | if (nvdimm_bus->id < 0) { | ||
284 | kfree(nvdimm_bus); | ||
285 | return NULL; | ||
286 | } | ||
287 | nvdimm_bus->nd_desc = nd_desc; | ||
288 | nvdimm_bus->dev.parent = parent; | ||
289 | nvdimm_bus->dev.release = nvdimm_bus_release; | ||
290 | nvdimm_bus->dev.groups = nd_desc->attr_groups; | ||
291 | nvdimm_bus->dev.bus = &nvdimm_bus_type; | ||
292 | dev_set_name(&nvdimm_bus->dev, "ndbus%d", nvdimm_bus->id); | ||
293 | rc = device_register(&nvdimm_bus->dev); | ||
294 | if (rc) { | ||
295 | dev_dbg(&nvdimm_bus->dev, "registration failed: %d\n", rc); | ||
296 | goto err; | ||
297 | } | ||
298 | |||
299 | return nvdimm_bus; | ||
300 | err: | ||
301 | put_device(&nvdimm_bus->dev); | ||
302 | return NULL; | ||
303 | } | ||
304 | EXPORT_SYMBOL_GPL(nvdimm_bus_register); | ||
305 | |||
306 | void nvdimm_bus_unregister(struct nvdimm_bus *nvdimm_bus) | ||
307 | { | ||
308 | if (!nvdimm_bus) | ||
309 | return; | ||
310 | device_unregister(&nvdimm_bus->dev); | ||
311 | } | ||
312 | EXPORT_SYMBOL_GPL(nvdimm_bus_unregister); | ||
313 | |||
314 | static int child_unregister(struct device *dev, void *data) | ||
315 | { | ||
316 | /* | ||
317 | * the singular ndctl class device per bus needs to be | ||
318 | * "device_destroy"ed, so skip it here | ||
319 | * | ||
320 | * i.e. remove classless children | ||
321 | */ | ||
322 | if (dev->class) | ||
323 | /* pass */; | ||
324 | else | ||
325 | nd_device_unregister(dev, ND_SYNC); | ||
326 | return 0; | ||
327 | } | ||
328 | |||
329 | static void free_poison_list(struct list_head *poison_list) | ||
330 | { | ||
331 | struct nd_poison *pl, *next; | ||
332 | |||
333 | list_for_each_entry_safe(pl, next, poison_list, list) { | ||
334 | list_del(&pl->list); | ||
335 | kfree(pl); | ||
336 | } | ||
337 | list_del_init(poison_list); | ||
338 | } | ||
339 | |||
340 | static int nd_bus_remove(struct device *dev) | ||
341 | { | ||
342 | struct nvdimm_bus *nvdimm_bus = to_nvdimm_bus(dev); | ||
343 | |||
344 | mutex_lock(&nvdimm_bus_list_mutex); | ||
345 | list_del_init(&nvdimm_bus->list); | ||
346 | mutex_unlock(&nvdimm_bus_list_mutex); | ||
347 | |||
348 | nd_synchronize(); | ||
349 | device_for_each_child(&nvdimm_bus->dev, NULL, child_unregister); | ||
350 | |||
351 | nvdimm_bus_lock(&nvdimm_bus->dev); | ||
352 | free_poison_list(&nvdimm_bus->poison_list); | ||
353 | nvdimm_bus_unlock(&nvdimm_bus->dev); | ||
354 | |||
355 | nvdimm_bus_destroy_ndctl(nvdimm_bus); | ||
356 | |||
357 | return 0; | ||
358 | } | ||
359 | |||
360 | static int nd_bus_probe(struct device *dev) | ||
361 | { | ||
362 | struct nvdimm_bus *nvdimm_bus = to_nvdimm_bus(dev); | ||
363 | int rc; | ||
364 | |||
365 | rc = nvdimm_bus_create_ndctl(nvdimm_bus); | ||
366 | if (rc) | ||
367 | return rc; | ||
368 | |||
369 | mutex_lock(&nvdimm_bus_list_mutex); | ||
370 | list_add_tail(&nvdimm_bus->list, &nvdimm_bus_list); | ||
371 | mutex_unlock(&nvdimm_bus_list_mutex); | ||
372 | |||
373 | /* enable bus provider attributes to look up their local context */ | ||
374 | dev_set_drvdata(dev, nvdimm_bus->nd_desc); | ||
375 | |||
376 | return 0; | ||
377 | } | ||
378 | |||
379 | static struct nd_device_driver nd_bus_driver = { | ||
380 | .probe = nd_bus_probe, | ||
381 | .remove = nd_bus_remove, | ||
382 | .drv = { | ||
383 | .name = "nd_bus", | ||
384 | .suppress_bind_attrs = true, | ||
385 | .bus = &nvdimm_bus_type, | ||
386 | .owner = THIS_MODULE, | ||
387 | .mod_name = KBUILD_MODNAME, | ||
388 | }, | ||
389 | }; | ||
390 | |||
391 | static int nvdimm_bus_match(struct device *dev, struct device_driver *drv) | ||
392 | { | ||
393 | struct nd_device_driver *nd_drv = to_nd_device_driver(drv); | ||
394 | |||
395 | if (is_nvdimm_bus(dev) && nd_drv == &nd_bus_driver) | ||
396 | return true; | ||
397 | |||
398 | return !!test_bit(to_nd_device_type(dev), &nd_drv->type); | ||
399 | } | ||
400 | |||
235 | static ASYNC_DOMAIN_EXCLUSIVE(nd_async_domain); | 401 | static ASYNC_DOMAIN_EXCLUSIVE(nd_async_domain); |
236 | 402 | ||
237 | void nd_synchronize(void) | 403 | void nd_synchronize(void) |
@@ -864,8 +1030,14 @@ int __init nvdimm_bus_init(void) | |||
864 | goto err_class; | 1030 | goto err_class; |
865 | } | 1031 | } |
866 | 1032 | ||
1033 | rc = driver_register(&nd_bus_driver.drv); | ||
1034 | if (rc) | ||
1035 | goto err_nd_bus; | ||
1036 | |||
867 | return 0; | 1037 | return 0; |
868 | 1038 | ||
1039 | err_nd_bus: | ||
1040 | class_destroy(nd_class); | ||
869 | err_class: | 1041 | err_class: |
870 | unregister_chrdev(nvdimm_major, "dimmctl"); | 1042 | unregister_chrdev(nvdimm_major, "dimmctl"); |
871 | err_dimm_chrdev: | 1043 | err_dimm_chrdev: |
@@ -878,8 +1050,10 @@ int __init nvdimm_bus_init(void) | |||
878 | 1050 | ||
879 | void nvdimm_bus_exit(void) | 1051 | void nvdimm_bus_exit(void) |
880 | { | 1052 | { |
1053 | driver_unregister(&nd_bus_driver.drv); | ||
881 | class_destroy(nd_class); | 1054 | class_destroy(nd_class); |
882 | unregister_chrdev(nvdimm_bus_major, "ndctl"); | 1055 | unregister_chrdev(nvdimm_bus_major, "ndctl"); |
883 | unregister_chrdev(nvdimm_major, "dimmctl"); | 1056 | unregister_chrdev(nvdimm_major, "dimmctl"); |
884 | bus_unregister(&nvdimm_bus_type); | 1057 | bus_unregister(&nvdimm_bus_type); |
1058 | ida_destroy(&nd_ida); | ||
885 | } | 1059 | } |
diff --git a/drivers/nvdimm/core.c b/drivers/nvdimm/core.c index e8528756f54f..2c98f958fabb 100644 --- a/drivers/nvdimm/core.c +++ b/drivers/nvdimm/core.c | |||
@@ -26,7 +26,6 @@ | |||
26 | 26 | ||
27 | LIST_HEAD(nvdimm_bus_list); | 27 | LIST_HEAD(nvdimm_bus_list); |
28 | DEFINE_MUTEX(nvdimm_bus_list_mutex); | 28 | DEFINE_MUTEX(nvdimm_bus_list_mutex); |
29 | static DEFINE_IDA(nd_ida); | ||
30 | 29 | ||
31 | void nvdimm_bus_lock(struct device *dev) | 30 | void nvdimm_bus_lock(struct device *dev) |
32 | { | 31 | { |
@@ -195,25 +194,6 @@ u64 nd_fletcher64(void *addr, size_t len, bool le) | |||
195 | } | 194 | } |
196 | EXPORT_SYMBOL_GPL(nd_fletcher64); | 195 | EXPORT_SYMBOL_GPL(nd_fletcher64); |
197 | 196 | ||
198 | static void nvdimm_bus_release(struct device *dev) | ||
199 | { | ||
200 | struct nvdimm_bus *nvdimm_bus; | ||
201 | |||
202 | nvdimm_bus = container_of(dev, struct nvdimm_bus, dev); | ||
203 | ida_simple_remove(&nd_ida, nvdimm_bus->id); | ||
204 | kfree(nvdimm_bus); | ||
205 | } | ||
206 | |||
207 | struct nvdimm_bus *to_nvdimm_bus(struct device *dev) | ||
208 | { | ||
209 | struct nvdimm_bus *nvdimm_bus; | ||
210 | |||
211 | nvdimm_bus = container_of(dev, struct nvdimm_bus, dev); | ||
212 | WARN_ON(nvdimm_bus->dev.release != nvdimm_bus_release); | ||
213 | return nvdimm_bus; | ||
214 | } | ||
215 | EXPORT_SYMBOL_GPL(to_nvdimm_bus); | ||
216 | |||
217 | struct nvdimm_bus_descriptor *to_nd_desc(struct nvdimm_bus *nvdimm_bus) | 197 | struct nvdimm_bus_descriptor *to_nd_desc(struct nvdimm_bus *nvdimm_bus) |
218 | { | 198 | { |
219 | /* struct nvdimm_bus definition is private to libnvdimm */ | 199 | /* struct nvdimm_bus definition is private to libnvdimm */ |
@@ -221,19 +201,6 @@ struct nvdimm_bus_descriptor *to_nd_desc(struct nvdimm_bus *nvdimm_bus) | |||
221 | } | 201 | } |
222 | EXPORT_SYMBOL_GPL(to_nd_desc); | 202 | EXPORT_SYMBOL_GPL(to_nd_desc); |
223 | 203 | ||
224 | struct nvdimm_bus *walk_to_nvdimm_bus(struct device *nd_dev) | ||
225 | { | ||
226 | struct device *dev; | ||
227 | |||
228 | for (dev = nd_dev; dev; dev = dev->parent) | ||
229 | if (dev->release == nvdimm_bus_release) | ||
230 | break; | ||
231 | dev_WARN_ONCE(nd_dev, !dev, "invalid dev, not on nd bus\n"); | ||
232 | if (dev) | ||
233 | return to_nvdimm_bus(dev); | ||
234 | return NULL; | ||
235 | } | ||
236 | |||
237 | static bool is_uuid_sep(char sep) | 204 | static bool is_uuid_sep(char sep) |
238 | { | 205 | { |
239 | if (sep == '\n' || sep == '-' || sep == ':' || sep == '\0') | 206 | if (sep == '\n' || sep == '-' || sep == ':' || sep == '\0') |
@@ -447,51 +414,6 @@ struct attribute_group nvdimm_bus_attribute_group = { | |||
447 | }; | 414 | }; |
448 | EXPORT_SYMBOL_GPL(nvdimm_bus_attribute_group); | 415 | EXPORT_SYMBOL_GPL(nvdimm_bus_attribute_group); |
449 | 416 | ||
450 | struct nvdimm_bus *nvdimm_bus_register(struct device *parent, | ||
451 | struct nvdimm_bus_descriptor *nd_desc) | ||
452 | { | ||
453 | struct nvdimm_bus *nvdimm_bus; | ||
454 | int rc; | ||
455 | |||
456 | nvdimm_bus = kzalloc(sizeof(*nvdimm_bus), GFP_KERNEL); | ||
457 | if (!nvdimm_bus) | ||
458 | return NULL; | ||
459 | INIT_LIST_HEAD(&nvdimm_bus->list); | ||
460 | INIT_LIST_HEAD(&nvdimm_bus->mapping_list); | ||
461 | INIT_LIST_HEAD(&nvdimm_bus->poison_list); | ||
462 | init_waitqueue_head(&nvdimm_bus->probe_wait); | ||
463 | nvdimm_bus->id = ida_simple_get(&nd_ida, 0, 0, GFP_KERNEL); | ||
464 | mutex_init(&nvdimm_bus->reconfig_mutex); | ||
465 | if (nvdimm_bus->id < 0) { | ||
466 | kfree(nvdimm_bus); | ||
467 | return NULL; | ||
468 | } | ||
469 | nvdimm_bus->nd_desc = nd_desc; | ||
470 | nvdimm_bus->dev.parent = parent; | ||
471 | nvdimm_bus->dev.release = nvdimm_bus_release; | ||
472 | nvdimm_bus->dev.groups = nd_desc->attr_groups; | ||
473 | dev_set_name(&nvdimm_bus->dev, "ndbus%d", nvdimm_bus->id); | ||
474 | rc = device_register(&nvdimm_bus->dev); | ||
475 | if (rc) { | ||
476 | dev_dbg(&nvdimm_bus->dev, "registration failed: %d\n", rc); | ||
477 | goto err; | ||
478 | } | ||
479 | |||
480 | rc = nvdimm_bus_create_ndctl(nvdimm_bus); | ||
481 | if (rc) | ||
482 | goto err; | ||
483 | |||
484 | mutex_lock(&nvdimm_bus_list_mutex); | ||
485 | list_add_tail(&nvdimm_bus->list, &nvdimm_bus_list); | ||
486 | mutex_unlock(&nvdimm_bus_list_mutex); | ||
487 | |||
488 | return nvdimm_bus; | ||
489 | err: | ||
490 | put_device(&nvdimm_bus->dev); | ||
491 | return NULL; | ||
492 | } | ||
493 | EXPORT_SYMBOL_GPL(nvdimm_bus_register); | ||
494 | |||
495 | static void set_badblock(struct badblocks *bb, sector_t s, int num) | 417 | static void set_badblock(struct badblocks *bb, sector_t s, int num) |
496 | { | 418 | { |
497 | dev_dbg(bb->dev, "Found a poison range (0x%llx, 0x%llx)\n", | 419 | dev_dbg(bb->dev, "Found a poison range (0x%llx, 0x%llx)\n", |
@@ -667,54 +589,6 @@ int nvdimm_bus_add_poison(struct nvdimm_bus *nvdimm_bus, u64 addr, u64 length) | |||
667 | } | 589 | } |
668 | EXPORT_SYMBOL_GPL(nvdimm_bus_add_poison); | 590 | EXPORT_SYMBOL_GPL(nvdimm_bus_add_poison); |
669 | 591 | ||
670 | static void free_poison_list(struct list_head *poison_list) | ||
671 | { | ||
672 | struct nd_poison *pl, *next; | ||
673 | |||
674 | list_for_each_entry_safe(pl, next, poison_list, list) { | ||
675 | list_del(&pl->list); | ||
676 | kfree(pl); | ||
677 | } | ||
678 | list_del_init(poison_list); | ||
679 | } | ||
680 | |||
681 | static int child_unregister(struct device *dev, void *data) | ||
682 | { | ||
683 | /* | ||
684 | * the singular ndctl class device per bus needs to be | ||
685 | * "device_destroy"ed, so skip it here | ||
686 | * | ||
687 | * i.e. remove classless children | ||
688 | */ | ||
689 | if (dev->class) | ||
690 | /* pass */; | ||
691 | else | ||
692 | nd_device_unregister(dev, ND_SYNC); | ||
693 | return 0; | ||
694 | } | ||
695 | |||
696 | void nvdimm_bus_unregister(struct nvdimm_bus *nvdimm_bus) | ||
697 | { | ||
698 | if (!nvdimm_bus) | ||
699 | return; | ||
700 | |||
701 | mutex_lock(&nvdimm_bus_list_mutex); | ||
702 | list_del_init(&nvdimm_bus->list); | ||
703 | mutex_unlock(&nvdimm_bus_list_mutex); | ||
704 | |||
705 | nd_synchronize(); | ||
706 | device_for_each_child(&nvdimm_bus->dev, NULL, child_unregister); | ||
707 | |||
708 | nvdimm_bus_lock(&nvdimm_bus->dev); | ||
709 | free_poison_list(&nvdimm_bus->poison_list); | ||
710 | nvdimm_bus_unlock(&nvdimm_bus->dev); | ||
711 | |||
712 | nvdimm_bus_destroy_ndctl(nvdimm_bus); | ||
713 | |||
714 | device_unregister(&nvdimm_bus->dev); | ||
715 | } | ||
716 | EXPORT_SYMBOL_GPL(nvdimm_bus_unregister); | ||
717 | |||
718 | #ifdef CONFIG_BLK_DEV_INTEGRITY | 592 | #ifdef CONFIG_BLK_DEV_INTEGRITY |
719 | int nd_integrity_init(struct gendisk *disk, unsigned long meta_size) | 593 | int nd_integrity_init(struct gendisk *disk, unsigned long meta_size) |
720 | { | 594 | { |
@@ -773,7 +647,6 @@ static __exit void libnvdimm_exit(void) | |||
773 | nvdimm_bus_exit(); | 647 | nvdimm_bus_exit(); |
774 | nd_region_devs_exit(); | 648 | nd_region_devs_exit(); |
775 | nvdimm_devs_exit(); | 649 | nvdimm_devs_exit(); |
776 | ida_destroy(&nd_ida); | ||
777 | } | 650 | } |
778 | 651 | ||
779 | MODULE_LICENSE("GPL v2"); | 652 | MODULE_LICENSE("GPL v2"); |