diff options
-rw-r--r-- | drivers/s390/cio/device.c | 75 |
1 files changed, 56 insertions, 19 deletions
diff --git a/drivers/s390/cio/device.c b/drivers/s390/cio/device.c index 039ef03cf21..cba33aa1df7 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); } |