Skip to content

Commit

Permalink
[S390] cio: Fix I/O subchannel refcounting.
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Cornelia Huck authored and Martin Schwidefsky committed Dec 25, 2008
1 parent 9cd6742 commit 6eff208
Showing 1 changed file with 56 additions and 19 deletions.
75 changes: 56 additions & 19 deletions drivers/s390/cio/device.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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)
Expand All @@ -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);
}
}

Expand All @@ -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;
Expand All @@ -890,16 +917,22 @@ 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);
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);
}

/*
Expand Down Expand Up @@ -948,21 +981,20 @@ 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;
}
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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
Expand All @@ -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);
}

Expand Down

0 comments on commit 6eff208

Please sign in to comment.