diff options
author | Stefan Richter <stefanr@s5r6.in-berlin.de> | 2008-03-24 15:54:28 -0400 |
---|---|---|
committer | Stefan Richter <stefanr@s5r6.in-berlin.de> | 2008-04-18 11:55:36 -0400 |
commit | c9755e14a01987ada4063e8b4c50c2b6738d879e (patch) | |
tree | 41a593c4b9ac10ccc4ad031510438e4551e51b5b /drivers/firewire/fw-device.c | |
parent | 1dadff71d6356ebb804c3f4f1d3049247e16111c (diff) |
firewire: reread config ROM when device reset the bus
When a device changes its configuration ROM, it announces this with a
bus reset. firewire-core has to check which node initiated a bus reset
and whether any unit directories went away or were added on this node.
Tested with an IOI FWB-IDE01AB which has its link-on bit set if bus
power is available but does not respond to ROM read requests if self
power is off. This implements
- recognition of the units if self power is switched on after fw-core
gave up the initial attempt to read the config ROM,
- shutdown of the units when self power is switched off.
Also tested with a second PC running Linux/ieee1394. When the eth1394
driver is inserted and removed on that node, fw-core now notices the
addition and removal of the IPv4 unit on the ieee1394 node.
Signed-off-by: Stefan Richter <stefanr@s5r6.in-berlin.de>
Diffstat (limited to 'drivers/firewire/fw-device.c')
-rw-r--r-- | drivers/firewire/fw-device.c | 222 |
1 files changed, 188 insertions, 34 deletions
diff --git a/drivers/firewire/fw-device.c b/drivers/firewire/fw-device.c index 20ac9a5afc37..75365cd0008a 100644 --- a/drivers/firewire/fw-device.c +++ b/drivers/firewire/fw-device.c | |||
@@ -25,7 +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/rwsem.h> | 28 | #include <linux/string.h> |
29 | #include <asm/semaphore.h> | 29 | #include <asm/semaphore.h> |
30 | #include <asm/system.h> | 30 | #include <asm/system.h> |
31 | #include <linux/ctype.h> | 31 | #include <linux/ctype.h> |
@@ -160,9 +160,9 @@ static void fw_device_release(struct device *dev) | |||
160 | * Take the card lock so we don't set this to NULL while a | 160 | * Take the card lock so we don't set this to NULL while a |
161 | * FW_NODE_UPDATED callback is being handled. | 161 | * FW_NODE_UPDATED callback is being handled. |
162 | */ | 162 | */ |
163 | spin_lock_irqsave(&device->card->lock, flags); | 163 | spin_lock_irqsave(&card->lock, flags); |
164 | device->node->data = NULL; | 164 | device->node->data = NULL; |
165 | spin_unlock_irqrestore(&device->card->lock, flags); | 165 | spin_unlock_irqrestore(&card->lock, flags); |
166 | 166 | ||
167 | fw_node_put(device->node); | 167 | fw_node_put(device->node); |
168 | kfree(device->config_rom); | 168 | kfree(device->config_rom); |
@@ -195,7 +195,9 @@ show_immediate(struct device *dev, struct device_attribute *dattr, char *buf) | |||
195 | container_of(dattr, struct config_rom_attribute, attr); | 195 | container_of(dattr, struct config_rom_attribute, attr); |
196 | struct fw_csr_iterator ci; | 196 | struct fw_csr_iterator ci; |
197 | u32 *dir; | 197 | u32 *dir; |
198 | int key, value; | 198 | int key, value, ret = -ENOENT; |
199 | |||
200 | down_read(&fw_device_rwsem); | ||
199 | 201 | ||
200 | if (is_fw_unit(dev)) | 202 | if (is_fw_unit(dev)) |
201 | dir = fw_unit(dev)->directory; | 203 | dir = fw_unit(dev)->directory; |
@@ -204,11 +206,15 @@ show_immediate(struct device *dev, struct device_attribute *dattr, char *buf) | |||
204 | 206 | ||
205 | fw_csr_iterator_init(&ci, dir); | 207 | fw_csr_iterator_init(&ci, dir); |
206 | while (fw_csr_iterator_next(&ci, &key, &value)) | 208 | while (fw_csr_iterator_next(&ci, &key, &value)) |
207 | if (attr->key == key) | 209 | if (attr->key == key) { |
208 | return snprintf(buf, buf ? PAGE_SIZE : 0, | 210 | ret = snprintf(buf, buf ? PAGE_SIZE : 0, |
209 | "0x%06x\n", value); | 211 | "0x%06x\n", value); |
212 | break; | ||
213 | } | ||
214 | |||
215 | up_read(&fw_device_rwsem); | ||
210 | 216 | ||
211 | return -ENOENT; | 217 | return ret; |
212 | } | 218 | } |
213 | 219 | ||
214 | #define IMMEDIATE_ATTR(name, key) \ | 220 | #define IMMEDIATE_ATTR(name, key) \ |
@@ -221,9 +227,11 @@ show_text_leaf(struct device *dev, struct device_attribute *dattr, char *buf) | |||
221 | container_of(dattr, struct config_rom_attribute, attr); | 227 | container_of(dattr, struct config_rom_attribute, attr); |
222 | struct fw_csr_iterator ci; | 228 | struct fw_csr_iterator ci; |
223 | u32 *dir, *block = NULL, *p, *end; | 229 | u32 *dir, *block = NULL, *p, *end; |
224 | int length, key, value, last_key = 0; | 230 | int length, key, value, last_key = 0, ret = -ENOENT; |
225 | char *b; | 231 | char *b; |
226 | 232 | ||
233 | down_read(&fw_device_rwsem); | ||
234 | |||
227 | if (is_fw_unit(dev)) | 235 | if (is_fw_unit(dev)) |
228 | dir = fw_unit(dev)->directory; | 236 | dir = fw_unit(dev)->directory; |
229 | else | 237 | else |
@@ -238,18 +246,20 @@ show_text_leaf(struct device *dev, struct device_attribute *dattr, char *buf) | |||
238 | } | 246 | } |
239 | 247 | ||
240 | if (block == NULL) | 248 | if (block == NULL) |
241 | return -ENOENT; | 249 | goto out; |
242 | 250 | ||
243 | length = min(block[0] >> 16, 256U); | 251 | length = min(block[0] >> 16, 256U); |
244 | if (length < 3) | 252 | if (length < 3) |
245 | return -ENOENT; | 253 | goto out; |
246 | 254 | ||
247 | if (block[1] != 0 || block[2] != 0) | 255 | if (block[1] != 0 || block[2] != 0) |
248 | /* Unknown encoding. */ | 256 | /* Unknown encoding. */ |
249 | return -ENOENT; | 257 | goto out; |
250 | 258 | ||
251 | if (buf == NULL) | 259 | if (buf == NULL) { |
252 | return length * 4; | 260 | ret = length * 4; |
261 | goto out; | ||
262 | } | ||
253 | 263 | ||
254 | b = buf; | 264 | b = buf; |
255 | end = &block[length + 1]; | 265 | end = &block[length + 1]; |
@@ -259,8 +269,11 @@ show_text_leaf(struct device *dev, struct device_attribute *dattr, char *buf) | |||
259 | /* Strip trailing whitespace and add newline. */ | 269 | /* Strip trailing whitespace and add newline. */ |
260 | while (b--, (isspace(*b) || *b == '\0') && b > buf); | 270 | while (b--, (isspace(*b) || *b == '\0') && b > buf); |
261 | strcpy(b + 1, "\n"); | 271 | strcpy(b + 1, "\n"); |
272 | ret = b + 2 - buf; | ||
273 | out: | ||
274 | up_read(&fw_device_rwsem); | ||
262 | 275 | ||
263 | return b + 2 - buf; | 276 | return ret; |
264 | } | 277 | } |
265 | 278 | ||
266 | #define TEXT_LEAF_ATTR(name, key) \ | 279 | #define TEXT_LEAF_ATTR(name, key) \ |
@@ -337,19 +350,28 @@ static ssize_t | |||
337 | config_rom_show(struct device *dev, struct device_attribute *attr, char *buf) | 350 | config_rom_show(struct device *dev, struct device_attribute *attr, char *buf) |
338 | { | 351 | { |
339 | struct fw_device *device = fw_device(dev); | 352 | struct fw_device *device = fw_device(dev); |
353 | size_t length; | ||
340 | 354 | ||
341 | memcpy(buf, device->config_rom, device->config_rom_length * 4); | 355 | down_read(&fw_device_rwsem); |
356 | length = device->config_rom_length * 4; | ||
357 | memcpy(buf, device->config_rom, length); | ||
358 | up_read(&fw_device_rwsem); | ||
342 | 359 | ||
343 | return device->config_rom_length * 4; | 360 | return length; |
344 | } | 361 | } |
345 | 362 | ||
346 | static ssize_t | 363 | static ssize_t |
347 | guid_show(struct device *dev, struct device_attribute *attr, char *buf) | 364 | guid_show(struct device *dev, struct device_attribute *attr, char *buf) |
348 | { | 365 | { |
349 | struct fw_device *device = fw_device(dev); | 366 | struct fw_device *device = fw_device(dev); |
367 | int ret; | ||
368 | |||
369 | down_read(&fw_device_rwsem); | ||
370 | ret = snprintf(buf, PAGE_SIZE, "0x%08x%08x\n", | ||
371 | device->config_rom[3], device->config_rom[4]); | ||
372 | up_read(&fw_device_rwsem); | ||
350 | 373 | ||
351 | return snprintf(buf, PAGE_SIZE, "0x%08x%08x\n", | 374 | return ret; |
352 | device->config_rom[3], device->config_rom[4]); | ||
353 | } | 375 | } |
354 | 376 | ||
355 | static struct device_attribute fw_device_attributes[] = { | 377 | static struct device_attribute fw_device_attributes[] = { |
@@ -412,7 +434,7 @@ read_rom(struct fw_device *device, int generation, int index, u32 *data) | |||
412 | */ | 434 | */ |
413 | static int read_bus_info_block(struct fw_device *device, int generation) | 435 | static int read_bus_info_block(struct fw_device *device, int generation) |
414 | { | 436 | { |
415 | u32 *rom, *stack; | 437 | u32 *rom, *stack, *old_rom, *new_rom; |
416 | u32 sp, key; | 438 | u32 sp, key; |
417 | int i, end, length, ret = -1; | 439 | int i, end, length, ret = -1; |
418 | 440 | ||
@@ -527,12 +549,19 @@ static int read_bus_info_block(struct fw_device *device, int generation) | |||
527 | length = i; | 549 | length = i; |
528 | } | 550 | } |
529 | 551 | ||
530 | device->config_rom = kmalloc(length * 4, GFP_KERNEL); | 552 | old_rom = device->config_rom; |
531 | if (device->config_rom == NULL) | 553 | new_rom = kmemdup(rom, length * 4, GFP_KERNEL); |
554 | if (new_rom == NULL) | ||
532 | goto out; | 555 | goto out; |
533 | memcpy(device->config_rom, rom, length * 4); | 556 | |
557 | down_write(&fw_device_rwsem); | ||
558 | device->config_rom = new_rom; | ||
534 | device->config_rom_length = length; | 559 | device->config_rom_length = length; |
560 | up_write(&fw_device_rwsem); | ||
561 | |||
562 | kfree(old_rom); | ||
535 | ret = 0; | 563 | ret = 0; |
564 | device->cmc = rom[2] & 1 << 30; | ||
536 | out: | 565 | out: |
537 | kfree(rom); | 566 | kfree(rom); |
538 | 567 | ||
@@ -605,7 +634,14 @@ static int shutdown_unit(struct device *device, void *data) | |||
605 | return 0; | 634 | return 0; |
606 | } | 635 | } |
607 | 636 | ||
608 | static DECLARE_RWSEM(idr_rwsem); | 637 | /* |
638 | * fw_device_rwsem acts as dual purpose mutex: | ||
639 | * - serializes accesses to fw_device_idr, | ||
640 | * - serializes accesses to fw_device.config_rom/.config_rom_length and | ||
641 | * fw_unit.directory, unless those accesses happen at safe occasions | ||
642 | */ | ||
643 | DECLARE_RWSEM(fw_device_rwsem); | ||
644 | |||
609 | static DEFINE_IDR(fw_device_idr); | 645 | static DEFINE_IDR(fw_device_idr); |
610 | int fw_cdev_major; | 646 | int fw_cdev_major; |
611 | 647 | ||
@@ -613,11 +649,11 @@ struct fw_device *fw_device_get_by_devt(dev_t devt) | |||
613 | { | 649 | { |
614 | struct fw_device *device; | 650 | struct fw_device *device; |
615 | 651 | ||
616 | down_read(&idr_rwsem); | 652 | down_read(&fw_device_rwsem); |
617 | device = idr_find(&fw_device_idr, MINOR(devt)); | 653 | device = idr_find(&fw_device_idr, MINOR(devt)); |
618 | if (device) | 654 | if (device) |
619 | fw_device_get(device); | 655 | fw_device_get(device); |
620 | up_read(&idr_rwsem); | 656 | up_read(&fw_device_rwsem); |
621 | 657 | ||
622 | return device; | 658 | return device; |
623 | } | 659 | } |
@@ -632,9 +668,9 @@ static void fw_device_shutdown(struct work_struct *work) | |||
632 | device_for_each_child(&device->device, NULL, shutdown_unit); | 668 | device_for_each_child(&device->device, NULL, shutdown_unit); |
633 | device_unregister(&device->device); | 669 | device_unregister(&device->device); |
634 | 670 | ||
635 | down_write(&idr_rwsem); | 671 | down_write(&fw_device_rwsem); |
636 | idr_remove(&fw_device_idr, minor); | 672 | idr_remove(&fw_device_idr, minor); |
637 | up_write(&idr_rwsem); | 673 | up_write(&fw_device_rwsem); |
638 | fw_device_put(device); | 674 | fw_device_put(device); |
639 | } | 675 | } |
640 | 676 | ||
@@ -687,10 +723,10 @@ static void fw_device_init(struct work_struct *work) | |||
687 | err = -ENOMEM; | 723 | err = -ENOMEM; |
688 | 724 | ||
689 | fw_device_get(device); | 725 | fw_device_get(device); |
690 | down_write(&idr_rwsem); | 726 | down_write(&fw_device_rwsem); |
691 | if (idr_pre_get(&fw_device_idr, GFP_KERNEL)) | 727 | if (idr_pre_get(&fw_device_idr, GFP_KERNEL)) |
692 | err = idr_get_new(&fw_device_idr, device, &minor); | 728 | err = idr_get_new(&fw_device_idr, device, &minor); |
693 | up_write(&idr_rwsem); | 729 | up_write(&fw_device_rwsem); |
694 | 730 | ||
695 | if (err < 0) | 731 | if (err < 0) |
696 | goto error; | 732 | goto error; |
@@ -724,7 +760,7 @@ static void fw_device_init(struct work_struct *work) | |||
724 | if (atomic_cmpxchg(&device->state, | 760 | if (atomic_cmpxchg(&device->state, |
725 | FW_DEVICE_INITIALIZING, | 761 | FW_DEVICE_INITIALIZING, |
726 | FW_DEVICE_RUNNING) == FW_DEVICE_SHUTDOWN) { | 762 | FW_DEVICE_RUNNING) == FW_DEVICE_SHUTDOWN) { |
727 | fw_device_shutdown(&device->work.work); | 763 | fw_device_shutdown(work); |
728 | } else { | 764 | } else { |
729 | if (device->config_rom_retries) | 765 | if (device->config_rom_retries) |
730 | fw_notify("created device %s: GUID %08x%08x, S%d00, " | 766 | fw_notify("created device %s: GUID %08x%08x, S%d00, " |
@@ -738,6 +774,7 @@ static void fw_device_init(struct work_struct *work) | |||
738 | device->device.bus_id, | 774 | device->device.bus_id, |
739 | device->config_rom[3], device->config_rom[4], | 775 | device->config_rom[3], device->config_rom[4], |
740 | 1 << device->max_speed); | 776 | 1 << device->max_speed); |
777 | device->config_rom_retries = 0; | ||
741 | } | 778 | } |
742 | 779 | ||
743 | /* | 780 | /* |
@@ -752,9 +789,9 @@ static void fw_device_init(struct work_struct *work) | |||
752 | return; | 789 | return; |
753 | 790 | ||
754 | error_with_cdev: | 791 | error_with_cdev: |
755 | down_write(&idr_rwsem); | 792 | down_write(&fw_device_rwsem); |
756 | idr_remove(&fw_device_idr, minor); | 793 | idr_remove(&fw_device_idr, minor); |
757 | up_write(&idr_rwsem); | 794 | up_write(&fw_device_rwsem); |
758 | error: | 795 | error: |
759 | fw_device_put(device); /* fw_device_idr's reference */ | 796 | fw_device_put(device); /* fw_device_idr's reference */ |
760 | 797 | ||
@@ -784,6 +821,106 @@ static void fw_device_update(struct work_struct *work) | |||
784 | device_for_each_child(&device->device, NULL, update_unit); | 821 | device_for_each_child(&device->device, NULL, update_unit); |
785 | } | 822 | } |
786 | 823 | ||
824 | enum { | ||
825 | REREAD_BIB_ERROR, | ||
826 | REREAD_BIB_GONE, | ||
827 | REREAD_BIB_UNCHANGED, | ||
828 | REREAD_BIB_CHANGED, | ||
829 | }; | ||
830 | |||
831 | /* Reread and compare bus info block and header of root directory */ | ||
832 | static int reread_bus_info_block(struct fw_device *device, int generation) | ||
833 | { | ||
834 | u32 q; | ||
835 | int i; | ||
836 | |||
837 | for (i = 0; i < 6; i++) { | ||
838 | if (read_rom(device, generation, i, &q) != RCODE_COMPLETE) | ||
839 | return REREAD_BIB_ERROR; | ||
840 | |||
841 | if (i == 0 && q == 0) | ||
842 | return REREAD_BIB_GONE; | ||
843 | |||
844 | if (i > device->config_rom_length || q != device->config_rom[i]) | ||
845 | return REREAD_BIB_CHANGED; | ||
846 | } | ||
847 | |||
848 | return REREAD_BIB_UNCHANGED; | ||
849 | } | ||
850 | |||
851 | static void fw_device_refresh(struct work_struct *work) | ||
852 | { | ||
853 | struct fw_device *device = | ||
854 | container_of(work, struct fw_device, work.work); | ||
855 | struct fw_card *card = device->card; | ||
856 | int node_id = device->node_id; | ||
857 | |||
858 | switch (reread_bus_info_block(device, device->generation)) { | ||
859 | case REREAD_BIB_ERROR: | ||
860 | if (device->config_rom_retries < MAX_RETRIES / 2 && | ||
861 | atomic_read(&device->state) == FW_DEVICE_INITIALIZING) { | ||
862 | device->config_rom_retries++; | ||
863 | schedule_delayed_work(&device->work, RETRY_DELAY / 2); | ||
864 | |||
865 | return; | ||
866 | } | ||
867 | goto give_up; | ||
868 | |||
869 | case REREAD_BIB_GONE: | ||
870 | goto gone; | ||
871 | |||
872 | case REREAD_BIB_UNCHANGED: | ||
873 | if (atomic_cmpxchg(&device->state, | ||
874 | FW_DEVICE_INITIALIZING, | ||
875 | FW_DEVICE_RUNNING) == FW_DEVICE_SHUTDOWN) | ||
876 | goto gone; | ||
877 | |||
878 | fw_device_update(work); | ||
879 | device->config_rom_retries = 0; | ||
880 | goto out; | ||
881 | |||
882 | case REREAD_BIB_CHANGED: | ||
883 | break; | ||
884 | } | ||
885 | |||
886 | /* | ||
887 | * Something changed. We keep things simple and don't investigate | ||
888 | * further. We just destroy all previous units and create new ones. | ||
889 | */ | ||
890 | device_for_each_child(&device->device, NULL, shutdown_unit); | ||
891 | |||
892 | if (read_bus_info_block(device, device->generation) < 0) { | ||
893 | if (device->config_rom_retries < MAX_RETRIES && | ||
894 | atomic_read(&device->state) == FW_DEVICE_INITIALIZING) { | ||
895 | device->config_rom_retries++; | ||
896 | schedule_delayed_work(&device->work, RETRY_DELAY); | ||
897 | |||
898 | return; | ||
899 | } | ||
900 | goto give_up; | ||
901 | } | ||
902 | |||
903 | create_units(device); | ||
904 | |||
905 | if (atomic_cmpxchg(&device->state, | ||
906 | FW_DEVICE_INITIALIZING, | ||
907 | FW_DEVICE_RUNNING) == FW_DEVICE_SHUTDOWN) | ||
908 | goto gone; | ||
909 | |||
910 | fw_notify("refreshed device %s\n", device->device.bus_id); | ||
911 | device->config_rom_retries = 0; | ||
912 | goto out; | ||
913 | |||
914 | give_up: | ||
915 | fw_notify("giving up on refresh of device %s\n", device->device.bus_id); | ||
916 | gone: | ||
917 | atomic_set(&device->state, FW_DEVICE_SHUTDOWN); | ||
918 | fw_device_shutdown(work); | ||
919 | out: | ||
920 | if (node_id == card->root_node->node_id) | ||
921 | schedule_delayed_work(&card->work, 0); | ||
922 | } | ||
923 | |||
787 | void fw_node_event(struct fw_card *card, struct fw_node *node, int event) | 924 | void fw_node_event(struct fw_card *card, struct fw_node *node, int event) |
788 | { | 925 | { |
789 | struct fw_device *device; | 926 | struct fw_device *device; |
@@ -793,7 +930,7 @@ void fw_node_event(struct fw_card *card, struct fw_node *node, int event) | |||
793 | case FW_NODE_LINK_ON: | 930 | case FW_NODE_LINK_ON: |
794 | if (!node->link_on) | 931 | if (!node->link_on) |
795 | break; | 932 | break; |
796 | 933 | create: | |
797 | device = kzalloc(sizeof(*device), GFP_ATOMIC); | 934 | device = kzalloc(sizeof(*device), GFP_ATOMIC); |
798 | if (device == NULL) | 935 | if (device == NULL) |
799 | break; | 936 | break; |
@@ -832,6 +969,23 @@ void fw_node_event(struct fw_card *card, struct fw_node *node, int event) | |||
832 | schedule_delayed_work(&device->work, INITIAL_DELAY); | 969 | schedule_delayed_work(&device->work, INITIAL_DELAY); |
833 | break; | 970 | break; |
834 | 971 | ||
972 | case FW_NODE_INITIATED_RESET: | ||
973 | device = node->data; | ||
974 | if (device == NULL) | ||
975 | goto create; | ||
976 | |||
977 | device->node_id = node->node_id; | ||
978 | smp_wmb(); /* update node_id before generation */ | ||
979 | device->generation = card->generation; | ||
980 | if (atomic_cmpxchg(&device->state, | ||
981 | FW_DEVICE_RUNNING, | ||
982 | FW_DEVICE_INITIALIZING) == FW_DEVICE_RUNNING) { | ||
983 | PREPARE_DELAYED_WORK(&device->work, fw_device_refresh); | ||
984 | schedule_delayed_work(&device->work, | ||
985 | node == card->local_node ? 0 : INITIAL_DELAY); | ||
986 | } | ||
987 | break; | ||
988 | |||
835 | case FW_NODE_UPDATED: | 989 | case FW_NODE_UPDATED: |
836 | if (!node->link_on || node->data == NULL) | 990 | if (!node->link_on || node->data == NULL) |
837 | break; | 991 | break; |