diff options
author | Cornelia Huck <cornelia.huck@de.ibm.com> | 2006-12-08 09:54:28 -0500 |
---|---|---|
committer | Martin Schwidefsky <schwidefsky@de.ibm.com> | 2006-12-08 09:54:28 -0500 |
commit | d7b5a4c94f49131811112526f7d404a50f0b5ca7 (patch) | |
tree | 159cb6717e16339b821315c0bc6b17b6f5df5119 /drivers/s390/cio/device.c | |
parent | 2ec2298412e1ab4674b3780005058d4f0b8bd858 (diff) |
[S390] Support for disconnected devices reappearing on another subchannel.
- create a 'pseudo_subchannel' per channel subsystem (the 'orphanage')
- use the orphanage as a shelter for ccw_devices that can't remain on the same
subchannel
Signed-off-by: Cornelia Huck <cornelia.huck@de.ibm.com>
Cc: Greg KH <greg@kroah.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Diffstat (limited to 'drivers/s390/cio/device.c')
-rw-r--r-- | drivers/s390/cio/device.c | 287 |
1 files changed, 237 insertions, 50 deletions
diff --git a/drivers/s390/cio/device.c b/drivers/s390/cio/device.c index 9a31239fe028..7fe1ccdc7812 100644 --- a/drivers/s390/cio/device.c +++ b/drivers/s390/cio/device.c | |||
@@ -23,6 +23,7 @@ | |||
23 | #include <asm/param.h> /* HZ */ | 23 | #include <asm/param.h> /* HZ */ |
24 | 24 | ||
25 | #include "cio.h" | 25 | #include "cio.h" |
26 | #include "cio_debug.h" | ||
26 | #include "css.h" | 27 | #include "css.h" |
27 | #include "device.h" | 28 | #include "device.h" |
28 | #include "ioasm.h" | 29 | #include "ioasm.h" |
@@ -294,6 +295,11 @@ online_show (struct device *dev, struct device_attribute *attr, char *buf) | |||
294 | return sprintf(buf, cdev->online ? "1\n" : "0\n"); | 295 | return sprintf(buf, cdev->online ? "1\n" : "0\n"); |
295 | } | 296 | } |
296 | 297 | ||
298 | int ccw_device_is_orphan(struct ccw_device *cdev) | ||
299 | { | ||
300 | return sch_is_pseudo_sch(to_subchannel(cdev->dev.parent)); | ||
301 | } | ||
302 | |||
297 | static void ccw_device_unregister(struct work_struct *work) | 303 | static void ccw_device_unregister(struct work_struct *work) |
298 | { | 304 | { |
299 | struct ccw_device_private *priv; | 305 | struct ccw_device_private *priv; |
@@ -310,10 +316,23 @@ static void | |||
310 | ccw_device_remove_disconnected(struct ccw_device *cdev) | 316 | ccw_device_remove_disconnected(struct ccw_device *cdev) |
311 | { | 317 | { |
312 | struct subchannel *sch; | 318 | struct subchannel *sch; |
319 | unsigned long flags; | ||
313 | /* | 320 | /* |
314 | * Forced offline in disconnected state means | 321 | * Forced offline in disconnected state means |
315 | * 'throw away device'. | 322 | * 'throw away device'. |
316 | */ | 323 | */ |
324 | if (ccw_device_is_orphan(cdev)) { | ||
325 | /* Deregister ccw device. */ | ||
326 | spin_lock_irqsave(cdev->ccwlock, flags); | ||
327 | cdev->private->state = DEV_STATE_NOT_OPER; | ||
328 | spin_unlock_irqrestore(cdev->ccwlock, flags); | ||
329 | if (get_device(&cdev->dev)) { | ||
330 | PREPARE_WORK(&cdev->private->kick_work, | ||
331 | ccw_device_unregister); | ||
332 | queue_work(ccw_device_work, &cdev->private->kick_work); | ||
333 | } | ||
334 | return ; | ||
335 | } | ||
317 | sch = to_subchannel(cdev->dev.parent); | 336 | sch = to_subchannel(cdev->dev.parent); |
318 | css_sch_device_unregister(sch); | 337 | css_sch_device_unregister(sch); |
319 | /* Reset intparm to zeroes. */ | 338 | /* Reset intparm to zeroes. */ |
@@ -474,6 +493,8 @@ available_show (struct device *dev, struct device_attribute *attr, char *buf) | |||
474 | struct ccw_device *cdev = to_ccwdev(dev); | 493 | struct ccw_device *cdev = to_ccwdev(dev); |
475 | struct subchannel *sch; | 494 | struct subchannel *sch; |
476 | 495 | ||
496 | if (ccw_device_is_orphan(cdev)) | ||
497 | return sprintf(buf, "no device\n"); | ||
477 | switch (cdev->private->state) { | 498 | switch (cdev->private->state) { |
478 | case DEV_STATE_BOXED: | 499 | case DEV_STATE_BOXED: |
479 | return sprintf(buf, "boxed\n"); | 500 | return sprintf(buf, "boxed\n"); |
@@ -574,11 +595,10 @@ match_devno(struct device * dev, void * data) | |||
574 | 595 | ||
575 | cdev = to_ccwdev(dev); | 596 | cdev = to_ccwdev(dev); |
576 | if ((cdev->private->state == DEV_STATE_DISCONNECTED) && | 597 | if ((cdev->private->state == DEV_STATE_DISCONNECTED) && |
598 | !ccw_device_is_orphan(cdev) && | ||
577 | ccw_dev_id_is_equal(&cdev->private->dev_id, &d->dev_id) && | 599 | ccw_dev_id_is_equal(&cdev->private->dev_id, &d->dev_id) && |
578 | (cdev != d->sibling)) { | 600 | (cdev != d->sibling)) |
579 | cdev->private->state = DEV_STATE_NOT_OPER; | ||
580 | return 1; | 601 | return 1; |
581 | } | ||
582 | return 0; | 602 | return 0; |
583 | } | 603 | } |
584 | 604 | ||
@@ -595,6 +615,28 @@ static struct ccw_device * get_disc_ccwdev_by_dev_id(struct ccw_dev_id *dev_id, | |||
595 | return dev ? to_ccwdev(dev) : NULL; | 615 | return dev ? to_ccwdev(dev) : NULL; |
596 | } | 616 | } |
597 | 617 | ||
618 | static int match_orphan(struct device *dev, void *data) | ||
619 | { | ||
620 | struct ccw_dev_id *dev_id; | ||
621 | struct ccw_device *cdev; | ||
622 | |||
623 | dev_id = data; | ||
624 | cdev = to_ccwdev(dev); | ||
625 | return ccw_dev_id_is_equal(&cdev->private->dev_id, dev_id); | ||
626 | } | ||
627 | |||
628 | static struct ccw_device * | ||
629 | get_orphaned_ccwdev_by_dev_id(struct channel_subsystem *css, | ||
630 | struct ccw_dev_id *dev_id) | ||
631 | { | ||
632 | struct device *dev; | ||
633 | |||
634 | dev = device_find_child(&css->pseudo_subchannel->dev, dev_id, | ||
635 | match_orphan); | ||
636 | |||
637 | return dev ? to_ccwdev(dev) : NULL; | ||
638 | } | ||
639 | |||
598 | static void | 640 | static void |
599 | ccw_device_add_changed(struct work_struct *work) | 641 | ccw_device_add_changed(struct work_struct *work) |
600 | { | 642 | { |
@@ -614,64 +656,19 @@ ccw_device_add_changed(struct work_struct *work) | |||
614 | } | 656 | } |
615 | } | 657 | } |
616 | 658 | ||
617 | extern int css_get_ssd_info(struct subchannel *sch); | 659 | void ccw_device_do_unreg_rereg(struct work_struct *work) |
618 | |||
619 | void | ||
620 | ccw_device_do_unreg_rereg(struct work_struct *work) | ||
621 | { | 660 | { |
622 | struct ccw_device_private *priv; | 661 | struct ccw_device_private *priv; |
623 | struct ccw_device *cdev; | 662 | struct ccw_device *cdev; |
624 | struct subchannel *sch; | 663 | struct subchannel *sch; |
625 | int need_rename; | ||
626 | 664 | ||
627 | priv = container_of(work, struct ccw_device_private, kick_work); | 665 | priv = container_of(work, struct ccw_device_private, kick_work); |
628 | cdev = priv->cdev; | 666 | cdev = priv->cdev; |
629 | sch = to_subchannel(cdev->dev.parent); | 667 | sch = to_subchannel(cdev->dev.parent); |
630 | if (cdev->private->dev_id.devno != sch->schib.pmcw.dev) { | 668 | |
631 | /* | ||
632 | * The device number has changed. This is usually only when | ||
633 | * a device has been detached under VM and then re-appeared | ||
634 | * on another subchannel because of a different attachment | ||
635 | * order than before. Ideally, we should should just switch | ||
636 | * subchannels, but unfortunately, this is not possible with | ||
637 | * the current implementation. | ||
638 | * Instead, we search for the old subchannel for this device | ||
639 | * number and deregister so there are no collisions with the | ||
640 | * newly registered ccw_device. | ||
641 | * FIXME: Find another solution so the block layer doesn't | ||
642 | * get possibly sick... | ||
643 | */ | ||
644 | struct ccw_device *other_cdev; | ||
645 | struct ccw_dev_id dev_id; | ||
646 | |||
647 | need_rename = 1; | ||
648 | dev_id.devno = sch->schib.pmcw.dev; | ||
649 | dev_id.ssid = sch->schid.ssid; | ||
650 | other_cdev = get_disc_ccwdev_by_dev_id(&dev_id, cdev); | ||
651 | if (other_cdev) { | ||
652 | struct subchannel *other_sch; | ||
653 | |||
654 | other_sch = to_subchannel(other_cdev->dev.parent); | ||
655 | if (get_device(&other_sch->dev)) { | ||
656 | stsch(other_sch->schid, &other_sch->schib); | ||
657 | if (other_sch->schib.pmcw.dnv) { | ||
658 | other_sch->schib.pmcw.intparm = 0; | ||
659 | cio_modify(other_sch); | ||
660 | } | ||
661 | css_sch_device_unregister(other_sch); | ||
662 | } | ||
663 | } | ||
664 | /* Update ssd info here. */ | ||
665 | css_get_ssd_info(sch); | ||
666 | cdev->private->dev_id.devno = sch->schib.pmcw.dev; | ||
667 | } else | ||
668 | need_rename = 0; | ||
669 | device_remove_files(&cdev->dev); | 669 | device_remove_files(&cdev->dev); |
670 | if (test_and_clear_bit(1, &cdev->private->registered)) | 670 | if (test_and_clear_bit(1, &cdev->private->registered)) |
671 | device_del(&cdev->dev); | 671 | device_del(&cdev->dev); |
672 | if (need_rename) | ||
673 | snprintf (cdev->dev.bus_id, BUS_ID_SIZE, "0.%x.%04x", | ||
674 | sch->schid.ssid, sch->schib.pmcw.dev); | ||
675 | PREPARE_WORK(&cdev->private->kick_work, | 672 | PREPARE_WORK(&cdev->private->kick_work, |
676 | ccw_device_add_changed); | 673 | ccw_device_add_changed); |
677 | queue_work(ccw_device_work, &cdev->private->kick_work); | 674 | queue_work(ccw_device_work, &cdev->private->kick_work); |
@@ -736,6 +733,131 @@ static struct ccw_device * io_subchannel_create_ccwdev(struct subchannel *sch) | |||
736 | return cdev; | 733 | return cdev; |
737 | } | 734 | } |
738 | 735 | ||
736 | static int io_subchannel_recog(struct ccw_device *, struct subchannel *); | ||
737 | |||
738 | static void sch_attach_device(struct subchannel *sch, | ||
739 | struct ccw_device *cdev) | ||
740 | { | ||
741 | spin_lock_irq(sch->lock); | ||
742 | sch->dev.driver_data = cdev; | ||
743 | cdev->private->schid = sch->schid; | ||
744 | cdev->ccwlock = sch->lock; | ||
745 | device_trigger_reprobe(sch); | ||
746 | spin_unlock_irq(sch->lock); | ||
747 | } | ||
748 | |||
749 | static void sch_attach_disconnected_device(struct subchannel *sch, | ||
750 | struct ccw_device *cdev) | ||
751 | { | ||
752 | struct subchannel *other_sch; | ||
753 | int ret; | ||
754 | |||
755 | other_sch = to_subchannel(get_device(cdev->dev.parent)); | ||
756 | ret = device_move(&cdev->dev, &sch->dev); | ||
757 | if (ret) { | ||
758 | CIO_MSG_EVENT(2, "Moving disconnected device 0.%x.%04x failed " | ||
759 | "(ret=%d)!\n", cdev->private->dev_id.ssid, | ||
760 | cdev->private->dev_id.devno, ret); | ||
761 | put_device(&other_sch->dev); | ||
762 | return; | ||
763 | } | ||
764 | other_sch->dev.driver_data = NULL; | ||
765 | /* No need to keep a subchannel without ccw device around. */ | ||
766 | css_sch_device_unregister(other_sch); | ||
767 | put_device(&other_sch->dev); | ||
768 | sch_attach_device(sch, cdev); | ||
769 | } | ||
770 | |||
771 | static void sch_attach_orphaned_device(struct subchannel *sch, | ||
772 | struct ccw_device *cdev) | ||
773 | { | ||
774 | int ret; | ||
775 | |||
776 | /* Try to move the ccw device to its new subchannel. */ | ||
777 | ret = device_move(&cdev->dev, &sch->dev); | ||
778 | if (ret) { | ||
779 | CIO_MSG_EVENT(0, "Moving device 0.%x.%04x from orphanage " | ||
780 | "failed (ret=%d)!\n", | ||
781 | cdev->private->dev_id.ssid, | ||
782 | cdev->private->dev_id.devno, ret); | ||
783 | return; | ||
784 | } | ||
785 | sch_attach_device(sch, cdev); | ||
786 | } | ||
787 | |||
788 | static void sch_create_and_recog_new_device(struct subchannel *sch) | ||
789 | { | ||
790 | struct ccw_device *cdev; | ||
791 | |||
792 | /* Need to allocate a new ccw device. */ | ||
793 | cdev = io_subchannel_create_ccwdev(sch); | ||
794 | if (IS_ERR(cdev)) { | ||
795 | /* OK, we did everything we could... */ | ||
796 | css_sch_device_unregister(sch); | ||
797 | return; | ||
798 | } | ||
799 | spin_lock_irq(sch->lock); | ||
800 | sch->dev.driver_data = cdev; | ||
801 | spin_unlock_irq(sch->lock); | ||
802 | /* Start recognition for the new ccw device. */ | ||
803 | if (io_subchannel_recog(cdev, sch)) { | ||
804 | spin_lock_irq(sch->lock); | ||
805 | sch->dev.driver_data = NULL; | ||
806 | spin_unlock_irq(sch->lock); | ||
807 | if (cdev->dev.release) | ||
808 | cdev->dev.release(&cdev->dev); | ||
809 | css_sch_device_unregister(sch); | ||
810 | } | ||
811 | } | ||
812 | |||
813 | |||
814 | void ccw_device_move_to_orphanage(struct work_struct *work) | ||
815 | { | ||
816 | struct ccw_device_private *priv; | ||
817 | struct ccw_device *cdev; | ||
818 | struct ccw_device *replacing_cdev; | ||
819 | struct subchannel *sch; | ||
820 | int ret; | ||
821 | struct channel_subsystem *css; | ||
822 | struct ccw_dev_id dev_id; | ||
823 | |||
824 | priv = container_of(work, struct ccw_device_private, kick_work); | ||
825 | cdev = priv->cdev; | ||
826 | sch = to_subchannel(cdev->dev.parent); | ||
827 | css = to_css(sch->dev.parent); | ||
828 | dev_id.devno = sch->schib.pmcw.dev; | ||
829 | dev_id.ssid = sch->schid.ssid; | ||
830 | |||
831 | /* | ||
832 | * Move the orphaned ccw device to the orphanage so the replacing | ||
833 | * ccw device can take its place on the subchannel. | ||
834 | */ | ||
835 | ret = device_move(&cdev->dev, &css->pseudo_subchannel->dev); | ||
836 | if (ret) { | ||
837 | CIO_MSG_EVENT(0, "Moving device 0.%x.%04x to orphanage failed " | ||
838 | "(ret=%d)!\n", cdev->private->dev_id.ssid, | ||
839 | cdev->private->dev_id.devno, ret); | ||
840 | return; | ||
841 | } | ||
842 | cdev->ccwlock = css->pseudo_subchannel->lock; | ||
843 | /* | ||
844 | * Search for the replacing ccw device | ||
845 | * - among the disconnected devices | ||
846 | * - in the orphanage | ||
847 | */ | ||
848 | replacing_cdev = get_disc_ccwdev_by_dev_id(&dev_id, cdev); | ||
849 | if (replacing_cdev) { | ||
850 | sch_attach_disconnected_device(sch, replacing_cdev); | ||
851 | return; | ||
852 | } | ||
853 | replacing_cdev = get_orphaned_ccwdev_by_dev_id(css, &dev_id); | ||
854 | if (replacing_cdev) { | ||
855 | sch_attach_orphaned_device(sch, replacing_cdev); | ||
856 | return; | ||
857 | } | ||
858 | sch_create_and_recog_new_device(sch); | ||
859 | } | ||
860 | |||
739 | /* | 861 | /* |
740 | * Register recognized device. | 862 | * Register recognized device. |
741 | */ | 863 | */ |
@@ -890,12 +1012,55 @@ io_subchannel_recog(struct ccw_device *cdev, struct subchannel *sch) | |||
890 | return rc; | 1012 | return rc; |
891 | } | 1013 | } |
892 | 1014 | ||
1015 | static void ccw_device_move_to_sch(struct work_struct *work) | ||
1016 | { | ||
1017 | struct ccw_device_private *priv; | ||
1018 | int rc; | ||
1019 | struct subchannel *sch; | ||
1020 | struct ccw_device *cdev; | ||
1021 | struct subchannel *former_parent; | ||
1022 | |||
1023 | priv = container_of(work, struct ccw_device_private, kick_work); | ||
1024 | sch = priv->sch; | ||
1025 | cdev = priv->cdev; | ||
1026 | former_parent = ccw_device_is_orphan(cdev) ? | ||
1027 | NULL : to_subchannel(get_device(cdev->dev.parent)); | ||
1028 | mutex_lock(&sch->reg_mutex); | ||
1029 | /* Try to move the ccw device to its new subchannel. */ | ||
1030 | rc = device_move(&cdev->dev, &sch->dev); | ||
1031 | mutex_unlock(&sch->reg_mutex); | ||
1032 | if (rc) { | ||
1033 | CIO_MSG_EVENT(2, "Moving device 0.%x.%04x to subchannel " | ||
1034 | "0.%x.%04x failed (ret=%d)!\n", | ||
1035 | cdev->private->dev_id.ssid, | ||
1036 | cdev->private->dev_id.devno, sch->schid.ssid, | ||
1037 | sch->schid.sch_no, rc); | ||
1038 | css_sch_device_unregister(sch); | ||
1039 | goto out; | ||
1040 | } | ||
1041 | if (former_parent) { | ||
1042 | spin_lock_irq(former_parent->lock); | ||
1043 | former_parent->dev.driver_data = NULL; | ||
1044 | spin_unlock_irq(former_parent->lock); | ||
1045 | css_sch_device_unregister(former_parent); | ||
1046 | /* Reset intparm to zeroes. */ | ||
1047 | former_parent->schib.pmcw.intparm = 0; | ||
1048 | cio_modify(former_parent); | ||
1049 | } | ||
1050 | sch_attach_device(sch, cdev); | ||
1051 | out: | ||
1052 | if (former_parent) | ||
1053 | put_device(&former_parent->dev); | ||
1054 | put_device(&cdev->dev); | ||
1055 | } | ||
1056 | |||
893 | static int | 1057 | static int |
894 | io_subchannel_probe (struct subchannel *sch) | 1058 | io_subchannel_probe (struct subchannel *sch) |
895 | { | 1059 | { |
896 | struct ccw_device *cdev; | 1060 | struct ccw_device *cdev; |
897 | int rc; | 1061 | int rc; |
898 | unsigned long flags; | 1062 | unsigned long flags; |
1063 | struct ccw_dev_id dev_id; | ||
899 | 1064 | ||
900 | if (sch->dev.driver_data) { | 1065 | if (sch->dev.driver_data) { |
901 | /* | 1066 | /* |
@@ -918,6 +1083,28 @@ io_subchannel_probe (struct subchannel *sch) | |||
918 | get_device(&cdev->dev); | 1083 | get_device(&cdev->dev); |
919 | return 0; | 1084 | return 0; |
920 | } | 1085 | } |
1086 | /* | ||
1087 | * First check if a fitting device may be found amongst the | ||
1088 | * disconnected devices or in the orphanage. | ||
1089 | */ | ||
1090 | dev_id.devno = sch->schib.pmcw.dev; | ||
1091 | dev_id.ssid = sch->schid.ssid; | ||
1092 | cdev = get_disc_ccwdev_by_dev_id(&dev_id, NULL); | ||
1093 | if (!cdev) | ||
1094 | cdev = get_orphaned_ccwdev_by_dev_id(to_css(sch->dev.parent), | ||
1095 | &dev_id); | ||
1096 | if (cdev) { | ||
1097 | /* | ||
1098 | * Schedule moving the device until when we have a registered | ||
1099 | * subchannel to move to and succeed the probe. We can | ||
1100 | * unregister later again, when the probe is through. | ||
1101 | */ | ||
1102 | cdev->private->sch = sch; | ||
1103 | PREPARE_WORK(&cdev->private->kick_work, | ||
1104 | ccw_device_move_to_sch); | ||
1105 | queue_work(slow_path_wq, &cdev->private->kick_work); | ||
1106 | return 0; | ||
1107 | } | ||
921 | cdev = io_subchannel_create_ccwdev(sch); | 1108 | cdev = io_subchannel_create_ccwdev(sch); |
922 | if (IS_ERR(cdev)) | 1109 | if (IS_ERR(cdev)) |
923 | return PTR_ERR(cdev); | 1110 | return PTR_ERR(cdev); |