diff options
-rw-r--r-- | fs/block_dev.c | 297 | ||||
-rw-r--r-- | include/linux/fs.h | 10 |
2 files changed, 307 insertions, 0 deletions
diff --git a/fs/block_dev.c b/fs/block_dev.c index 5983d42df015..3f36df7e037c 100644 --- a/fs/block_dev.c +++ b/fs/block_dev.c | |||
@@ -266,6 +266,9 @@ static void init_once(void * foo, kmem_cache_t * cachep, unsigned long flags) | |||
266 | mutex_init(&bdev->bd_mount_mutex); | 266 | mutex_init(&bdev->bd_mount_mutex); |
267 | INIT_LIST_HEAD(&bdev->bd_inodes); | 267 | INIT_LIST_HEAD(&bdev->bd_inodes); |
268 | INIT_LIST_HEAD(&bdev->bd_list); | 268 | INIT_LIST_HEAD(&bdev->bd_list); |
269 | #ifdef CONFIG_SYSFS | ||
270 | INIT_LIST_HEAD(&bdev->bd_holder_list); | ||
271 | #endif | ||
269 | inode_init_once(&ei->vfs_inode); | 272 | inode_init_once(&ei->vfs_inode); |
270 | } | 273 | } |
271 | } | 274 | } |
@@ -490,6 +493,300 @@ void bd_release(struct block_device *bdev) | |||
490 | 493 | ||
491 | EXPORT_SYMBOL(bd_release); | 494 | EXPORT_SYMBOL(bd_release); |
492 | 495 | ||
496 | #ifdef CONFIG_SYSFS | ||
497 | /* | ||
498 | * Functions for bd_claim_by_kobject / bd_release_from_kobject | ||
499 | * | ||
500 | * If a kobject is passed to bd_claim_by_kobject() | ||
501 | * and the kobject has a parent directory, | ||
502 | * following symlinks are created: | ||
503 | * o from the kobject to the claimed bdev | ||
504 | * o from "holders" directory of the bdev to the parent of the kobject | ||
505 | * bd_release_from_kobject() removes these symlinks. | ||
506 | * | ||
507 | * Example: | ||
508 | * If /dev/dm-0 maps to /dev/sda, kobject corresponding to | ||
509 | * /sys/block/dm-0/slaves is passed to bd_claim_by_kobject(), then: | ||
510 | * /sys/block/dm-0/slaves/sda --> /sys/block/sda | ||
511 | * /sys/block/sda/holders/dm-0 --> /sys/block/dm-0 | ||
512 | */ | ||
513 | |||
514 | static struct kobject *bdev_get_kobj(struct block_device *bdev) | ||
515 | { | ||
516 | if (bdev->bd_contains != bdev) | ||
517 | return kobject_get(&bdev->bd_part->kobj); | ||
518 | else | ||
519 | return kobject_get(&bdev->bd_disk->kobj); | ||
520 | } | ||
521 | |||
522 | static struct kobject *bdev_get_holder(struct block_device *bdev) | ||
523 | { | ||
524 | if (bdev->bd_contains != bdev) | ||
525 | return kobject_get(bdev->bd_part->holder_dir); | ||
526 | else | ||
527 | return kobject_get(bdev->bd_disk->holder_dir); | ||
528 | } | ||
529 | |||
530 | static void add_symlink(struct kobject *from, struct kobject *to) | ||
531 | { | ||
532 | if (!from || !to) | ||
533 | return; | ||
534 | sysfs_create_link(from, to, kobject_name(to)); | ||
535 | } | ||
536 | |||
537 | static void del_symlink(struct kobject *from, struct kobject *to) | ||
538 | { | ||
539 | if (!from || !to) | ||
540 | return; | ||
541 | sysfs_remove_link(from, kobject_name(to)); | ||
542 | } | ||
543 | |||
544 | /* | ||
545 | * 'struct bd_holder' contains pointers to kobjects symlinked by | ||
546 | * bd_claim_by_kobject. | ||
547 | * It's connected to bd_holder_list which is protected by bdev->bd_sem. | ||
548 | */ | ||
549 | struct bd_holder { | ||
550 | struct list_head list; /* chain of holders of the bdev */ | ||
551 | int count; /* references from the holder */ | ||
552 | struct kobject *sdir; /* holder object, e.g. "/block/dm-0/slaves" */ | ||
553 | struct kobject *hdev; /* e.g. "/block/dm-0" */ | ||
554 | struct kobject *hdir; /* e.g. "/block/sda/holders" */ | ||
555 | struct kobject *sdev; /* e.g. "/block/sda" */ | ||
556 | }; | ||
557 | |||
558 | /* | ||
559 | * Get references of related kobjects at once. | ||
560 | * Returns 1 on success. 0 on failure. | ||
561 | * | ||
562 | * Should call bd_holder_release_dirs() after successful use. | ||
563 | */ | ||
564 | static int bd_holder_grab_dirs(struct block_device *bdev, | ||
565 | struct bd_holder *bo) | ||
566 | { | ||
567 | if (!bdev || !bo) | ||
568 | return 0; | ||
569 | |||
570 | bo->sdir = kobject_get(bo->sdir); | ||
571 | if (!bo->sdir) | ||
572 | return 0; | ||
573 | |||
574 | bo->hdev = kobject_get(bo->sdir->parent); | ||
575 | if (!bo->hdev) | ||
576 | goto fail_put_sdir; | ||
577 | |||
578 | bo->sdev = bdev_get_kobj(bdev); | ||
579 | if (!bo->sdev) | ||
580 | goto fail_put_hdev; | ||
581 | |||
582 | bo->hdir = bdev_get_holder(bdev); | ||
583 | if (!bo->hdir) | ||
584 | goto fail_put_sdev; | ||
585 | |||
586 | return 1; | ||
587 | |||
588 | fail_put_sdev: | ||
589 | kobject_put(bo->sdev); | ||
590 | fail_put_hdev: | ||
591 | kobject_put(bo->hdev); | ||
592 | fail_put_sdir: | ||
593 | kobject_put(bo->sdir); | ||
594 | |||
595 | return 0; | ||
596 | } | ||
597 | |||
598 | /* Put references of related kobjects at once. */ | ||
599 | static void bd_holder_release_dirs(struct bd_holder *bo) | ||
600 | { | ||
601 | kobject_put(bo->hdir); | ||
602 | kobject_put(bo->sdev); | ||
603 | kobject_put(bo->hdev); | ||
604 | kobject_put(bo->sdir); | ||
605 | } | ||
606 | |||
607 | static struct bd_holder *alloc_bd_holder(struct kobject *kobj) | ||
608 | { | ||
609 | struct bd_holder *bo; | ||
610 | |||
611 | bo = kzalloc(sizeof(*bo), GFP_KERNEL); | ||
612 | if (!bo) | ||
613 | return NULL; | ||
614 | |||
615 | bo->count = 1; | ||
616 | bo->sdir = kobj; | ||
617 | |||
618 | return bo; | ||
619 | } | ||
620 | |||
621 | static void free_bd_holder(struct bd_holder *bo) | ||
622 | { | ||
623 | kfree(bo); | ||
624 | } | ||
625 | |||
626 | /** | ||
627 | * add_bd_holder - create sysfs symlinks for bd_claim() relationship | ||
628 | * | ||
629 | * @bdev: block device to be bd_claimed | ||
630 | * @bo: preallocated and initialized by alloc_bd_holder() | ||
631 | * | ||
632 | * If there is no matching entry with @bo in @bdev->bd_holder_list, | ||
633 | * add @bo to the list, create symlinks. | ||
634 | * | ||
635 | * Returns 1 if @bo was added to the list. | ||
636 | * Returns 0 if @bo wasn't used by any reason and should be freed. | ||
637 | */ | ||
638 | static int add_bd_holder(struct block_device *bdev, struct bd_holder *bo) | ||
639 | { | ||
640 | struct bd_holder *tmp; | ||
641 | |||
642 | if (!bo) | ||
643 | return 0; | ||
644 | |||
645 | list_for_each_entry(tmp, &bdev->bd_holder_list, list) { | ||
646 | if (tmp->sdir == bo->sdir) { | ||
647 | tmp->count++; | ||
648 | return 0; | ||
649 | } | ||
650 | } | ||
651 | |||
652 | if (!bd_holder_grab_dirs(bdev, bo)) | ||
653 | return 0; | ||
654 | |||
655 | add_symlink(bo->sdir, bo->sdev); | ||
656 | add_symlink(bo->hdir, bo->hdev); | ||
657 | list_add_tail(&bo->list, &bdev->bd_holder_list); | ||
658 | return 1; | ||
659 | } | ||
660 | |||
661 | /** | ||
662 | * del_bd_holder - delete sysfs symlinks for bd_claim() relationship | ||
663 | * | ||
664 | * @bdev: block device to be bd_claimed | ||
665 | * @kobj: holder's kobject | ||
666 | * | ||
667 | * If there is matching entry with @kobj in @bdev->bd_holder_list | ||
668 | * and no other bd_claim() from the same kobject, | ||
669 | * remove the struct bd_holder from the list, delete symlinks for it. | ||
670 | * | ||
671 | * Returns a pointer to the struct bd_holder when it's removed from the list | ||
672 | * and ready to be freed. | ||
673 | * Returns NULL if matching claim isn't found or there is other bd_claim() | ||
674 | * by the same kobject. | ||
675 | */ | ||
676 | static struct bd_holder *del_bd_holder(struct block_device *bdev, | ||
677 | struct kobject *kobj) | ||
678 | { | ||
679 | struct bd_holder *bo; | ||
680 | |||
681 | list_for_each_entry(bo, &bdev->bd_holder_list, list) { | ||
682 | if (bo->sdir == kobj) { | ||
683 | bo->count--; | ||
684 | BUG_ON(bo->count < 0); | ||
685 | if (!bo->count) { | ||
686 | list_del(&bo->list); | ||
687 | del_symlink(bo->sdir, bo->sdev); | ||
688 | del_symlink(bo->hdir, bo->hdev); | ||
689 | bd_holder_release_dirs(bo); | ||
690 | return bo; | ||
691 | } | ||
692 | break; | ||
693 | } | ||
694 | } | ||
695 | |||
696 | return NULL; | ||
697 | } | ||
698 | |||
699 | /** | ||
700 | * bd_claim_by_kobject - bd_claim() with additional kobject signature | ||
701 | * | ||
702 | * @bdev: block device to be claimed | ||
703 | * @holder: holder's signature | ||
704 | * @kobj: holder's kobject | ||
705 | * | ||
706 | * Do bd_claim() and if it succeeds, create sysfs symlinks between | ||
707 | * the bdev and the holder's kobject. | ||
708 | * Use bd_release_from_kobject() when relesing the claimed bdev. | ||
709 | * | ||
710 | * Returns 0 on success. (same as bd_claim()) | ||
711 | * Returns errno on failure. | ||
712 | */ | ||
713 | static int bd_claim_by_kobject(struct block_device *bdev, void *holder, | ||
714 | struct kobject *kobj) | ||
715 | { | ||
716 | int res; | ||
717 | struct bd_holder *bo; | ||
718 | |||
719 | if (!kobj) | ||
720 | return -EINVAL; | ||
721 | |||
722 | bo = alloc_bd_holder(kobj); | ||
723 | if (!bo) | ||
724 | return -ENOMEM; | ||
725 | |||
726 | down(&bdev->bd_sem); | ||
727 | res = bd_claim(bdev, holder); | ||
728 | if (res || !add_bd_holder(bdev, bo)) | ||
729 | free_bd_holder(bo); | ||
730 | up(&bdev->bd_sem); | ||
731 | |||
732 | return res; | ||
733 | } | ||
734 | |||
735 | /** | ||
736 | * bd_release_from_kobject - bd_release() with additional kobject signature | ||
737 | * | ||
738 | * @bdev: block device to be released | ||
739 | * @kobj: holder's kobject | ||
740 | * | ||
741 | * Do bd_release() and remove sysfs symlinks created by bd_claim_by_kobject(). | ||
742 | */ | ||
743 | static void bd_release_from_kobject(struct block_device *bdev, | ||
744 | struct kobject *kobj) | ||
745 | { | ||
746 | struct bd_holder *bo; | ||
747 | |||
748 | if (!kobj) | ||
749 | return; | ||
750 | |||
751 | down(&bdev->bd_sem); | ||
752 | bd_release(bdev); | ||
753 | if ((bo = del_bd_holder(bdev, kobj))) | ||
754 | free_bd_holder(bo); | ||
755 | up(&bdev->bd_sem); | ||
756 | } | ||
757 | |||
758 | /** | ||
759 | * bd_claim_by_disk - wrapper function for bd_claim_by_kobject() | ||
760 | * | ||
761 | * @bdev: block device to be claimed | ||
762 | * @holder: holder's signature | ||
763 | * @disk: holder's gendisk | ||
764 | * | ||
765 | * Call bd_claim_by_kobject() with getting @disk->slave_dir. | ||
766 | */ | ||
767 | int bd_claim_by_disk(struct block_device *bdev, void *holder, | ||
768 | struct gendisk *disk) | ||
769 | { | ||
770 | return bd_claim_by_kobject(bdev, holder, kobject_get(disk->slave_dir)); | ||
771 | } | ||
772 | EXPORT_SYMBOL_GPL(bd_claim_by_disk); | ||
773 | |||
774 | /** | ||
775 | * bd_release_from_disk - wrapper function for bd_release_from_kobject() | ||
776 | * | ||
777 | * @bdev: block device to be claimed | ||
778 | * @disk: holder's gendisk | ||
779 | * | ||
780 | * Call bd_release_from_kobject() and put @disk->slave_dir. | ||
781 | */ | ||
782 | void bd_release_from_disk(struct block_device *bdev, struct gendisk *disk) | ||
783 | { | ||
784 | bd_release_from_kobject(bdev, disk->slave_dir); | ||
785 | kobject_put(disk->slave_dir); | ||
786 | } | ||
787 | EXPORT_SYMBOL_GPL(bd_release_from_disk); | ||
788 | #endif | ||
789 | |||
493 | /* | 790 | /* |
494 | * Tries to open block device by device number. Use it ONLY if you | 791 | * Tries to open block device by device number. Use it ONLY if you |
495 | * really do not have anything better - i.e. when you are behind a | 792 | * really do not have anything better - i.e. when you are behind a |
diff --git a/include/linux/fs.h b/include/linux/fs.h index 9d9674946956..680d913350e7 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h | |||
@@ -410,6 +410,9 @@ struct block_device { | |||
410 | struct list_head bd_inodes; | 410 | struct list_head bd_inodes; |
411 | void * bd_holder; | 411 | void * bd_holder; |
412 | int bd_holders; | 412 | int bd_holders; |
413 | #ifdef CONFIG_SYSFS | ||
414 | struct list_head bd_holder_list; | ||
415 | #endif | ||
413 | struct block_device * bd_contains; | 416 | struct block_device * bd_contains; |
414 | unsigned bd_block_size; | 417 | unsigned bd_block_size; |
415 | struct hd_struct * bd_part; | 418 | struct hd_struct * bd_part; |
@@ -1399,6 +1402,13 @@ extern int blkdev_get(struct block_device *, mode_t, unsigned); | |||
1399 | extern int blkdev_put(struct block_device *); | 1402 | extern int blkdev_put(struct block_device *); |
1400 | extern int bd_claim(struct block_device *, void *); | 1403 | extern int bd_claim(struct block_device *, void *); |
1401 | extern void bd_release(struct block_device *); | 1404 | extern void bd_release(struct block_device *); |
1405 | #ifdef CONFIG_SYSFS | ||
1406 | extern int bd_claim_by_disk(struct block_device *, void *, struct gendisk *); | ||
1407 | extern void bd_release_from_disk(struct block_device *, struct gendisk *); | ||
1408 | #else | ||
1409 | #define bd_claim_by_disk(bdev, holder, disk) bd_claim(bdev, holder) | ||
1410 | #define bd_release_from_disk(bdev, disk) bd_release(bdev) | ||
1411 | #endif | ||
1402 | 1412 | ||
1403 | /* fs/char_dev.c */ | 1413 | /* fs/char_dev.c */ |
1404 | extern int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *); | 1414 | extern int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *); |