diff options
Diffstat (limited to 'drivers/s390/cio/device.c')
-rw-r--r-- | drivers/s390/cio/device.c | 476 |
1 files changed, 360 insertions, 116 deletions
diff --git a/drivers/s390/cio/device.c b/drivers/s390/cio/device.c index 94bdd4d8a4c9..803579053c2f 100644 --- a/drivers/s390/cio/device.c +++ b/drivers/s390/cio/device.c @@ -23,6 +23,7 @@ #include <asm/param.h> /* HZ */ #include "cio.h" +#include "cio_debug.h" #include "css.h" #include "device.h" #include "ioasm.h" @@ -234,9 +235,11 @@ chpids_show (struct device * dev, struct device_attribute *attr, char * buf) ssize_t ret = 0; int chp; - for (chp = 0; chp < 8; chp++) - ret += sprintf (buf+ret, "%02x ", ssd->chpid[chp]); - + if (ssd) + for (chp = 0; chp < 8; chp++) + ret += sprintf (buf+ret, "%02x ", ssd->chpid[chp]); + else + ret += sprintf (buf, "n/a"); ret += sprintf (buf+ret, "\n"); return min((ssize_t)PAGE_SIZE, ret); } @@ -294,14 +297,44 @@ online_show (struct device *dev, struct device_attribute *attr, char *buf) return sprintf(buf, cdev->online ? "1\n" : "0\n"); } +int ccw_device_is_orphan(struct ccw_device *cdev) +{ + return sch_is_pseudo_sch(to_subchannel(cdev->dev.parent)); +} + +static void ccw_device_unregister(struct work_struct *work) +{ + struct ccw_device_private *priv; + struct ccw_device *cdev; + + priv = container_of(work, struct ccw_device_private, kick_work); + cdev = priv->cdev; + if (test_and_clear_bit(1, &cdev->private->registered)) + device_unregister(&cdev->dev); + put_device(&cdev->dev); +} + static void ccw_device_remove_disconnected(struct ccw_device *cdev) { struct subchannel *sch; + unsigned long flags; /* * Forced offline in disconnected state means * 'throw away device'. */ + if (ccw_device_is_orphan(cdev)) { + /* Deregister ccw device. */ + spin_lock_irqsave(cdev->ccwlock, flags); + cdev->private->state = DEV_STATE_NOT_OPER; + spin_unlock_irqrestore(cdev->ccwlock, flags); + if (get_device(&cdev->dev)) { + PREPARE_WORK(&cdev->private->kick_work, + ccw_device_unregister); + queue_work(ccw_device_work, &cdev->private->kick_work); + } + return ; + } sch = to_subchannel(cdev->dev.parent); css_sch_device_unregister(sch); /* Reset intparm to zeroes. */ @@ -462,6 +495,8 @@ available_show (struct device *dev, struct device_attribute *attr, char *buf) struct ccw_device *cdev = to_ccwdev(dev); struct subchannel *sch; + if (ccw_device_is_orphan(cdev)) + return sprintf(buf, "no device\n"); switch (cdev->private->state) { case DEV_STATE_BOXED: return sprintf(buf, "boxed\n"); @@ -498,11 +533,10 @@ static struct attribute_group subch_attr_group = { .attrs = subch_attrs, }; -static inline int -subchannel_add_files (struct device *dev) -{ - return sysfs_create_group(&dev->kobj, &subch_attr_group); -} +struct attribute_group *subch_attr_groups[] = { + &subch_attr_group, + NULL, +}; static struct attribute * ccwdev_attrs[] = { &dev_attr_devtype.attr, @@ -532,8 +566,7 @@ device_remove_files(struct device *dev) /* this is a simple abstraction for device_register that sets the * correct bus type and adds the bus specific files */ -int -ccw_device_register(struct ccw_device *cdev) +static int ccw_device_register(struct ccw_device *cdev) { struct device *dev = &cdev->dev; int ret; @@ -564,11 +597,10 @@ match_devno(struct device * dev, void * data) 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)) { - cdev->private->state = DEV_STATE_NOT_OPER; + (cdev != d->sibling)) return 1; - } return 0; } @@ -585,13 +617,36 @@ static struct ccw_device * get_disc_ccwdev_by_dev_id(struct ccw_dev_id *dev_id, return dev ? to_ccwdev(dev) : NULL; } -static void -ccw_device_add_changed(void *data) +static int match_orphan(struct device *dev, void *data) +{ + struct ccw_dev_id *dev_id; + struct ccw_device *cdev; + + 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) { + struct device *dev; + + dev = device_find_child(&css->pseudo_subchannel->dev, dev_id, + match_orphan); + + return dev ? to_ccwdev(dev) : NULL; +} +static void +ccw_device_add_changed(struct work_struct *work) +{ + struct ccw_device_private *priv; struct ccw_device *cdev; - cdev = data; + priv = container_of(work, struct ccw_device_private, kick_work); + cdev = priv->cdev; if (device_add(&cdev->dev)) { put_device(&cdev->dev); return; @@ -603,64 +658,21 @@ ccw_device_add_changed(void *data) } } -extern int css_get_ssd_info(struct subchannel *sch); - -void -ccw_device_do_unreg_rereg(void *data) +void ccw_device_do_unreg_rereg(struct work_struct *work) { + struct ccw_device_private *priv; struct ccw_device *cdev; struct subchannel *sch; - int need_rename; - cdev = data; + priv = container_of(work, struct ccw_device_private, kick_work); + cdev = priv->cdev; sch = to_subchannel(cdev->dev.parent); - if (cdev->private->dev_id.devno != sch->schib.pmcw.dev) { - /* - * The device number has changed. This is usually only when - * a device has been detached under VM and then re-appeared - * on another subchannel because of a different attachment - * order than before. Ideally, we should should just switch - * subchannels, but unfortunately, this is not possible with - * the current implementation. - * Instead, we search for the old subchannel for this device - * number and deregister so there are no collisions with the - * newly registered ccw_device. - * FIXME: Find another solution so the block layer doesn't - * get possibly sick... - */ - struct ccw_device *other_cdev; - struct ccw_dev_id dev_id; - - need_rename = 1; - dev_id.devno = sch->schib.pmcw.dev; - dev_id.ssid = sch->schid.ssid; - other_cdev = get_disc_ccwdev_by_dev_id(&dev_id, cdev); - if (other_cdev) { - struct subchannel *other_sch; - - other_sch = to_subchannel(other_cdev->dev.parent); - if (get_device(&other_sch->dev)) { - stsch(other_sch->schid, &other_sch->schib); - if (other_sch->schib.pmcw.dnv) { - other_sch->schib.pmcw.intparm = 0; - cio_modify(other_sch); - } - css_sch_device_unregister(other_sch); - } - } - /* Update ssd info here. */ - css_get_ssd_info(sch); - cdev->private->dev_id.devno = sch->schib.pmcw.dev; - } else - need_rename = 0; + device_remove_files(&cdev->dev); if (test_and_clear_bit(1, &cdev->private->registered)) device_del(&cdev->dev); - if (need_rename) - snprintf (cdev->dev.bus_id, BUS_ID_SIZE, "0.%x.%04x", - sch->schid.ssid, sch->schib.pmcw.dev); PREPARE_WORK(&cdev->private->kick_work, - ccw_device_add_changed, cdev); + ccw_device_add_changed); queue_work(ccw_device_work, &cdev->private->kick_work); } @@ -674,22 +686,210 @@ ccw_device_release(struct device *dev) kfree(cdev); } +static struct ccw_device * io_subchannel_allocate_dev(struct subchannel *sch) +{ + struct ccw_device *cdev; + + cdev = kzalloc(sizeof(*cdev), GFP_KERNEL); + if (cdev) { + cdev->private = kzalloc(sizeof(struct ccw_device_private), + GFP_KERNEL | GFP_DMA); + if (cdev->private) + return cdev; + } + kfree(cdev); + return ERR_PTR(-ENOMEM); +} + +static int io_subchannel_initialize_dev(struct subchannel *sch, + struct ccw_device *cdev) +{ + cdev->private->cdev = cdev; + atomic_set(&cdev->private->onoff, 0); + cdev->dev.parent = &sch->dev; + cdev->dev.release = ccw_device_release; + INIT_LIST_HEAD(&cdev->private->kick_work.entry); + /* Do first half of device_register. */ + device_initialize(&cdev->dev); + if (!get_device(&sch->dev)) { + if (cdev->dev.release) + cdev->dev.release(&cdev->dev); + return -ENODEV; + } + return 0; +} + +static struct ccw_device * io_subchannel_create_ccwdev(struct subchannel *sch) +{ + struct ccw_device *cdev; + int ret; + + cdev = io_subchannel_allocate_dev(sch); + if (!IS_ERR(cdev)) { + ret = io_subchannel_initialize_dev(sch, cdev); + if (ret) { + kfree(cdev); + cdev = ERR_PTR(ret); + } + } + return cdev; +} + +static int io_subchannel_recog(struct ccw_device *, struct subchannel *); + +static void sch_attach_device(struct subchannel *sch, + struct ccw_device *cdev) +{ + spin_lock_irq(sch->lock); + sch->dev.driver_data = cdev; + cdev->private->schid = sch->schid; + cdev->ccwlock = sch->lock; + device_trigger_reprobe(sch); + spin_unlock_irq(sch->lock); +} + +static void sch_attach_disconnected_device(struct subchannel *sch, + struct ccw_device *cdev) +{ + struct subchannel *other_sch; + int ret; + + other_sch = to_subchannel(get_device(cdev->dev.parent)); + ret = device_move(&cdev->dev, &sch->dev); + if (ret) { + CIO_MSG_EVENT(2, "Moving disconnected device 0.%x.%04x failed " + "(ret=%d)!\n", cdev->private->dev_id.ssid, + cdev->private->dev_id.devno, ret); + put_device(&other_sch->dev); + return; + } + other_sch->dev.driver_data = NULL; + /* No need to keep a subchannel without ccw device around. */ + css_sch_device_unregister(other_sch); + put_device(&other_sch->dev); + sch_attach_device(sch, cdev); +} + +static void sch_attach_orphaned_device(struct subchannel *sch, + struct ccw_device *cdev) +{ + int ret; + + /* Try to move the ccw device to its new subchannel. */ + ret = device_move(&cdev->dev, &sch->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); + return; + } + sch_attach_device(sch, cdev); +} + +static void sch_create_and_recog_new_device(struct subchannel *sch) +{ + struct ccw_device *cdev; + + /* Need to allocate a new ccw device. */ + cdev = io_subchannel_create_ccwdev(sch); + if (IS_ERR(cdev)) { + /* OK, we did everything we could... */ + css_sch_device_unregister(sch); + return; + } + spin_lock_irq(sch->lock); + sch->dev.driver_data = cdev; + spin_unlock_irq(sch->lock); + /* Start recognition for the new ccw device. */ + if (io_subchannel_recog(cdev, sch)) { + spin_lock_irq(sch->lock); + sch->dev.driver_data = NULL; + spin_unlock_irq(sch->lock); + if (cdev->dev.release) + cdev->dev.release(&cdev->dev); + css_sch_device_unregister(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; + + /* + * Move the orphaned ccw device to the orphanage so the replacing + * ccw device can take its place on the subchannel. + */ + ret = device_move(&cdev->dev, &css->pseudo_subchannel->dev); + 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); + 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); + return; + } + replacing_cdev = get_orphaned_ccwdev_by_dev_id(css, &dev_id); + if (replacing_cdev) { + sch_attach_orphaned_device(sch, replacing_cdev); + return; + } + sch_create_and_recog_new_device(sch); +} + /* * Register recognized device. */ static void -io_subchannel_register(void *data) +io_subchannel_register(struct work_struct *work) { + struct ccw_device_private *priv; struct ccw_device *cdev; struct subchannel *sch; int ret; unsigned long flags; - cdev = data; + priv = container_of(work, struct ccw_device_private, kick_work); + cdev = priv->cdev; sch = to_subchannel(cdev->dev.parent); + /* + * io_subchannel_register() will also be called after device + * recognition has been done for a boxed device (which will already + * be registered). We need to reprobe since we may now have sense id + * information. + */ if (klist_node_attached(&cdev->dev.knode_parent)) { - bus_rescan_devices(&ccw_bus_type); + if (!cdev->drv) { + ret = device_reprobe(&cdev->dev); + if (ret) + /* We can't do much here. */ + dev_info(&cdev->dev, "device_reprobe() returned" + " %d\n", ret); + } goto out; } /* make it known to the system */ @@ -698,9 +898,9 @@ io_subchannel_register(void *data) printk (KERN_WARNING "%s: could not register %s\n", __func__, cdev->dev.bus_id); put_device(&cdev->dev); - spin_lock_irqsave(&sch->lock, flags); + spin_lock_irqsave(sch->lock, flags); sch->dev.driver_data = NULL; - spin_unlock_irqrestore(&sch->lock, flags); + spin_unlock_irqrestore(sch->lock, flags); kfree (cdev->private); kfree (cdev); put_device(&sch->dev); @@ -708,11 +908,6 @@ io_subchannel_register(void *data) wake_up(&ccw_device_init_wq); return; } - - ret = subchannel_add_files(cdev->dev.parent); - if (ret) - printk(KERN_WARNING "%s: could not add attributes to %s\n", - __func__, sch->dev.bus_id); put_device(&cdev->dev); out: cdev->private->flags.recog_done = 1; @@ -723,11 +918,14 @@ out: } void -ccw_device_call_sch_unregister(void *data) +ccw_device_call_sch_unregister(struct work_struct *work) { - struct ccw_device *cdev = data; + struct ccw_device_private *priv; + struct ccw_device *cdev; struct subchannel *sch; + priv = container_of(work, struct ccw_device_private, kick_work); + cdev = priv->cdev; sch = to_subchannel(cdev->dev.parent); css_sch_device_unregister(sch); /* Reset intparm to zeroes. */ @@ -757,7 +955,7 @@ io_subchannel_recog_done(struct ccw_device *cdev) break; sch = to_subchannel(cdev->dev.parent); PREPARE_WORK(&cdev->private->kick_work, - ccw_device_call_sch_unregister, cdev); + ccw_device_call_sch_unregister); queue_work(slow_path_wq, &cdev->private->kick_work); if (atomic_dec_and_test(&ccw_device_init_count)) wake_up(&ccw_device_init_wq); @@ -772,7 +970,7 @@ io_subchannel_recog_done(struct ccw_device *cdev) if (!get_device(&cdev->dev)) break; PREPARE_WORK(&cdev->private->kick_work, - io_subchannel_register, cdev); + io_subchannel_register); queue_work(slow_path_wq, &cdev->private->kick_work); break; } @@ -786,7 +984,7 @@ io_subchannel_recog(struct ccw_device *cdev, struct subchannel *sch) sch->dev.driver_data = cdev; sch->driver = &io_subchannel_driver; - cdev->ccwlock = &sch->lock; + cdev->ccwlock = sch->lock; /* Init private data. */ priv = cdev->private; @@ -806,9 +1004,9 @@ io_subchannel_recog(struct ccw_device *cdev, struct subchannel *sch) atomic_inc(&ccw_device_init_count); /* Start async. device sensing. */ - spin_lock_irq(&sch->lock); + spin_lock_irq(sch->lock); rc = ccw_device_recognition(cdev); - spin_unlock_irq(&sch->lock); + spin_unlock_irq(sch->lock); if (rc) { if (atomic_dec_and_test(&ccw_device_init_count)) wake_up(&ccw_device_init_wq); @@ -816,12 +1014,55 @@ io_subchannel_recog(struct ccw_device *cdev, struct subchannel *sch) return rc; } +static void ccw_device_move_to_sch(struct work_struct *work) +{ + struct ccw_device_private *priv; + 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 = ccw_device_is_orphan(cdev) ? + NULL : to_subchannel(get_device(cdev->dev.parent)); + mutex_lock(&sch->reg_mutex); + /* Try to move the ccw device to its new subchannel. */ + rc = device_move(&cdev->dev, &sch->dev); + mutex_unlock(&sch->reg_mutex); + if (rc) { + CIO_MSG_EVENT(2, "Moving device 0.%x.%04x to subchannel " + "0.%x.%04x failed (ret=%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); + goto out; + } + if (former_parent) { + spin_lock_irq(former_parent->lock); + former_parent->dev.driver_data = NULL; + spin_unlock_irq(former_parent->lock); + css_sch_device_unregister(former_parent); + /* Reset intparm to zeroes. */ + former_parent->schib.pmcw.intparm = 0; + cio_modify(former_parent); + } + sch_attach_device(sch, cdev); +out: + if (former_parent) + put_device(&former_parent->dev); + put_device(&cdev->dev); +} + static int io_subchannel_probe (struct subchannel *sch) { struct ccw_device *cdev; int rc; unsigned long flags; + struct ccw_dev_id dev_id; if (sch->dev.driver_data) { /* @@ -832,7 +1073,6 @@ io_subchannel_probe (struct subchannel *sch) cdev = sch->dev.driver_data; device_initialize(&cdev->dev); ccw_device_register(cdev); - subchannel_add_files(&sch->dev); /* * Check if the device is already online. If it is * the reference count needs to be corrected @@ -845,33 +1085,37 @@ io_subchannel_probe (struct subchannel *sch) get_device(&cdev->dev); return 0; } - cdev = kzalloc (sizeof(*cdev), GFP_KERNEL); + /* + * 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) - return -ENOMEM; - cdev->private = kzalloc(sizeof(struct ccw_device_private), - GFP_KERNEL | GFP_DMA); - if (!cdev->private) { - kfree(cdev); - return -ENOMEM; - } - atomic_set(&cdev->private->onoff, 0); - cdev->dev.parent = &sch->dev; - cdev->dev.release = ccw_device_release; - INIT_LIST_HEAD(&cdev->private->kick_work.entry); - /* Do first half of device_register. */ - device_initialize(&cdev->dev); - - if (!get_device(&sch->dev)) { - if (cdev->dev.release) - cdev->dev.release(&cdev->dev); - return -ENODEV; + 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)) + return PTR_ERR(cdev); rc = io_subchannel_recog(cdev, sch); if (rc) { - spin_lock_irqsave(&sch->lock, flags); + spin_lock_irqsave(sch->lock, flags); sch->dev.driver_data = NULL; - spin_unlock_irqrestore(&sch->lock, flags); + spin_unlock_irqrestore(sch->lock, flags); if (cdev->dev.release) cdev->dev.release(&cdev->dev); } @@ -879,17 +1123,6 @@ io_subchannel_probe (struct subchannel *sch) return rc; } -static void -ccw_device_unregister(void *data) -{ - struct ccw_device *cdev; - - cdev = (struct ccw_device *)data; - if (test_and_clear_bit(1, &cdev->private->registered)) - device_unregister(&cdev->dev); - put_device(&cdev->dev); -} - static int io_subchannel_remove (struct subchannel *sch) { @@ -910,7 +1143,7 @@ io_subchannel_remove (struct subchannel *sch) */ if (get_device(&cdev->dev)) { PREPARE_WORK(&cdev->private->kick_work, - ccw_device_unregister, cdev); + ccw_device_unregister); queue_work(ccw_device_work, &cdev->private->kick_work); } return 0; @@ -949,6 +1182,9 @@ io_subchannel_ioterm(struct device *dev) cdev = dev->driver_data; if (!cdev) return; + /* Internal I/O will be retried by the interrupt handler. */ + if (cdev->private->flags.intretry) + return; cdev->private->state = DEV_STATE_CLEAR_VERIFY; if (cdev->handler) cdev->handler(cdev, cdev->private->intparm, @@ -989,6 +1225,13 @@ static struct ccw_device console_cdev; static struct ccw_device_private console_private; static int console_cdev_in_use; +static DEFINE_SPINLOCK(ccw_console_lock); + +spinlock_t * cio_get_console_lock(void) +{ + return &ccw_console_lock; +} + static int ccw_device_console_enable (struct ccw_device *cdev, struct subchannel *sch) { @@ -1034,6 +1277,7 @@ ccw_device_probe_console(void) memset(&console_cdev, 0, sizeof(struct ccw_device)); memset(&console_private, 0, sizeof(struct ccw_device_private)); console_cdev.private = &console_private; + console_private.cdev = &console_cdev; ret = ccw_device_console_enable(&console_cdev, sch); if (ret) { cio_release_console(); |