X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=drivers%2Fs390%2Fcio%2Fdevice.c;h=7b6f85b3e42cbf7388dd3469ea8ba9028f262497;hb=6a77f38946aaee1cd85eeec6cf4229b204c15071;hp=31d561bea1c94b3bf4e83a1874c00f09dd307522;hpb=5273a3df6485dc2ad6aa7ddd441b9a21970f003b;p=linux-2.6.git diff --git a/drivers/s390/cio/device.c b/drivers/s390/cio/device.c index 31d561bea..7b6f85b3e 100644 --- a/drivers/s390/cio/device.c +++ b/drivers/s390/cio/device.c @@ -1,7 +1,7 @@ /* * drivers/s390/cio/device.c * bus driver for ccw devices - * $Revision: 1.115 $ + * $Revision: 1.129 $ * * Copyright (C) 2002 IBM Deutschland Entwicklung GmbH, * IBM Corporation @@ -26,6 +26,7 @@ #include "cio.h" #include "css.h" #include "device.h" +#include "ioasm.h" /******************* bus type handling ***********************/ @@ -67,9 +68,6 @@ ccw_hotplug (struct device *dev, char **envp, int num_envp, if (!cdev) return -ENODEV; - if (cdev->private->state == DEV_STATE_NOT_OPER) - return -ENODEV; - /* what we want to pass to /sbin/hotplug */ envp[i++] = buffer; @@ -158,6 +156,11 @@ init_ccw_bus_type (void) ret = -ENOMEM; /* FIXME: better errno ? */ goto out_err; } + slow_path_wq = create_singlethread_workqueue("kslowcrw"); + if (!slow_path_wq) { + ret = -ENOMEM; /* FIXME: better errno ? */ + goto out_err; + } if ((ret = bus_register (&ccw_bus_type))) goto out_err; @@ -173,6 +176,8 @@ out_err: destroy_workqueue(ccw_device_work); if (ccw_device_notify_work) destroy_workqueue(ccw_device_notify_work); + if (slow_path_wq) + destroy_workqueue(slow_path_wq); return ret; } @@ -493,26 +498,121 @@ ccw_device_register(struct ccw_device *cdev) if ((ret = device_add(dev))) return ret; - if ((ret = device_add_files(dev))) - device_del(dev); - + set_bit(1, &cdev->private->registered); + if ((ret = device_add_files(dev))) { + if (test_and_clear_bit(1, &cdev->private->registered)) + device_del(dev); + } return ret; } -void -ccw_device_do_unreg_rereg(void *data) +static struct ccw_device * +get_disc_ccwdev_by_devno(unsigned int devno, struct ccw_device *sibling) { + struct ccw_device *cdev; + struct list_head *entry; struct device *dev; - dev = (struct device *)data; - device_remove_files(dev); - device_del(dev); - if (device_add(dev)) { + if (!get_bus(&ccw_bus_type)) + return NULL; + down_read(&ccw_bus_type.subsys.rwsem); + cdev = NULL; + list_for_each(entry, &ccw_bus_type.devices.list) { + dev = get_device(container_of(entry, + struct device, bus_list)); + if (!dev) + continue; + cdev = to_ccwdev(dev); + if ((cdev->private->state == DEV_STATE_DISCONNECTED) && + (cdev->private->devno == devno) && + (cdev != sibling)) { + cdev->private->state = DEV_STATE_NOT_OPER; + break; + } put_device(dev); + cdev = NULL; + } + up_read(&ccw_bus_type.subsys.rwsem); + put_bus(&ccw_bus_type); + + return cdev; +} + +static void +ccw_device_add_changed(void *data) +{ + + struct ccw_device *cdev; + + cdev = (struct ccw_device *)data; + if (device_add(&cdev->dev)) { + put_device(&cdev->dev); return; } - if (device_add_files(dev)) - device_unregister(dev); + set_bit(1, &cdev->private->registered); + if (device_add_files(&cdev->dev)) { + if (test_and_clear_bit(1, &cdev->private->registered)) + device_unregister(&cdev->dev); + } +} + +extern int css_get_ssd_info(struct subchannel *sch); + +void +ccw_device_do_unreg_rereg(void *data) +{ + struct ccw_device *cdev; + struct subchannel *sch; + int need_rename; + + cdev = (struct ccw_device *)data; + sch = to_subchannel(cdev->dev.parent); + if (cdev->private->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; + + need_rename = 1; + other_cdev = get_disc_ccwdev_by_devno(sch->schib.pmcw.dev, + 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->irq, &other_sch->schib); + if (other_sch->schib.pmcw.dnv) { + other_sch->schib.pmcw.intparm = 0; + cio_modify(other_sch); + } + device_unregister(&other_sch->dev); + } + } + /* Update ssd info here. */ + css_get_ssd_info(sch); + cdev->private->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.0.%04x", + sch->schib.pmcw.dev); + PREPARE_WORK(&cdev->private->kick_work, + ccw_device_add_changed, (void *)cdev); + queue_work(ccw_device_work, &cdev->private->kick_work); } static void @@ -534,6 +634,7 @@ io_subchannel_register(void *data) struct ccw_device *cdev; struct subchannel *sch; int ret; + unsigned long flags; cdev = (struct ccw_device *) data; sch = to_subchannel(cdev->dev.parent); @@ -548,10 +649,15 @@ io_subchannel_register(void *data) printk (KERN_WARNING "%s: could not register %s\n", __func__, cdev->dev.bus_id); put_device(&cdev->dev); - sch->dev.driver_data = 0; + spin_lock_irqsave(&sch->lock, flags); + sch->dev.driver_data = NULL; + spin_unlock_irqrestore(&sch->lock, flags); kfree (cdev->private); kfree (cdev); - goto out; + put_device(&sch->dev); + if (atomic_dec_and_test(&ccw_device_init_count)) + wake_up(&ccw_device_init_wq); + return; } ret = subchannel_add_files(cdev->dev.parent); @@ -563,6 +669,8 @@ out: cdev->private->flags.recog_done = 1; put_device(&sch->dev); wake_up(&cdev->private->wait_q); + if (atomic_dec_and_test(&ccw_device_init_count)) + wake_up(&ccw_device_init_wq); } void @@ -572,9 +680,7 @@ ccw_device_call_sch_unregister(void *data) struct subchannel *sch; sch = to_subchannel(cdev->dev.parent); - /* Check if device is registered. */ - if (!list_empty(&sch->dev.node)) - device_unregister(&sch->dev); + device_unregister(&sch->dev); /* Reset intparm to zeroes. */ sch->schib.pmcw.intparm = 0; cio_modify(sch); @@ -601,9 +707,11 @@ io_subchannel_recog_done(struct ccw_device *cdev) if (!get_device(&cdev->dev)) break; sch = to_subchannel(cdev->dev.parent); - INIT_WORK(&cdev->private->kick_work, - ccw_device_call_sch_unregister, (void *) cdev); - queue_work(ccw_device_work, &cdev->private->kick_work); + PREPARE_WORK(&cdev->private->kick_work, + ccw_device_call_sch_unregister, (void *) cdev); + queue_work(slow_path_wq, &cdev->private->kick_work); + if (atomic_dec_and_test(&ccw_device_init_count)) + wake_up(&ccw_device_init_wq); break; case DEV_STATE_BOXED: /* Device did not respond in time. */ @@ -614,31 +722,30 @@ io_subchannel_recog_done(struct ccw_device *cdev) */ if (!get_device(&cdev->dev)) break; - INIT_WORK(&cdev->private->kick_work, - io_subchannel_register, (void *) cdev); - queue_work(ccw_device_work, &cdev->private->kick_work); + PREPARE_WORK(&cdev->private->kick_work, + io_subchannel_register, (void *) cdev); + queue_work(slow_path_wq, &cdev->private->kick_work); break; } - if (atomic_dec_and_test(&ccw_device_init_count)) - wake_up(&ccw_device_init_wq); } static int io_subchannel_recog(struct ccw_device *cdev, struct subchannel *sch) { int rc; + struct ccw_device_private *priv; sch->dev.driver_data = cdev; sch->driver = &io_subchannel_driver; cdev->ccwlock = &sch->lock; - *cdev->private = (struct ccw_device_private) { - .devno = sch->schib.pmcw.dev, - .irq = sch->irq, - .state = DEV_STATE_NOT_OPER, - .cmb_list = LIST_HEAD_INIT(cdev->private->cmb_list), - }; - init_waitqueue_head(&cdev->private->wait_q); - init_timer(&cdev->private->timer); + /* Init private data. */ + priv = cdev->private; + priv->devno = sch->schib.pmcw.dev; + priv->irq = sch->irq; + priv->state = DEV_STATE_NOT_OPER; + INIT_LIST_HEAD(&priv->cmb_list); + init_waitqueue_head(&priv->wait_q); + init_timer(&priv->timer); /* Set an initial name for the device. */ snprintf (cdev->dev.bus_id, BUS_ID_SIZE, "0.0.%04x", @@ -664,6 +771,7 @@ io_subchannel_probe (struct device *pdev) struct subchannel *sch; struct ccw_device *cdev; int rc; + unsigned long flags; sch = to_subchannel(pdev); if (sch->dev.driver_data) { @@ -704,6 +812,7 @@ io_subchannel_probe (struct device *pdev) .parent = pdev, .release = ccw_device_release, }; + INIT_LIST_HEAD(&cdev->private->kick_work.entry); /* Do first half of device_register. */ device_initialize(&cdev->dev); @@ -715,7 +824,9 @@ io_subchannel_probe (struct device *pdev) rc = io_subchannel_recog(cdev, to_subchannel(pdev)); if (rc) { - sch->dev.driver_data = 0; + spin_lock_irqsave(&sch->lock, flags); + sch->dev.driver_data = NULL; + spin_unlock_irqrestore(&sch->lock, flags); if (cdev->dev.release) cdev->dev.release(&cdev->dev); } @@ -723,24 +834,40 @@ io_subchannel_probe (struct device *pdev) 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 device *dev) { struct ccw_device *cdev; + unsigned long flags; if (!dev->driver_data) return 0; cdev = dev->driver_data; /* Set ccw device to not operational and drop reference. */ + spin_lock_irqsave(cdev->ccwlock, flags); + dev->driver_data = NULL; cdev->private->state = DEV_STATE_NOT_OPER; + spin_unlock_irqrestore(cdev->ccwlock, flags); /* - * Careful here. Our ccw device might be yet unregistered when - * de-registering its subchannel (machine check during device - * recognition). Better look if the subchannel has children. + * Put unregistration on workqueue to avoid livelocks on the css bus + * semaphore. */ - if (!list_empty(&dev->children)) - device_unregister(&cdev->dev); - dev->driver_data = NULL; + if (get_device(&cdev->dev)) { + PREPARE_WORK(&cdev->private->kick_work, + ccw_device_unregister, (void *) cdev); + queue_work(ccw_device_work, &cdev->private->kick_work); + } return 0; }