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 | |
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>
-rw-r--r-- | drivers/s390/cio/cio.c | 6 | ||||
-rw-r--r-- | drivers/s390/cio/cio.h | 2 | ||||
-rw-r--r-- | drivers/s390/cio/css.c | 37 | ||||
-rw-r--r-- | drivers/s390/cio/css.h | 4 | ||||
-rw-r--r-- | drivers/s390/cio/device.c | 287 | ||||
-rw-r--r-- | drivers/s390/cio/device.h | 2 | ||||
-rw-r--r-- | drivers/s390/cio/device_fsm.c | 17 |
7 files changed, 296 insertions, 59 deletions
diff --git a/drivers/s390/cio/cio.c b/drivers/s390/cio/cio.c index e8d331493fd8..7835a714a405 100644 --- a/drivers/s390/cio/cio.c +++ b/drivers/s390/cio/cio.c | |||
@@ -415,6 +415,8 @@ cio_enable_subchannel (struct subchannel *sch, unsigned int isc) | |||
415 | CIO_TRACE_EVENT (2, "ensch"); | 415 | CIO_TRACE_EVENT (2, "ensch"); |
416 | CIO_TRACE_EVENT (2, sch->dev.bus_id); | 416 | CIO_TRACE_EVENT (2, sch->dev.bus_id); |
417 | 417 | ||
418 | if (sch_is_pseudo_sch(sch)) | ||
419 | return -EINVAL; | ||
418 | ccode = stsch (sch->schid, &sch->schib); | 420 | ccode = stsch (sch->schid, &sch->schib); |
419 | if (ccode) | 421 | if (ccode) |
420 | return -ENODEV; | 422 | return -ENODEV; |
@@ -462,6 +464,8 @@ cio_disable_subchannel (struct subchannel *sch) | |||
462 | CIO_TRACE_EVENT (2, "dissch"); | 464 | CIO_TRACE_EVENT (2, "dissch"); |
463 | CIO_TRACE_EVENT (2, sch->dev.bus_id); | 465 | CIO_TRACE_EVENT (2, sch->dev.bus_id); |
464 | 466 | ||
467 | if (sch_is_pseudo_sch(sch)) | ||
468 | return 0; | ||
465 | ccode = stsch (sch->schid, &sch->schib); | 469 | ccode = stsch (sch->schid, &sch->schib); |
466 | if (ccode == 3) /* Not operational. */ | 470 | if (ccode == 3) /* Not operational. */ |
467 | return -ENODEV; | 471 | return -ENODEV; |
@@ -496,7 +500,7 @@ cio_disable_subchannel (struct subchannel *sch) | |||
496 | return ret; | 500 | return ret; |
497 | } | 501 | } |
498 | 502 | ||
499 | static int cio_create_sch_lock(struct subchannel *sch) | 503 | int cio_create_sch_lock(struct subchannel *sch) |
500 | { | 504 | { |
501 | sch->lock = kmalloc(sizeof(spinlock_t), GFP_KERNEL); | 505 | sch->lock = kmalloc(sizeof(spinlock_t), GFP_KERNEL); |
502 | if (!sch->lock) | 506 | if (!sch->lock) |
diff --git a/drivers/s390/cio/cio.h b/drivers/s390/cio/cio.h index 7e7369b21a93..35154a210357 100644 --- a/drivers/s390/cio/cio.h +++ b/drivers/s390/cio/cio.h | |||
@@ -131,6 +131,8 @@ extern int cio_set_options (struct subchannel *, int); | |||
131 | extern int cio_get_options (struct subchannel *); | 131 | extern int cio_get_options (struct subchannel *); |
132 | extern int cio_modify (struct subchannel *); | 132 | extern int cio_modify (struct subchannel *); |
133 | 133 | ||
134 | int cio_create_sch_lock(struct subchannel *); | ||
135 | |||
134 | /* Use with care. */ | 136 | /* Use with care. */ |
135 | #ifdef CONFIG_CCW_CONSOLE | 137 | #ifdef CONFIG_CCW_CONSOLE |
136 | extern struct subchannel *cio_probe_console(void); | 138 | extern struct subchannel *cio_probe_console(void); |
diff --git a/drivers/s390/cio/css.c b/drivers/s390/cio/css.c index 65939e2eb415..0bf716619378 100644 --- a/drivers/s390/cio/css.c +++ b/drivers/s390/cio/css.c | |||
@@ -580,12 +580,24 @@ css_cm_enable_store(struct device *dev, struct device_attribute *attr, | |||
580 | 580 | ||
581 | static DEVICE_ATTR(cm_enable, 0644, css_cm_enable_show, css_cm_enable_store); | 581 | static DEVICE_ATTR(cm_enable, 0644, css_cm_enable_show, css_cm_enable_store); |
582 | 582 | ||
583 | static inline void __init | 583 | static inline int __init setup_css(int nr) |
584 | setup_css(int nr) | ||
585 | { | 584 | { |
586 | u32 tod_high; | 585 | u32 tod_high; |
586 | int ret; | ||
587 | 587 | ||
588 | memset(css[nr], 0, sizeof(struct channel_subsystem)); | 588 | memset(css[nr], 0, sizeof(struct channel_subsystem)); |
589 | css[nr]->pseudo_subchannel = | ||
590 | kzalloc(sizeof(*css[nr]->pseudo_subchannel), GFP_KERNEL); | ||
591 | if (!css[nr]->pseudo_subchannel) | ||
592 | return -ENOMEM; | ||
593 | css[nr]->pseudo_subchannel->dev.parent = &css[nr]->device; | ||
594 | css[nr]->pseudo_subchannel->dev.release = css_subchannel_release; | ||
595 | sprintf(css[nr]->pseudo_subchannel->dev.bus_id, "defunct"); | ||
596 | ret = cio_create_sch_lock(css[nr]->pseudo_subchannel); | ||
597 | if (ret) { | ||
598 | kfree(css[nr]->pseudo_subchannel); | ||
599 | return ret; | ||
600 | } | ||
589 | mutex_init(&css[nr]->mutex); | 601 | mutex_init(&css[nr]->mutex); |
590 | css[nr]->valid = 1; | 602 | css[nr]->valid = 1; |
591 | css[nr]->cssid = nr; | 603 | css[nr]->cssid = nr; |
@@ -593,6 +605,7 @@ setup_css(int nr) | |||
593 | css[nr]->device.release = channel_subsystem_release; | 605 | css[nr]->device.release = channel_subsystem_release; |
594 | tod_high = (u32) (get_clock() >> 32); | 606 | tod_high = (u32) (get_clock() >> 32); |
595 | css_generate_pgid(css[nr], tod_high); | 607 | css_generate_pgid(css[nr], tod_high); |
608 | return 0; | ||
596 | } | 609 | } |
597 | 610 | ||
598 | /* | 611 | /* |
@@ -629,10 +642,12 @@ init_channel_subsystem (void) | |||
629 | ret = -ENOMEM; | 642 | ret = -ENOMEM; |
630 | goto out_unregister; | 643 | goto out_unregister; |
631 | } | 644 | } |
632 | setup_css(i); | 645 | ret = setup_css(i); |
633 | ret = device_register(&css[i]->device); | ||
634 | if (ret) | 646 | if (ret) |
635 | goto out_free; | 647 | goto out_free; |
648 | ret = device_register(&css[i]->device); | ||
649 | if (ret) | ||
650 | goto out_free_all; | ||
636 | if (css_characteristics_avail && | 651 | if (css_characteristics_avail && |
637 | css_chsc_characteristics.secm) { | 652 | css_chsc_characteristics.secm) { |
638 | ret = device_create_file(&css[i]->device, | 653 | ret = device_create_file(&css[i]->device, |
@@ -640,6 +655,9 @@ init_channel_subsystem (void) | |||
640 | if (ret) | 655 | if (ret) |
641 | goto out_device; | 656 | goto out_device; |
642 | } | 657 | } |
658 | ret = device_register(&css[i]->pseudo_subchannel->dev); | ||
659 | if (ret) | ||
660 | goto out_file; | ||
643 | } | 661 | } |
644 | css_init_done = 1; | 662 | css_init_done = 1; |
645 | 663 | ||
@@ -647,13 +665,19 @@ init_channel_subsystem (void) | |||
647 | 665 | ||
648 | for_each_subchannel(__init_channel_subsystem, NULL); | 666 | for_each_subchannel(__init_channel_subsystem, NULL); |
649 | return 0; | 667 | return 0; |
668 | out_file: | ||
669 | device_remove_file(&css[i]->device, &dev_attr_cm_enable); | ||
650 | out_device: | 670 | out_device: |
651 | device_unregister(&css[i]->device); | 671 | device_unregister(&css[i]->device); |
672 | out_free_all: | ||
673 | kfree(css[i]->pseudo_subchannel->lock); | ||
674 | kfree(css[i]->pseudo_subchannel); | ||
652 | out_free: | 675 | out_free: |
653 | kfree(css[i]); | 676 | kfree(css[i]); |
654 | out_unregister: | 677 | out_unregister: |
655 | while (i > 0) { | 678 | while (i > 0) { |
656 | i--; | 679 | i--; |
680 | device_unregister(&css[i]->pseudo_subchannel->dev); | ||
657 | if (css_characteristics_avail && css_chsc_characteristics.secm) | 681 | if (css_characteristics_avail && css_chsc_characteristics.secm) |
658 | device_remove_file(&css[i]->device, | 682 | device_remove_file(&css[i]->device, |
659 | &dev_attr_cm_enable); | 683 | &dev_attr_cm_enable); |
@@ -665,6 +689,11 @@ out: | |||
665 | return ret; | 689 | return ret; |
666 | } | 690 | } |
667 | 691 | ||
692 | int sch_is_pseudo_sch(struct subchannel *sch) | ||
693 | { | ||
694 | return sch == to_css(sch->dev.parent)->pseudo_subchannel; | ||
695 | } | ||
696 | |||
668 | /* | 697 | /* |
669 | * find a driver for a subchannel. They identify by the subchannel | 698 | * find a driver for a subchannel. They identify by the subchannel |
670 | * type with the exception that the console subchannel driver has its own | 699 | * type with the exception that the console subchannel driver has its own |
diff --git a/drivers/s390/cio/css.h b/drivers/s390/cio/css.h index ac845c1ebf83..71fe380f86f6 100644 --- a/drivers/s390/cio/css.h +++ b/drivers/s390/cio/css.h | |||
@@ -160,6 +160,8 @@ struct channel_subsystem { | |||
160 | int cm_enabled; | 160 | int cm_enabled; |
161 | void *cub_addr1; | 161 | void *cub_addr1; |
162 | void *cub_addr2; | 162 | void *cub_addr2; |
163 | /* for orphaned ccw devices */ | ||
164 | struct subchannel *pseudo_subchannel; | ||
163 | }; | 165 | }; |
164 | #define to_css(dev) container_of(dev, struct channel_subsystem, device) | 166 | #define to_css(dev) container_of(dev, struct channel_subsystem, device) |
165 | 167 | ||
@@ -187,6 +189,8 @@ void css_clear_subchannel_slow_list(void); | |||
187 | int css_slow_subchannels_exist(void); | 189 | int css_slow_subchannels_exist(void); |
188 | extern int need_rescan; | 190 | extern int need_rescan; |
189 | 191 | ||
192 | int sch_is_pseudo_sch(struct subchannel *); | ||
193 | |||
190 | extern struct workqueue_struct *slow_path_wq; | 194 | extern struct workqueue_struct *slow_path_wq; |
191 | extern struct work_struct slow_path_work; | 195 | extern struct work_struct slow_path_work; |
192 | 196 | ||
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); |
diff --git a/drivers/s390/cio/device.h b/drivers/s390/cio/device.h index d5fe95e04cfe..29db6341d632 100644 --- a/drivers/s390/cio/device.h +++ b/drivers/s390/cio/device.h | |||
@@ -80,6 +80,8 @@ int ccw_device_cancel_halt_clear(struct ccw_device *); | |||
80 | 80 | ||
81 | void ccw_device_do_unreg_rereg(struct work_struct *); | 81 | void ccw_device_do_unreg_rereg(struct work_struct *); |
82 | void ccw_device_call_sch_unregister(struct work_struct *); | 82 | void ccw_device_call_sch_unregister(struct work_struct *); |
83 | void ccw_device_move_to_orphanage(struct work_struct *); | ||
84 | int ccw_device_is_orphan(struct ccw_device *); | ||
83 | 85 | ||
84 | int ccw_device_recognition(struct ccw_device *); | 86 | int ccw_device_recognition(struct ccw_device *); |
85 | int ccw_device_online(struct ccw_device *); | 87 | int ccw_device_online(struct ccw_device *); |
diff --git a/drivers/s390/cio/device_fsm.c b/drivers/s390/cio/device_fsm.c index a487fb0e7d3d..eed14572fc3b 100644 --- a/drivers/s390/cio/device_fsm.c +++ b/drivers/s390/cio/device_fsm.c | |||
@@ -186,13 +186,12 @@ ccw_device_handle_oper(struct ccw_device *cdev) | |||
186 | /* | 186 | /* |
187 | * Check if cu type and device type still match. If | 187 | * Check if cu type and device type still match. If |
188 | * not, it is certainly another device and we have to | 188 | * not, it is certainly another device and we have to |
189 | * de- and re-register. Also check here for non-matching devno. | 189 | * de- and re-register. |
190 | */ | 190 | */ |
191 | if (cdev->id.cu_type != cdev->private->senseid.cu_type || | 191 | if (cdev->id.cu_type != cdev->private->senseid.cu_type || |
192 | cdev->id.cu_model != cdev->private->senseid.cu_model || | 192 | cdev->id.cu_model != cdev->private->senseid.cu_model || |
193 | cdev->id.dev_type != cdev->private->senseid.dev_type || | 193 | cdev->id.dev_type != cdev->private->senseid.dev_type || |
194 | cdev->id.dev_model != cdev->private->senseid.dev_model || | 194 | cdev->id.dev_model != cdev->private->senseid.dev_model) { |
195 | cdev->private->dev_id.devno != sch->schib.pmcw.dev) { | ||
196 | PREPARE_WORK(&cdev->private->kick_work, | 195 | PREPARE_WORK(&cdev->private->kick_work, |
197 | ccw_device_do_unreg_rereg); | 196 | ccw_device_do_unreg_rereg); |
198 | queue_work(ccw_device_work, &cdev->private->kick_work); | 197 | queue_work(ccw_device_work, &cdev->private->kick_work); |
@@ -676,6 +675,10 @@ ccw_device_offline(struct ccw_device *cdev) | |||
676 | { | 675 | { |
677 | struct subchannel *sch; | 676 | struct subchannel *sch; |
678 | 677 | ||
678 | if (ccw_device_is_orphan(cdev)) { | ||
679 | ccw_device_done(cdev, DEV_STATE_OFFLINE); | ||
680 | return 0; | ||
681 | } | ||
679 | sch = to_subchannel(cdev->dev.parent); | 682 | sch = to_subchannel(cdev->dev.parent); |
680 | if (stsch(sch->schid, &sch->schib) || !sch->schib.pmcw.dnv) | 683 | if (stsch(sch->schid, &sch->schib) || !sch->schib.pmcw.dnv) |
681 | return -ENODEV; | 684 | return -ENODEV; |
@@ -1121,7 +1124,13 @@ device_trigger_reprobe(struct subchannel *sch) | |||
1121 | sch->schib.pmcw.mp = 1; | 1124 | sch->schib.pmcw.mp = 1; |
1122 | sch->schib.pmcw.intparm = (__u32)(unsigned long)sch; | 1125 | sch->schib.pmcw.intparm = (__u32)(unsigned long)sch; |
1123 | /* We should also udate ssd info, but this has to wait. */ | 1126 | /* We should also udate ssd info, but this has to wait. */ |
1124 | ccw_device_start_id(cdev, 0); | 1127 | /* Check if this is another device which appeared on the same sch. */ |
1128 | if (sch->schib.pmcw.dev != cdev->private->dev_id.devno) { | ||
1129 | PREPARE_WORK(&cdev->private->kick_work, | ||
1130 | ccw_device_move_to_orphanage); | ||
1131 | queue_work(ccw_device_work, &cdev->private->kick_work); | ||
1132 | } else | ||
1133 | ccw_device_start_id(cdev, 0); | ||
1125 | } | 1134 | } |
1126 | 1135 | ||
1127 | static void | 1136 | static void |