/*
* 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
#include "cio.h"
#include "css.h"
#include "device.h"
+#include "ioasm.h"
/******************* bus type handling ***********************/
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;
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;
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;
}
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
struct ccw_device *cdev;
struct subchannel *sch;
int ret;
+ unsigned long flags;
cdev = (struct ccw_device *) data;
sch = to_subchannel(cdev->dev.parent);
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);
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
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);
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. */
*/
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",
struct subchannel *sch;
struct ccw_device *cdev;
int rc;
+ unsigned long flags;
sch = to_subchannel(pdev);
if (sch->dev.driver_data) {
.parent = pdev,
.release = ccw_device_release,
};
+ INIT_LIST_HEAD(&cdev->private->kick_work.entry);
/* Do first half of device_register. */
device_initialize(&cdev->dev);
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);
}
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;
}