diff options
author | Peter Oberparleiter <peter.oberparleiter@de.ibm.com> | 2009-12-07 12:51:17 +0100 |
---|---|---|
committer | Martin Schwidefsky <sky@mschwide.boeblingen.de.ibm.com> | 2009-12-07 12:51:29 +0100 |
commit | 5d6e6b6f6f3eac10a7f5a15e961bac3b36824d9d (patch) | |
tree | b64af704c59b0820c0537bd0bed45caa0d5e26b7 /drivers/s390/cio/device.c | |
parent | 60e4dac1abdf49ccdb7545ec406325f08423d848 (diff) |
[S390] cio: introduce parent-initiated device move
Change the initiative to update subchannel-ccw device associations
to the subchannel: when there is an indication that the internal
association no longer reflects the current hardware state, mark
each affected subchannel as requiring attention. Once processing
reaches a subchannel, determine the correct association for that
subchannel at that time and perform the necessary device_move
operations.
This change fixes problems with the previous approach which would
leave devices in an inconsistent state when a new hardware change
occurred while a device_move was already scheduled.
Signed-off-by: Peter Oberparleiter <peter.oberparleiter@de.ibm.com>
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 | 513 |
1 files changed, 182 insertions, 331 deletions
diff --git a/drivers/s390/cio/device.c b/drivers/s390/cio/device.c index 0efecefdb83..6097763f103 100644 --- a/drivers/s390/cio/device.c +++ b/drivers/s390/cio/device.c @@ -673,57 +673,19 @@ static int ccw_device_register(struct ccw_device *cdev) return ret; } -struct match_data { - struct ccw_dev_id dev_id; - struct ccw_device * sibling; -}; - -static int -match_devno(struct device * dev, void * data) -{ - struct match_data * d = data; - struct ccw_device * cdev; - - cdev = to_ccwdev(dev); - if ((cdev->private->state == DEV_STATE_DISCONNECTED) && - !ccw_device_is_orphan(cdev) && - ccw_dev_id_is_equal(&cdev->private->dev_id, &d->dev_id) && - (cdev != d->sibling)) - return 1; - return 0; -} - -static struct ccw_device * get_disc_ccwdev_by_dev_id(struct ccw_dev_id *dev_id, - struct ccw_device *sibling) +static int match_dev_id(struct device *dev, void *data) { - struct device *dev; - struct match_data data; - - data.dev_id = *dev_id; - data.sibling = sibling; - dev = bus_find_device(&ccw_bus_type, NULL, &data, match_devno); - - return dev ? to_ccwdev(dev) : NULL; -} - -static int match_orphan(struct device *dev, void *data) -{ - struct ccw_dev_id *dev_id; - struct ccw_device *cdev; + struct ccw_device *cdev = to_ccwdev(dev); + struct ccw_dev_id *dev_id = data; - dev_id = data; - cdev = to_ccwdev(dev); return ccw_dev_id_is_equal(&cdev->private->dev_id, dev_id); } -static struct ccw_device * -get_orphaned_ccwdev_by_dev_id(struct channel_subsystem *css, - struct ccw_dev_id *dev_id) +static struct ccw_device *get_ccwdev_by_dev_id(struct ccw_dev_id *dev_id) { struct device *dev; - dev = device_find_child(&css->pseudo_subchannel->dev, dev_id, - match_orphan); + dev = bus_find_device(&ccw_bus_type, NULL, dev_id, match_dev_id); return dev ? to_ccwdev(dev) : NULL; } @@ -808,75 +770,6 @@ static struct ccw_device * io_subchannel_create_ccwdev(struct subchannel *sch) static int io_subchannel_recog(struct ccw_device *, struct subchannel *); -static void sch_attach_device(struct subchannel *sch, - struct ccw_device *cdev) -{ - css_update_ssd_info(sch); - spin_lock_irq(sch->lock); - sch_set_cdev(sch, cdev); - cdev->private->schid = sch->schid; - cdev->ccwlock = sch->lock; - ccw_device_trigger_reprobe(cdev); - spin_unlock_irq(sch->lock); -} - -static void sch_attach_disconnected_device(struct subchannel *sch, - struct ccw_device *cdev) -{ - struct subchannel *other_sch; - int ret; - - /* Get reference for new parent. */ - if (!get_device(&sch->dev)) - return; - other_sch = to_subchannel(cdev->dev.parent); - /* Note: device_move() changes cdev->dev.parent */ - ret = device_move(&cdev->dev, &sch->dev, DPM_ORDER_PARENT_BEFORE_DEV); - if (ret) { - CIO_MSG_EVENT(0, "Moving disconnected device 0.%x.%04x failed " - "(ret=%d)!\n", cdev->private->dev_id.ssid, - cdev->private->dev_id.devno, ret); - /* Put reference for new parent. */ - put_device(&sch->dev); - return; - } - sch_set_cdev(other_sch, NULL); - /* No need to keep a subchannel without ccw device around. */ - css_sch_device_unregister(other_sch); - sch_attach_device(sch, cdev); - /* Put reference for old parent. */ - put_device(&other_sch->dev); -} - -static void sch_attach_orphaned_device(struct subchannel *sch, - struct ccw_device *cdev) -{ - int ret; - struct subchannel *pseudo_sch; - - /* Get reference for new parent. */ - if (!get_device(&sch->dev)) - return; - pseudo_sch = to_subchannel(cdev->dev.parent); - /* - * Try to move the ccw device to its new subchannel. - * Note: device_move() changes cdev->dev.parent - */ - ret = device_move(&cdev->dev, &sch->dev, DPM_ORDER_PARENT_BEFORE_DEV); - if (ret) { - CIO_MSG_EVENT(0, "Moving device 0.%x.%04x from orphanage " - "failed (ret=%d)!\n", - cdev->private->dev_id.ssid, - cdev->private->dev_id.devno, ret); - /* Put reference for new parent. */ - put_device(&sch->dev); - return; - } - sch_attach_device(sch, cdev); - /* Put reference on pseudo subchannel. */ - put_device(&pseudo_sch->dev); -} - static void sch_create_and_recog_new_device(struct subchannel *sch) { struct ccw_device *cdev; @@ -901,70 +794,6 @@ static void sch_create_and_recog_new_device(struct subchannel *sch) } } - -void ccw_device_move_to_orphanage(struct work_struct *work) -{ - struct ccw_device_private *priv; - struct ccw_device *cdev; - struct ccw_device *replacing_cdev; - struct subchannel *sch; - int ret; - struct channel_subsystem *css; - struct ccw_dev_id dev_id; - - priv = container_of(work, struct ccw_device_private, kick_work); - cdev = priv->cdev; - sch = to_subchannel(cdev->dev.parent); - css = to_css(sch->dev.parent); - dev_id.devno = sch->schib.pmcw.dev; - dev_id.ssid = sch->schid.ssid; - - /* Increase refcount for pseudo subchannel. */ - get_device(&css->pseudo_subchannel->dev); - /* - * Move the orphaned ccw device to the orphanage so the replacing - * ccw device can take its place on the subchannel. - * Note: device_move() changes cdev->dev.parent - */ - ret = device_move(&cdev->dev, &css->pseudo_subchannel->dev, - DPM_ORDER_NONE); - if (ret) { - CIO_MSG_EVENT(0, "Moving device 0.%x.%04x to orphanage failed " - "(ret=%d)!\n", cdev->private->dev_id.ssid, - cdev->private->dev_id.devno, ret); - /* Decrease refcount for pseudo subchannel again. */ - put_device(&css->pseudo_subchannel->dev); - return; - } - cdev->ccwlock = css->pseudo_subchannel->lock; - /* - * Search for the replacing ccw device - * - among the disconnected devices - * - in the orphanage - */ - replacing_cdev = get_disc_ccwdev_by_dev_id(&dev_id, cdev); - if (replacing_cdev) { - sch_attach_disconnected_device(sch, replacing_cdev); - /* Release reference from get_disc_ccwdev_by_dev_id() */ - put_device(&replacing_cdev->dev); - /* Release reference of subchannel from old cdev. */ - put_device(&sch->dev); - return; - } - replacing_cdev = get_orphaned_ccwdev_by_dev_id(css, &dev_id); - if (replacing_cdev) { - sch_attach_orphaned_device(sch, replacing_cdev); - /* Release reference from get_orphaned_ccwdev_by_dev_id() */ - put_device(&replacing_cdev->dev); - /* Release reference of subchannel from old cdev. */ - put_device(&sch->dev); - return; - } - sch_create_and_recog_new_device(sch); - /* Release reference of subchannel from old cdev. */ - put_device(&sch->dev); -} - /* * Register recognized device. */ @@ -1131,53 +960,56 @@ io_subchannel_recog(struct ccw_device *cdev, struct subchannel *sch) return rc; } -static void ccw_device_move_to_sch(struct work_struct *work) +static int ccw_device_move_to_sch(struct ccw_device *cdev, + struct subchannel *sch) { - struct ccw_device_private *priv; + struct subchannel *old_sch; int rc; - struct subchannel *sch; - struct ccw_device *cdev; - struct subchannel *former_parent; - priv = container_of(work, struct ccw_device_private, kick_work); - sch = priv->sch; - cdev = priv->cdev; - former_parent = to_subchannel(cdev->dev.parent); - /* Get reference for new parent. */ + old_sch = to_subchannel(cdev->dev.parent); + /* Obtain child reference for new parent. */ if (!get_device(&sch->dev)) - return; + return -ENODEV; mutex_lock(&sch->reg_mutex); - /* - * Try to move the ccw device to its new subchannel. - * Note: device_move() changes cdev->dev.parent - */ rc = device_move(&cdev->dev, &sch->dev, DPM_ORDER_PARENT_BEFORE_DEV); mutex_unlock(&sch->reg_mutex); if (rc) { - CIO_MSG_EVENT(0, "Moving device 0.%x.%04x to subchannel " - "0.%x.%04x failed (ret=%d)!\n", + CIO_MSG_EVENT(0, "device_move(0.%x.%04x,0.%x.%04x)=%d\n", cdev->private->dev_id.ssid, cdev->private->dev_id.devno, sch->schid.ssid, - sch->schid.sch_no, rc); - css_sch_device_unregister(sch); - /* Put reference for new parent again. */ + sch->schib.pmcw.dev, rc); + /* Release child reference for new parent. */ put_device(&sch->dev); - goto out; + return rc; } - if (!sch_is_pseudo_sch(former_parent)) { - spin_lock_irq(former_parent->lock); - sch_set_cdev(former_parent, NULL); - spin_unlock_irq(former_parent->lock); - css_sch_device_unregister(former_parent); - /* Reset intparm to zeroes. */ - former_parent->config.intparm = 0; - cio_commit_config(former_parent); + /* Clean up old subchannel. */ + if (!sch_is_pseudo_sch(old_sch)) { + spin_lock_irq(old_sch->lock); + sch_set_cdev(old_sch, NULL); + cio_disable_subchannel(old_sch); + spin_unlock_irq(old_sch->lock); + css_schedule_eval(old_sch->schid); } - sch_attach_device(sch, cdev); -out: - /* Put reference for old parent. */ - put_device(&former_parent->dev); - put_device(&cdev->dev); + /* Release child reference for old parent. */ + put_device(&old_sch->dev); + /* Initialize new subchannel. */ + spin_lock_irq(sch->lock); + cdev->private->schid = sch->schid; + cdev->ccwlock = sch->lock; + if (!sch_is_pseudo_sch(sch)) + sch_set_cdev(sch, cdev); + spin_unlock_irq(sch->lock); + if (!sch_is_pseudo_sch(sch)) + css_update_ssd_info(sch); + return 0; +} + +static int ccw_device_move_to_orph(struct ccw_device *cdev) +{ + struct subchannel *sch = to_subchannel(cdev->dev.parent); + struct channel_subsystem *css = to_css(sch->dev.parent); + + return ccw_device_move_to_sch(cdev, css->pseudo_subchannel); } static void io_subchannel_irq(struct subchannel *sch) @@ -1244,8 +1076,6 @@ static int io_subchannel_probe(struct subchannel *sch) { struct ccw_device *cdev; int rc; - unsigned long flags; - struct ccw_dev_id dev_id; if (cio_is_console(sch->schid)) { rc = sysfs_create_group(&sch->dev.kobj, @@ -1290,37 +1120,7 @@ static int io_subchannel_probe(struct subchannel *sch) GFP_KERNEL | GFP_DMA); if (!sch->private) goto out_schedule; - /* - * First check if a fitting device may be found amongst the - * disconnected devices or in the orphanage. - */ - dev_id.devno = sch->schib.pmcw.dev; - dev_id.ssid = sch->schid.ssid; - cdev = get_disc_ccwdev_by_dev_id(&dev_id, NULL); - if (!cdev) - cdev = get_orphaned_ccwdev_by_dev_id(to_css(sch->dev.parent), - &dev_id); - if (cdev) { - /* - * Schedule moving the device until when we have a registered - * subchannel to move to and succeed the probe. We can - * unregister later again, when the probe is through. - */ - cdev->private->sch = sch; - PREPARE_WORK(&cdev->private->kick_work, - ccw_device_move_to_sch); - queue_work(slow_path_wq, &cdev->private->kick_work); - return 0; - } - cdev = io_subchannel_create_ccwdev(sch); - if (IS_ERR(cdev)) - goto out_schedule; - rc = io_subchannel_recog(cdev, sch); - if (rc) { - spin_lock_irqsave(sch->lock, flags); - io_subchannel_recog_done(cdev); - spin_unlock_irqrestore(sch->lock, flags); - } + css_schedule_eval(sch->schid); return 0; out_schedule: @@ -1349,16 +1149,6 @@ out_free: return 0; } -static int io_subchannel_notify(struct subchannel *sch, int event) -{ - struct ccw_device *cdev; - - cdev = sch_get_cdev(sch); - if (!cdev) - return 0; - return ccw_device_notify(cdev, event); -} - static void io_subchannel_verify(struct subchannel *sch) { struct ccw_device *cdev; @@ -1482,19 +1272,6 @@ io_subchannel_shutdown(struct subchannel *sch) cio_disable_subchannel(sch); } -static int io_subchannel_get_status(struct subchannel *sch) -{ - struct schib schib; - - if (stsch(sch->schid, &schib) || !schib.pmcw.dnv) - return CIO_GONE; - if (sch->schib.pmcw.dnv && (schib.pmcw.dev != sch->schib.pmcw.dev)) - return CIO_REVALIDATE; - if (!sch->lpm) - return CIO_NO_PATH; - return CIO_OPER; -} - static int device_is_disconnected(struct ccw_device *cdev) { if (!cdev) @@ -1626,91 +1403,165 @@ void ccw_device_set_notoper(struct ccw_device *cdev) cdev->private->state = DEV_STATE_NOT_OPER; } -static int io_subchannel_sch_event(struct subchannel *sch, int slow) +enum io_sch_action { + IO_SCH_UNREG, + IO_SCH_ORPH_UNREG, + IO_SCH_ATTACH, + IO_SCH_UNREG_ATTACH, + IO_SCH_ORPH_ATTACH, + IO_SCH_REPROBE, + IO_SCH_VERIFY, + IO_SCH_DISC, + IO_SCH_NOP, +}; + +static enum io_sch_action sch_get_action(struct subchannel *sch) +{ + struct ccw_device *cdev; + + cdev = sch_get_cdev(sch); + if (cio_update_schib(sch)) { + /* Not operational. */ + if (!cdev) + return IO_SCH_UNREG; + if (!ccw_device_notify(cdev, CIO_GONE)) + return IO_SCH_UNREG; + return IO_SCH_ORPH_UNREG; + } + /* Operational. */ + if (!cdev) + return IO_SCH_ATTACH; + if (sch->schib.pmcw.dev != cdev->private->dev_id.devno) { + if (!ccw_device_notify(cdev, CIO_GONE)) + return IO_SCH_UNREG_ATTACH; + return IO_SCH_ORPH_ATTACH; + } + if ((sch->schib.pmcw.pam & sch->opm) == 0) { + if (!ccw_device_notify(cdev, CIO_NO_PATH)) + return IO_SCH_UNREG; + return IO_SCH_DISC; + } + if (device_is_disconnected(cdev)) + return IO_SCH_REPROBE; + if (cdev->online) + return IO_SCH_VERIFY; + return IO_SCH_NOP; +} + +/** + * io_subchannel_sch_event - process subchannel event + * @sch: subchannel + * @process: non-zero if function is called in process context + * + * An unspecified event occurred for this subchannel. Adjust data according + * to the current operational state of the subchannel and device. Return + * zero when the event has been handled sufficiently or -EAGAIN when this + * function should be called again in process context. + */ +static int io_subchannel_sch_event(struct subchannel *sch, int process) { - int event, ret, disc; unsigned long flags; - enum { NONE, UNREGISTER, UNREGISTER_PROBE, REPROBE, DISC } action; struct ccw_device *cdev; + struct ccw_dev_id dev_id; + enum io_sch_action action; + int rc = -EAGAIN; spin_lock_irqsave(sch->lock, flags); + if (!device_is_registered(&sch->dev)) + goto out_unlock; + action = sch_get_action(sch); + CIO_MSG_EVENT(2, "event: sch 0.%x.%04x, process=%d, action=%d\n", + sch->schid.ssid, sch->schid.sch_no, process, + action); + /* Perform immediate actions while holding the lock. */ cdev = sch_get_cdev(sch); - disc = device_is_disconnected(cdev); - if (disc && slow) { - /* Disconnected devices are evaluated directly only.*/ - spin_unlock_irqrestore(sch->lock, flags); - return 0; - } - /* No interrupt after machine check - kill pending timers. */ - if (cdev) - ccw_device_set_timeout(cdev, 0); - if (!disc && !slow) { - /* Non-disconnected devices are evaluated on the slow path. */ - spin_unlock_irqrestore(sch->lock, flags); - return -EAGAIN; + switch (action) { + case IO_SCH_REPROBE: + /* Trigger device recognition. */ + ccw_device_trigger_reprobe(cdev); + rc = 0; + goto out_unlock; + case IO_SCH_VERIFY: + /* Trigger path verification. */ + io_subchannel_verify(sch); + rc = 0; + goto out_unlock; + case IO_SCH_DISC: + ccw_device_set_disconnected(cdev); + rc = 0; + goto out_unlock; + case IO_SCH_ORPH_UNREG: + case IO_SCH_ORPH_ATTACH: + ccw_device_set_disconnected(cdev); + break; + case IO_SCH_UNREG_ATTACH: + case IO_SCH_UNREG: + if (cdev) + ccw_device_set_notoper(cdev); + break; + case IO_SCH_NOP: + rc = 0; + goto out_unlock; + default: + break; } - event = io_subchannel_get_status(sch); - CIO_MSG_EVENT(4, "Evaluating schid 0.%x.%04x, event %d, %s, %s path.\n", - sch->schid.ssid, sch->schid.sch_no, event, - disc ? "disconnected" : "normal", - slow ? "slow" : "fast"); - /* Analyze subchannel status. */ - action = NONE; - switch (event) { - case CIO_NO_PATH: - if (disc) { - /* Check if paths have become available. */ - action = REPROBE; - break; - } - /* fall through */ - case CIO_GONE: - /* Ask driver what to do with device. */ - if (io_subchannel_notify(sch, event)) - action = DISC; - else - action = UNREGISTER; + spin_unlock_irqrestore(sch->lock, flags); + /* All other actions require process context. */ + if (!process) + goto out; + /* Handle attached ccw device. */ + switch (action) { + case IO_SCH_ORPH_UNREG: + case IO_SCH_ORPH_ATTACH: + /* Move ccw device to orphanage. */ + rc = ccw_device_move_to_orph(cdev); + if (rc) + goto out; break; - case CIO_REVALIDATE: - /* Device will be removed, so no notify necessary. */ - if (disc) - /* Reprobe because immediate unregister might block. */ - action = REPROBE; - else - action = UNREGISTER_PROBE; + case IO_SCH_UNREG_ATTACH: + /* Unregister ccw device. */ + ccw_device_unregister(cdev); break; - case CIO_OPER: - if (disc) - /* Get device operational again. */ - action = REPROBE; + default: break; } - /* Perform action. */ - ret = 0; + /* Handle subchannel. */ switch (action) { - case UNREGISTER: - case UNREGISTER_PROBE: - ccw_device_set_notoper(cdev); - /* Unregister device (will use subchannel lock). */ - spin_unlock_irqrestore(sch->lock, flags); + case IO_SCH_ORPH_UNREG: + case IO_SCH_UNREG: css_sch_device_unregister(sch); - spin_lock_irqsave(sch->lock, flags); break; - case REPROBE: + case IO_SCH_ORPH_ATTACH: + case IO_SCH_UNREG_ATTACH: + case IO_SCH_ATTACH: + dev_id.ssid = sch->schid.ssid; + dev_id.devno = sch->schib.pmcw.dev; + cdev = get_ccwdev_by_dev_id(&dev_id); + if (!cdev) { + sch_create_and_recog_new_device(sch); + break; + } + rc = ccw_device_move_to_sch(cdev, sch); + if (rc) { + /* Release reference from get_ccwdev_by_dev_id() */ + put_device(&cdev->dev); + goto out; + } + spin_lock_irqsave(sch->lock, flags); ccw_device_trigger_reprobe(cdev); - break; - case DISC: - ccw_device_set_disconnected(cdev); + spin_unlock_irqrestore(sch->lock, flags); + /* Release reference from get_ccwdev_by_dev_id() */ + put_device(&cdev->dev); break; default: break; } - spin_unlock_irqrestore(sch->lock, flags); - /* Probe if necessary. */ - if (action == UNREGISTER_PROBE) - ret = css_probe_device(sch->schid); + return 0; - return ret; +out_unlock: + spin_unlock_irqrestore(sch->lock, flags); +out: + return rc; } #ifdef CONFIG_CCW_CONSOLE |