diff options
author | Stefan Richter <stefanr@s5r6.in-berlin.de> | 2009-01-17 16:45:54 -0500 |
---|---|---|
committer | Stefan Richter <stefanr@s5r6.in-berlin.de> | 2009-01-20 13:29:52 -0500 |
commit | 3d36a0df3b473fb53531484df227f2da8bc7494b (patch) | |
tree | f0fcb758c8e68f1e5389ccd895d98efcc55ed0e8 /drivers/firewire | |
parent | 8cd0bbbdff7471163cc6a058be8b8610ddd01d6b (diff) |
firewire: keep highlevel drivers attached during brief connection loss
There are situations when nodes vanish from the bus and come back
quickly thereafter:
- When certain bus-powered hubs are plugged in,
- when certain devices are plugged into 6-port hubs,
- when certain disk enclosures are switched from self-power to bus
power or vice versa and break the daisy chain during the transition,
- when the user plugs a cable out and quickly plugs it back in, e.g.
to reorder a daisy chain (works on Mac OS X if done quickly enough),
- when certain hubs temporarily malfunction during high bus traffic.
Until now, firewire-core reported affected nodes as lost to the
highlevel drivers (firewire-sbp2 and userspace drivers). We now delay
the destruction of device representations until after at least two
seconds after the last bus reset. If a "new" device is detected in this
period whose bus information block and root directory header match that
of a device which is pending for deletion, we resurrect that device and
send update calls to highlevel drivers.
Signed-off-by: Stefan Richter <stefanr@s5r6.in-berlin.de>
Diffstat (limited to 'drivers/firewire')
-rw-r--r-- | drivers/firewire/fw-device.c | 121 | ||||
-rw-r--r-- | drivers/firewire/fw-device.h | 1 |
2 files changed, 100 insertions, 22 deletions
diff --git a/drivers/firewire/fw-device.c b/drivers/firewire/fw-device.c index 2af5a8d1e012..0925d91b2610 100644 --- a/drivers/firewire/fw-device.c +++ b/drivers/firewire/fw-device.c | |||
@@ -25,6 +25,7 @@ | |||
25 | #include <linux/device.h> | 25 | #include <linux/device.h> |
26 | #include <linux/delay.h> | 26 | #include <linux/delay.h> |
27 | #include <linux/idr.h> | 27 | #include <linux/idr.h> |
28 | #include <linux/jiffies.h> | ||
28 | #include <linux/string.h> | 29 | #include <linux/string.h> |
29 | #include <linux/rwsem.h> | 30 | #include <linux/rwsem.h> |
30 | #include <linux/semaphore.h> | 31 | #include <linux/semaphore.h> |
@@ -634,12 +635,38 @@ struct fw_device *fw_device_get_by_devt(dev_t devt) | |||
634 | return device; | 635 | return device; |
635 | } | 636 | } |
636 | 637 | ||
638 | /* | ||
639 | * These defines control the retry behavior for reading the config | ||
640 | * rom. It shouldn't be necessary to tweak these; if the device | ||
641 | * doesn't respond to a config rom read within 10 seconds, it's not | ||
642 | * going to respond at all. As for the initial delay, a lot of | ||
643 | * devices will be able to respond within half a second after bus | ||
644 | * reset. On the other hand, it's not really worth being more | ||
645 | * aggressive than that, since it scales pretty well; if 10 devices | ||
646 | * are plugged in, they're all getting read within one second. | ||
647 | */ | ||
648 | |||
649 | #define MAX_RETRIES 10 | ||
650 | #define RETRY_DELAY (3 * HZ) | ||
651 | #define INITIAL_DELAY (HZ / 2) | ||
652 | #define SHUTDOWN_DELAY (2 * HZ) | ||
653 | |||
637 | static void fw_device_shutdown(struct work_struct *work) | 654 | static void fw_device_shutdown(struct work_struct *work) |
638 | { | 655 | { |
639 | struct fw_device *device = | 656 | struct fw_device *device = |
640 | container_of(work, struct fw_device, work.work); | 657 | container_of(work, struct fw_device, work.work); |
641 | int minor = MINOR(device->device.devt); | 658 | int minor = MINOR(device->device.devt); |
642 | 659 | ||
660 | if (time_is_after_jiffies(device->card->reset_jiffies + SHUTDOWN_DELAY)) { | ||
661 | schedule_delayed_work(&device->work, SHUTDOWN_DELAY); | ||
662 | return; | ||
663 | } | ||
664 | |||
665 | if (atomic_cmpxchg(&device->state, | ||
666 | FW_DEVICE_GONE, | ||
667 | FW_DEVICE_SHUTDOWN) != FW_DEVICE_GONE) | ||
668 | return; | ||
669 | |||
643 | fw_device_cdev_remove(device); | 670 | fw_device_cdev_remove(device); |
644 | device_for_each_child(&device->device, NULL, shutdown_unit); | 671 | device_for_each_child(&device->device, NULL, shutdown_unit); |
645 | device_unregister(&device->device); | 672 | device_unregister(&device->device); |
@@ -647,6 +674,7 @@ static void fw_device_shutdown(struct work_struct *work) | |||
647 | down_write(&fw_device_rwsem); | 674 | down_write(&fw_device_rwsem); |
648 | idr_remove(&fw_device_idr, minor); | 675 | idr_remove(&fw_device_idr, minor); |
649 | up_write(&fw_device_rwsem); | 676 | up_write(&fw_device_rwsem); |
677 | |||
650 | fw_device_put(device); | 678 | fw_device_put(device); |
651 | } | 679 | } |
652 | 680 | ||
@@ -654,25 +682,63 @@ static struct device_type fw_device_type = { | |||
654 | .release = fw_device_release, | 682 | .release = fw_device_release, |
655 | }; | 683 | }; |
656 | 684 | ||
685 | static void fw_device_update(struct work_struct *work); | ||
686 | |||
657 | /* | 687 | /* |
658 | * These defines control the retry behavior for reading the config | 688 | * If a device was pending for deletion because its node went away but its |
659 | * rom. It shouldn't be necessary to tweak these; if the device | 689 | * bus info block and root directory header matches that of a newly discovered |
660 | * doesn't respond to a config rom read within 10 seconds, it's not | 690 | * device, revive the existing fw_device. |
661 | * going to respond at all. As for the initial delay, a lot of | 691 | * The newly allocated fw_device becomes obsolete instead. |
662 | * devices will be able to respond within half a second after bus | ||
663 | * reset. On the other hand, it's not really worth being more | ||
664 | * aggressive than that, since it scales pretty well; if 10 devices | ||
665 | * are plugged in, they're all getting read within one second. | ||
666 | */ | 692 | */ |
693 | static int lookup_existing_device(struct device *dev, void *data) | ||
694 | { | ||
695 | struct fw_device *old = fw_device(dev); | ||
696 | struct fw_device *new = data; | ||
697 | struct fw_card *card = new->card; | ||
698 | int match = 0; | ||
699 | |||
700 | down_read(&fw_device_rwsem); /* serialize config_rom access */ | ||
701 | spin_lock_irq(&card->lock); /* serialize node access */ | ||
702 | |||
703 | if (memcmp(old->config_rom, new->config_rom, 6 * 4) == 0 && | ||
704 | atomic_cmpxchg(&old->state, | ||
705 | FW_DEVICE_GONE, | ||
706 | FW_DEVICE_RUNNING) == FW_DEVICE_GONE) { | ||
707 | struct fw_node *current_node = new->node; | ||
708 | struct fw_node *obsolete_node = old->node; | ||
709 | |||
710 | new->node = obsolete_node; | ||
711 | new->node->data = new; | ||
712 | old->node = current_node; | ||
713 | old->node->data = old; | ||
714 | |||
715 | old->max_speed = new->max_speed; | ||
716 | old->node_id = current_node->node_id; | ||
717 | smp_wmb(); /* update node_id before generation */ | ||
718 | old->generation = card->generation; | ||
719 | old->config_rom_retries = 0; | ||
720 | fw_notify("rediscovered device %s\n", dev_name(dev)); | ||
667 | 721 | ||
668 | #define MAX_RETRIES 10 | 722 | PREPARE_DELAYED_WORK(&old->work, fw_device_update); |
669 | #define RETRY_DELAY (3 * HZ) | 723 | schedule_delayed_work(&old->work, 0); |
670 | #define INITIAL_DELAY (HZ / 2) | 724 | |
725 | if (current_node == card->root_node) | ||
726 | fw_schedule_bm_work(card, 0); | ||
727 | |||
728 | match = 1; | ||
729 | } | ||
730 | |||
731 | spin_unlock_irq(&card->lock); | ||
732 | up_read(&fw_device_rwsem); | ||
733 | |||
734 | return match; | ||
735 | } | ||
671 | 736 | ||
672 | static void fw_device_init(struct work_struct *work) | 737 | static void fw_device_init(struct work_struct *work) |
673 | { | 738 | { |
674 | struct fw_device *device = | 739 | struct fw_device *device = |
675 | container_of(work, struct fw_device, work.work); | 740 | container_of(work, struct fw_device, work.work); |
741 | struct device *revived_dev; | ||
676 | int minor, err; | 742 | int minor, err; |
677 | 743 | ||
678 | /* | 744 | /* |
@@ -696,6 +762,15 @@ static void fw_device_init(struct work_struct *work) | |||
696 | return; | 762 | return; |
697 | } | 763 | } |
698 | 764 | ||
765 | revived_dev = device_find_child(device->card->device, | ||
766 | device, lookup_existing_device); | ||
767 | if (revived_dev) { | ||
768 | put_device(revived_dev); | ||
769 | fw_device_release(&device->device); | ||
770 | |||
771 | return; | ||
772 | } | ||
773 | |||
699 | device_initialize(&device->device); | 774 | device_initialize(&device->device); |
700 | 775 | ||
701 | fw_device_get(device); | 776 | fw_device_get(device); |
@@ -734,9 +809,10 @@ static void fw_device_init(struct work_struct *work) | |||
734 | * fw_node_event(). | 809 | * fw_node_event(). |
735 | */ | 810 | */ |
736 | if (atomic_cmpxchg(&device->state, | 811 | if (atomic_cmpxchg(&device->state, |
737 | FW_DEVICE_INITIALIZING, | 812 | FW_DEVICE_INITIALIZING, |
738 | FW_DEVICE_RUNNING) == FW_DEVICE_SHUTDOWN) { | 813 | FW_DEVICE_RUNNING) == FW_DEVICE_GONE) { |
739 | fw_device_shutdown(work); | 814 | PREPARE_DELAYED_WORK(&device->work, fw_device_shutdown); |
815 | schedule_delayed_work(&device->work, SHUTDOWN_DELAY); | ||
740 | } else { | 816 | } else { |
741 | if (device->config_rom_retries) | 817 | if (device->config_rom_retries) |
742 | fw_notify("created device %s: GUID %08x%08x, S%d00, " | 818 | fw_notify("created device %s: GUID %08x%08x, S%d00, " |
@@ -847,8 +923,8 @@ static void fw_device_refresh(struct work_struct *work) | |||
847 | 923 | ||
848 | case REREAD_BIB_UNCHANGED: | 924 | case REREAD_BIB_UNCHANGED: |
849 | if (atomic_cmpxchg(&device->state, | 925 | if (atomic_cmpxchg(&device->state, |
850 | FW_DEVICE_INITIALIZING, | 926 | FW_DEVICE_INITIALIZING, |
851 | FW_DEVICE_RUNNING) == FW_DEVICE_SHUTDOWN) | 927 | FW_DEVICE_RUNNING) == FW_DEVICE_GONE) |
852 | goto gone; | 928 | goto gone; |
853 | 929 | ||
854 | fw_device_update(work); | 930 | fw_device_update(work); |
@@ -879,8 +955,8 @@ static void fw_device_refresh(struct work_struct *work) | |||
879 | create_units(device); | 955 | create_units(device); |
880 | 956 | ||
881 | if (atomic_cmpxchg(&device->state, | 957 | if (atomic_cmpxchg(&device->state, |
882 | FW_DEVICE_INITIALIZING, | 958 | FW_DEVICE_INITIALIZING, |
883 | FW_DEVICE_RUNNING) == FW_DEVICE_SHUTDOWN) | 959 | FW_DEVICE_RUNNING) == FW_DEVICE_GONE) |
884 | goto gone; | 960 | goto gone; |
885 | 961 | ||
886 | fw_notify("refreshed device %s\n", dev_name(&device->device)); | 962 | fw_notify("refreshed device %s\n", dev_name(&device->device)); |
@@ -890,8 +966,9 @@ static void fw_device_refresh(struct work_struct *work) | |||
890 | give_up: | 966 | give_up: |
891 | fw_notify("giving up on refresh of device %s\n", dev_name(&device->device)); | 967 | fw_notify("giving up on refresh of device %s\n", dev_name(&device->device)); |
892 | gone: | 968 | gone: |
893 | atomic_set(&device->state, FW_DEVICE_SHUTDOWN); | 969 | atomic_set(&device->state, FW_DEVICE_GONE); |
894 | fw_device_shutdown(work); | 970 | PREPARE_DELAYED_WORK(&device->work, fw_device_shutdown); |
971 | schedule_delayed_work(&device->work, SHUTDOWN_DELAY); | ||
895 | out: | 972 | out: |
896 | if (node_id == card->root_node->node_id) | 973 | if (node_id == card->root_node->node_id) |
897 | fw_schedule_bm_work(card, 0); | 974 | fw_schedule_bm_work(card, 0); |
@@ -995,9 +1072,9 @@ void fw_node_event(struct fw_card *card, struct fw_node *node, int event) | |||
995 | */ | 1072 | */ |
996 | device = node->data; | 1073 | device = node->data; |
997 | if (atomic_xchg(&device->state, | 1074 | if (atomic_xchg(&device->state, |
998 | FW_DEVICE_SHUTDOWN) == FW_DEVICE_RUNNING) { | 1075 | FW_DEVICE_GONE) == FW_DEVICE_RUNNING) { |
999 | PREPARE_DELAYED_WORK(&device->work, fw_device_shutdown); | 1076 | PREPARE_DELAYED_WORK(&device->work, fw_device_shutdown); |
1000 | schedule_delayed_work(&device->work, 0); | 1077 | schedule_delayed_work(&device->work, SHUTDOWN_DELAY); |
1001 | } | 1078 | } |
1002 | break; | 1079 | break; |
1003 | } | 1080 | } |
diff --git a/drivers/firewire/fw-device.h b/drivers/firewire/fw-device.h index df51732608d9..8ef6ec2ca21c 100644 --- a/drivers/firewire/fw-device.h +++ b/drivers/firewire/fw-device.h | |||
@@ -28,6 +28,7 @@ | |||
28 | enum fw_device_state { | 28 | enum fw_device_state { |
29 | FW_DEVICE_INITIALIZING, | 29 | FW_DEVICE_INITIALIZING, |
30 | FW_DEVICE_RUNNING, | 30 | FW_DEVICE_RUNNING, |
31 | FW_DEVICE_GONE, | ||
31 | FW_DEVICE_SHUTDOWN, | 32 | FW_DEVICE_SHUTDOWN, |
32 | }; | 33 | }; |
33 | 34 | ||