aboutsummaryrefslogtreecommitdiff
path: root/drivers/s390
diff options
context:
space:
mode:
authorCornelia Huck2008-12-25 13:39:07 +0100
committerMartin Schwidefsky2008-12-25 13:39:08 +0100
commit6eff208f479d6fe99fd92c0e6bf7e930bb45cd30 (patch)
tree9dd40942bd0083873471f91e21d07b865a228414 /drivers/s390
parent9cd67421977a701272820987ff9e6f197b1b97b7 (diff)
[S390] cio: Fix I/O subchannel refcounting.
Subchannel refcounting was incorrect in some places, especially a refcount was missing when ccw_device_call_sch_unregister() was called and the refcount was not correctly switched after moving devices. Fix this by establishing the following rules: - The ccw_device obtains a reference on its parent subchannel when dev.parent is set and gives it up in its release function. This is needed because we need a parent reference for correct refcounting even before the ccw device is (if at all) registered. - When calling device_move(), obtain a reference on the new subchannel before moving the ccw device and give up the reference on the old parent after moving. This brings the refcount in line with the first rule. Signed-off-by: Cornelia Huck <cornelia.huck@de.ibm.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Diffstat (limited to 'drivers/s390')
-rw-r--r--drivers/s390/cio/device.c75
1 files changed, 56 insertions, 19 deletions
diff --git a/drivers/s390/cio/device.c b/drivers/s390/cio/device.c
index 039ef03cf217..cba33aa1df79 100644
--- a/drivers/s390/cio/device.c
+++ b/drivers/s390/cio/device.c
@@ -718,6 +718,8 @@ ccw_device_release(struct device *dev)
struct ccw_device *cdev;
cdev = to_ccwdev(dev);
+ /* Release reference of parent subchannel. */
+ put_device(cdev->dev.parent);
kfree(cdev->private);
kfree(cdev);
}
@@ -792,37 +794,55 @@ static void sch_attach_disconnected_device(struct subchannel *sch,
struct subchannel *other_sch;
int ret;
- other_sch = to_subchannel(get_device(cdev->dev.parent));
+ /* 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);
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_device(&other_sch->dev);
+ /* 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);
- put_device(&other_sch->dev);
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;
- /* Try to move the ccw device to its new subchannel. */
+ /* 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);
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)
@@ -844,9 +864,11 @@ static void sch_create_and_recog_new_device(struct subchannel *sch)
spin_lock_irq(sch->lock);
sch_set_cdev(sch, NULL);
spin_unlock_irq(sch->lock);
- if (cdev->dev.release)
- cdev->dev.release(&cdev->dev);
css_sch_device_unregister(sch);
+ /* Put reference from io_subchannel_create_ccwdev(). */
+ put_device(&sch->dev);
+ /* Give up initial reference. */
+ put_device(&cdev->dev);
}
}
@@ -868,15 +890,20 @@ void ccw_device_move_to_orphanage(struct work_struct *work)
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);
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;
@@ -890,6 +917,8 @@ void ccw_device_move_to_orphanage(struct work_struct *work)
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);
@@ -897,9 +926,13 @@ void ccw_device_move_to_orphanage(struct work_struct *work)
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);
}
/*
@@ -948,13 +981,13 @@ io_subchannel_register(struct work_struct *work)
CIO_MSG_EVENT(0, "Could not register ccw dev 0.%x.%04x: %d\n",
cdev->private->dev_id.ssid,
cdev->private->dev_id.devno, ret);
- put_device(&cdev->dev);
spin_lock_irqsave(sch->lock, flags);
sch_set_cdev(sch, NULL);
spin_unlock_irqrestore(sch->lock, flags);
- kfree (cdev->private);
- kfree (cdev);
- put_device(&sch->dev);
+ /* Release reference for workqueue processing. */
+ put_device(&cdev->dev);
+ /* Release initial device reference. */
+ put_device(&cdev->dev);
if (atomic_dec_and_test(&ccw_device_init_count))
wake_up(&ccw_device_init_wq);
return;
@@ -962,7 +995,6 @@ io_subchannel_register(struct work_struct *work)
put_device(&cdev->dev);
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);
@@ -1012,8 +1044,6 @@ io_subchannel_recog_done(struct ccw_device *cdev)
PREPARE_WORK(&cdev->private->kick_work,
ccw_device_call_sch_unregister);
queue_work(slow_path_wq, &cdev->private->kick_work);
- /* Release subchannel reference for asynchronous recognition. */
- put_device(&sch->dev);
if (atomic_dec_and_test(&ccw_device_init_count))
wake_up(&ccw_device_init_wq);
break;
@@ -1084,10 +1114,15 @@ static void ccw_device_move_to_sch(struct work_struct *work)
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));
+ former_parent = to_subchannel(cdev->dev.parent);
+ /* Get reference for new parent. */
+ if (!get_device(&sch->dev))
+ return;
mutex_lock(&sch->reg_mutex);
- /* Try to move the ccw device to its new subchannel. */
+ /*
+ * Try to move the ccw device to its new subchannel.
+ * Note: device_move() changes cdev->dev.parent
+ */
rc = device_move(&cdev->dev, &sch->dev);
mutex_unlock(&sch->reg_mutex);
if (rc) {
@@ -1097,9 +1132,11 @@ static void ccw_device_move_to_sch(struct work_struct *work)
cdev->private->dev_id.devno, sch->schid.ssid,
sch->schid.sch_no, rc);
css_sch_device_unregister(sch);
+ /* Put reference for new parent again. */
+ put_device(&sch->dev);
goto out;
}
- if (former_parent) {
+ 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);
@@ -1110,8 +1147,8 @@ static void ccw_device_move_to_sch(struct work_struct *work)
}
sch_attach_device(sch, cdev);
out:
- if (former_parent)
- put_device(&former_parent->dev);
+ /* Put reference for old parent. */
+ put_device(&former_parent->dev);
put_device(&cdev->dev);
}