|
@@ -376,19 +376,23 @@ int ccw_device_set_offline(struct ccw_device *cdev)
|
|
dev_fsm_event(cdev, DEV_EVENT_NOTOPER);
|
|
dev_fsm_event(cdev, DEV_EVENT_NOTOPER);
|
|
}
|
|
}
|
|
spin_unlock_irq(cdev->ccwlock);
|
|
spin_unlock_irq(cdev->ccwlock);
|
|
|
|
+ /* Give up reference from ccw_device_set_online(). */
|
|
|
|
+ put_device(&cdev->dev);
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|
|
spin_unlock_irq(cdev->ccwlock);
|
|
spin_unlock_irq(cdev->ccwlock);
|
|
- if (ret == 0)
|
|
|
|
|
|
+ if (ret == 0) {
|
|
wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev));
|
|
wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev));
|
|
- else {
|
|
|
|
|
|
+ /* Give up reference from ccw_device_set_online(). */
|
|
|
|
+ put_device(&cdev->dev);
|
|
|
|
+ } else {
|
|
CIO_MSG_EVENT(0, "ccw_device_offline returned %d, "
|
|
CIO_MSG_EVENT(0, "ccw_device_offline returned %d, "
|
|
"device 0.%x.%04x\n",
|
|
"device 0.%x.%04x\n",
|
|
ret, cdev->private->dev_id.ssid,
|
|
ret, cdev->private->dev_id.ssid,
|
|
cdev->private->dev_id.devno);
|
|
cdev->private->dev_id.devno);
|
|
cdev->online = 1;
|
|
cdev->online = 1;
|
|
}
|
|
}
|
|
- return ret;
|
|
|
|
|
|
+ return ret;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -411,6 +415,9 @@ int ccw_device_set_online(struct ccw_device *cdev)
|
|
return -ENODEV;
|
|
return -ENODEV;
|
|
if (cdev->online || !cdev->drv)
|
|
if (cdev->online || !cdev->drv)
|
|
return -EINVAL;
|
|
return -EINVAL;
|
|
|
|
+ /* Hold on to an extra reference while device is online. */
|
|
|
|
+ if (!get_device(&cdev->dev))
|
|
|
|
+ return -ENODEV;
|
|
|
|
|
|
spin_lock_irq(cdev->ccwlock);
|
|
spin_lock_irq(cdev->ccwlock);
|
|
ret = ccw_device_online(cdev);
|
|
ret = ccw_device_online(cdev);
|
|
@@ -422,10 +429,15 @@ int ccw_device_set_online(struct ccw_device *cdev)
|
|
"device 0.%x.%04x\n",
|
|
"device 0.%x.%04x\n",
|
|
ret, cdev->private->dev_id.ssid,
|
|
ret, cdev->private->dev_id.ssid,
|
|
cdev->private->dev_id.devno);
|
|
cdev->private->dev_id.devno);
|
|
|
|
+ /* Give up online reference since onlining failed. */
|
|
|
|
+ put_device(&cdev->dev);
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|
|
- if (cdev->private->state != DEV_STATE_ONLINE)
|
|
|
|
|
|
+ if (cdev->private->state != DEV_STATE_ONLINE) {
|
|
|
|
+ /* Give up online reference since onlining failed. */
|
|
|
|
+ put_device(&cdev->dev);
|
|
return -ENODEV;
|
|
return -ENODEV;
|
|
|
|
+ }
|
|
if (!cdev->drv->set_online || cdev->drv->set_online(cdev) == 0) {
|
|
if (!cdev->drv->set_online || cdev->drv->set_online(cdev) == 0) {
|
|
cdev->online = 1;
|
|
cdev->online = 1;
|
|
return 0;
|
|
return 0;
|
|
@@ -440,6 +452,8 @@ int ccw_device_set_online(struct ccw_device *cdev)
|
|
"device 0.%x.%04x\n",
|
|
"device 0.%x.%04x\n",
|
|
ret, cdev->private->dev_id.ssid,
|
|
ret, cdev->private->dev_id.ssid,
|
|
cdev->private->dev_id.devno);
|
|
cdev->private->dev_id.devno);
|
|
|
|
+ /* Give up online reference since onlining failed. */
|
|
|
|
+ put_device(&cdev->dev);
|
|
return (ret == 0) ? -ENODEV : ret;
|
|
return (ret == 0) ? -ENODEV : ret;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -704,6 +718,8 @@ ccw_device_release(struct device *dev)
|
|
struct ccw_device *cdev;
|
|
struct ccw_device *cdev;
|
|
|
|
|
|
cdev = to_ccwdev(dev);
|
|
cdev = to_ccwdev(dev);
|
|
|
|
+ /* Release reference of parent subchannel. */
|
|
|
|
+ put_device(cdev->dev.parent);
|
|
kfree(cdev->private);
|
|
kfree(cdev->private);
|
|
kfree(cdev);
|
|
kfree(cdev);
|
|
}
|
|
}
|
|
@@ -735,8 +751,8 @@ static int io_subchannel_initialize_dev(struct subchannel *sch,
|
|
/* Do first half of device_register. */
|
|
/* Do first half of device_register. */
|
|
device_initialize(&cdev->dev);
|
|
device_initialize(&cdev->dev);
|
|
if (!get_device(&sch->dev)) {
|
|
if (!get_device(&sch->dev)) {
|
|
- if (cdev->dev.release)
|
|
|
|
- cdev->dev.release(&cdev->dev);
|
|
|
|
|
|
+ /* Release reference from device_initialize(). */
|
|
|
|
+ put_device(&cdev->dev);
|
|
return -ENODEV;
|
|
return -ENODEV;
|
|
}
|
|
}
|
|
return 0;
|
|
return 0;
|
|
@@ -778,37 +794,55 @@ static void sch_attach_disconnected_device(struct subchannel *sch,
|
|
struct subchannel *other_sch;
|
|
struct subchannel *other_sch;
|
|
int ret;
|
|
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);
|
|
ret = device_move(&cdev->dev, &sch->dev);
|
|
if (ret) {
|
|
if (ret) {
|
|
CIO_MSG_EVENT(0, "Moving disconnected device 0.%x.%04x failed "
|
|
CIO_MSG_EVENT(0, "Moving disconnected device 0.%x.%04x failed "
|
|
"(ret=%d)!\n", cdev->private->dev_id.ssid,
|
|
"(ret=%d)!\n", cdev->private->dev_id.ssid,
|
|
cdev->private->dev_id.devno, ret);
|
|
cdev->private->dev_id.devno, ret);
|
|
- put_device(&other_sch->dev);
|
|
|
|
|
|
+ /* Put reference for new parent. */
|
|
|
|
+ put_device(&sch->dev);
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
sch_set_cdev(other_sch, NULL);
|
|
sch_set_cdev(other_sch, NULL);
|
|
/* No need to keep a subchannel without ccw device around. */
|
|
/* No need to keep a subchannel without ccw device around. */
|
|
css_sch_device_unregister(other_sch);
|
|
css_sch_device_unregister(other_sch);
|
|
- put_device(&other_sch->dev);
|
|
|
|
sch_attach_device(sch, cdev);
|
|
sch_attach_device(sch, cdev);
|
|
|
|
+ /* Put reference for old parent. */
|
|
|
|
+ put_device(&other_sch->dev);
|
|
}
|
|
}
|
|
|
|
|
|
static void sch_attach_orphaned_device(struct subchannel *sch,
|
|
static void sch_attach_orphaned_device(struct subchannel *sch,
|
|
struct ccw_device *cdev)
|
|
struct ccw_device *cdev)
|
|
{
|
|
{
|
|
int ret;
|
|
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);
|
|
ret = device_move(&cdev->dev, &sch->dev);
|
|
if (ret) {
|
|
if (ret) {
|
|
CIO_MSG_EVENT(0, "Moving device 0.%x.%04x from orphanage "
|
|
CIO_MSG_EVENT(0, "Moving device 0.%x.%04x from orphanage "
|
|
"failed (ret=%d)!\n",
|
|
"failed (ret=%d)!\n",
|
|
cdev->private->dev_id.ssid,
|
|
cdev->private->dev_id.ssid,
|
|
cdev->private->dev_id.devno, ret);
|
|
cdev->private->dev_id.devno, ret);
|
|
|
|
+ /* Put reference for new parent. */
|
|
|
|
+ put_device(&sch->dev);
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
sch_attach_device(sch, cdev);
|
|
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)
|
|
static void sch_create_and_recog_new_device(struct subchannel *sch)
|
|
@@ -830,9 +864,11 @@ static void sch_create_and_recog_new_device(struct subchannel *sch)
|
|
spin_lock_irq(sch->lock);
|
|
spin_lock_irq(sch->lock);
|
|
sch_set_cdev(sch, NULL);
|
|
sch_set_cdev(sch, NULL);
|
|
spin_unlock_irq(sch->lock);
|
|
spin_unlock_irq(sch->lock);
|
|
- if (cdev->dev.release)
|
|
|
|
- cdev->dev.release(&cdev->dev);
|
|
|
|
css_sch_device_unregister(sch);
|
|
css_sch_device_unregister(sch);
|
|
|
|
+ /* Put reference from io_subchannel_create_ccwdev(). */
|
|
|
|
+ put_device(&sch->dev);
|
|
|
|
+ /* Give up initial reference. */
|
|
|
|
+ put_device(&cdev->dev);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -854,15 +890,20 @@ void ccw_device_move_to_orphanage(struct work_struct *work)
|
|
dev_id.devno = sch->schib.pmcw.dev;
|
|
dev_id.devno = sch->schib.pmcw.dev;
|
|
dev_id.ssid = sch->schid.ssid;
|
|
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
|
|
* Move the orphaned ccw device to the orphanage so the replacing
|
|
* ccw device can take its place on the subchannel.
|
|
* 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);
|
|
ret = device_move(&cdev->dev, &css->pseudo_subchannel->dev);
|
|
if (ret) {
|
|
if (ret) {
|
|
CIO_MSG_EVENT(0, "Moving device 0.%x.%04x to orphanage failed "
|
|
CIO_MSG_EVENT(0, "Moving device 0.%x.%04x to orphanage failed "
|
|
"(ret=%d)!\n", cdev->private->dev_id.ssid,
|
|
"(ret=%d)!\n", cdev->private->dev_id.ssid,
|
|
cdev->private->dev_id.devno, ret);
|
|
cdev->private->dev_id.devno, ret);
|
|
|
|
+ /* Decrease refcount for pseudo subchannel again. */
|
|
|
|
+ put_device(&css->pseudo_subchannel->dev);
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
cdev->ccwlock = css->pseudo_subchannel->lock;
|
|
cdev->ccwlock = css->pseudo_subchannel->lock;
|
|
@@ -875,17 +916,23 @@ void ccw_device_move_to_orphanage(struct work_struct *work)
|
|
if (replacing_cdev) {
|
|
if (replacing_cdev) {
|
|
sch_attach_disconnected_device(sch, replacing_cdev);
|
|
sch_attach_disconnected_device(sch, replacing_cdev);
|
|
/* Release reference from get_disc_ccwdev_by_dev_id() */
|
|
/* Release reference from get_disc_ccwdev_by_dev_id() */
|
|
- put_device(&cdev->dev);
|
|
|
|
|
|
+ put_device(&replacing_cdev->dev);
|
|
|
|
+ /* Release reference of subchannel from old cdev. */
|
|
|
|
+ put_device(&sch->dev);
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
replacing_cdev = get_orphaned_ccwdev_by_dev_id(css, &dev_id);
|
|
replacing_cdev = get_orphaned_ccwdev_by_dev_id(css, &dev_id);
|
|
if (replacing_cdev) {
|
|
if (replacing_cdev) {
|
|
sch_attach_orphaned_device(sch, replacing_cdev);
|
|
sch_attach_orphaned_device(sch, replacing_cdev);
|
|
/* Release reference from get_orphaned_ccwdev_by_dev_id() */
|
|
/* Release reference from get_orphaned_ccwdev_by_dev_id() */
|
|
- put_device(&cdev->dev);
|
|
|
|
|
|
+ put_device(&replacing_cdev->dev);
|
|
|
|
+ /* Release reference of subchannel from old cdev. */
|
|
|
|
+ put_device(&sch->dev);
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
sch_create_and_recog_new_device(sch);
|
|
sch_create_and_recog_new_device(sch);
|
|
|
|
+ /* Release reference of subchannel from old cdev. */
|
|
|
|
+ put_device(&sch->dev);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
/*
|
|
@@ -903,6 +950,14 @@ io_subchannel_register(struct work_struct *work)
|
|
priv = container_of(work, struct ccw_device_private, kick_work);
|
|
priv = container_of(work, struct ccw_device_private, kick_work);
|
|
cdev = priv->cdev;
|
|
cdev = priv->cdev;
|
|
sch = to_subchannel(cdev->dev.parent);
|
|
sch = to_subchannel(cdev->dev.parent);
|
|
|
|
+ /*
|
|
|
|
+ * Check if subchannel is still registered. It may have become
|
|
|
|
+ * unregistered if a machine check hit us after finishing
|
|
|
|
+ * device recognition but before the register work could be
|
|
|
|
+ * queued.
|
|
|
|
+ */
|
|
|
|
+ if (!device_is_registered(&sch->dev))
|
|
|
|
+ goto out_err;
|
|
css_update_ssd_info(sch);
|
|
css_update_ssd_info(sch);
|
|
/*
|
|
/*
|
|
* io_subchannel_register() will also be called after device
|
|
* io_subchannel_register() will also be called after device
|
|
@@ -910,7 +965,7 @@ io_subchannel_register(struct work_struct *work)
|
|
* be registered). We need to reprobe since we may now have sense id
|
|
* be registered). We need to reprobe since we may now have sense id
|
|
* information.
|
|
* information.
|
|
*/
|
|
*/
|
|
- if (klist_node_attached(&cdev->dev.knode_parent)) {
|
|
|
|
|
|
+ if (device_is_registered(&cdev->dev)) {
|
|
if (!cdev->drv) {
|
|
if (!cdev->drv) {
|
|
ret = device_reprobe(&cdev->dev);
|
|
ret = device_reprobe(&cdev->dev);
|
|
if (ret)
|
|
if (ret)
|
|
@@ -934,22 +989,19 @@ io_subchannel_register(struct work_struct *work)
|
|
CIO_MSG_EVENT(0, "Could not register ccw dev 0.%x.%04x: %d\n",
|
|
CIO_MSG_EVENT(0, "Could not register ccw dev 0.%x.%04x: %d\n",
|
|
cdev->private->dev_id.ssid,
|
|
cdev->private->dev_id.ssid,
|
|
cdev->private->dev_id.devno, ret);
|
|
cdev->private->dev_id.devno, ret);
|
|
- put_device(&cdev->dev);
|
|
|
|
spin_lock_irqsave(sch->lock, flags);
|
|
spin_lock_irqsave(sch->lock, flags);
|
|
sch_set_cdev(sch, NULL);
|
|
sch_set_cdev(sch, NULL);
|
|
spin_unlock_irqrestore(sch->lock, flags);
|
|
spin_unlock_irqrestore(sch->lock, flags);
|
|
- kfree (cdev->private);
|
|
|
|
- kfree (cdev);
|
|
|
|
- put_device(&sch->dev);
|
|
|
|
- if (atomic_dec_and_test(&ccw_device_init_count))
|
|
|
|
- wake_up(&ccw_device_init_wq);
|
|
|
|
- return;
|
|
|
|
|
|
+ /* Release initial device reference. */
|
|
|
|
+ put_device(&cdev->dev);
|
|
|
|
+ goto out_err;
|
|
}
|
|
}
|
|
- put_device(&cdev->dev);
|
|
|
|
out:
|
|
out:
|
|
cdev->private->flags.recog_done = 1;
|
|
cdev->private->flags.recog_done = 1;
|
|
- put_device(&sch->dev);
|
|
|
|
wake_up(&cdev->private->wait_q);
|
|
wake_up(&cdev->private->wait_q);
|
|
|
|
+out_err:
|
|
|
|
+ /* Release reference for workqueue processing. */
|
|
|
|
+ put_device(&cdev->dev);
|
|
if (atomic_dec_and_test(&ccw_device_init_count))
|
|
if (atomic_dec_and_test(&ccw_device_init_count))
|
|
wake_up(&ccw_device_init_wq);
|
|
wake_up(&ccw_device_init_wq);
|
|
}
|
|
}
|
|
@@ -968,8 +1020,8 @@ static void ccw_device_call_sch_unregister(struct work_struct *work)
|
|
sch = to_subchannel(cdev->dev.parent);
|
|
sch = to_subchannel(cdev->dev.parent);
|
|
css_sch_device_unregister(sch);
|
|
css_sch_device_unregister(sch);
|
|
/* Reset intparm to zeroes. */
|
|
/* Reset intparm to zeroes. */
|
|
- sch->schib.pmcw.intparm = 0;
|
|
|
|
- cio_modify(sch);
|
|
|
|
|
|
+ sch->config.intparm = 0;
|
|
|
|
+ cio_commit_config(sch);
|
|
/* Release cdev reference for workqueue processing.*/
|
|
/* Release cdev reference for workqueue processing.*/
|
|
put_device(&cdev->dev);
|
|
put_device(&cdev->dev);
|
|
/* Release subchannel reference for local processing. */
|
|
/* Release subchannel reference for local processing. */
|
|
@@ -998,8 +1050,6 @@ io_subchannel_recog_done(struct ccw_device *cdev)
|
|
PREPARE_WORK(&cdev->private->kick_work,
|
|
PREPARE_WORK(&cdev->private->kick_work,
|
|
ccw_device_call_sch_unregister);
|
|
ccw_device_call_sch_unregister);
|
|
queue_work(slow_path_wq, &cdev->private->kick_work);
|
|
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))
|
|
if (atomic_dec_and_test(&ccw_device_init_count))
|
|
wake_up(&ccw_device_init_wq);
|
|
wake_up(&ccw_device_init_wq);
|
|
break;
|
|
break;
|
|
@@ -1070,10 +1120,15 @@ static void ccw_device_move_to_sch(struct work_struct *work)
|
|
priv = container_of(work, struct ccw_device_private, kick_work);
|
|
priv = container_of(work, struct ccw_device_private, kick_work);
|
|
sch = priv->sch;
|
|
sch = priv->sch;
|
|
cdev = priv->cdev;
|
|
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);
|
|
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);
|
|
rc = device_move(&cdev->dev, &sch->dev);
|
|
mutex_unlock(&sch->reg_mutex);
|
|
mutex_unlock(&sch->reg_mutex);
|
|
if (rc) {
|
|
if (rc) {
|
|
@@ -1083,21 +1138,23 @@ static void ccw_device_move_to_sch(struct work_struct *work)
|
|
cdev->private->dev_id.devno, sch->schid.ssid,
|
|
cdev->private->dev_id.devno, sch->schid.ssid,
|
|
sch->schid.sch_no, rc);
|
|
sch->schid.sch_no, rc);
|
|
css_sch_device_unregister(sch);
|
|
css_sch_device_unregister(sch);
|
|
|
|
+ /* Put reference for new parent again. */
|
|
|
|
+ put_device(&sch->dev);
|
|
goto out;
|
|
goto out;
|
|
}
|
|
}
|
|
- if (former_parent) {
|
|
|
|
|
|
+ if (!sch_is_pseudo_sch(former_parent)) {
|
|
spin_lock_irq(former_parent->lock);
|
|
spin_lock_irq(former_parent->lock);
|
|
sch_set_cdev(former_parent, NULL);
|
|
sch_set_cdev(former_parent, NULL);
|
|
spin_unlock_irq(former_parent->lock);
|
|
spin_unlock_irq(former_parent->lock);
|
|
css_sch_device_unregister(former_parent);
|
|
css_sch_device_unregister(former_parent);
|
|
/* Reset intparm to zeroes. */
|
|
/* Reset intparm to zeroes. */
|
|
- former_parent->schib.pmcw.intparm = 0;
|
|
|
|
- cio_modify(former_parent);
|
|
|
|
|
|
+ former_parent->config.intparm = 0;
|
|
|
|
+ cio_commit_config(former_parent);
|
|
}
|
|
}
|
|
sch_attach_device(sch, cdev);
|
|
sch_attach_device(sch, cdev);
|
|
out:
|
|
out:
|
|
- if (former_parent)
|
|
|
|
- put_device(&former_parent->dev);
|
|
|
|
|
|
+ /* Put reference for old parent. */
|
|
|
|
+ put_device(&former_parent->dev);
|
|
put_device(&cdev->dev);
|
|
put_device(&cdev->dev);
|
|
}
|
|
}
|
|
|
|
|
|
@@ -1113,6 +1170,15 @@ static void io_subchannel_irq(struct subchannel *sch)
|
|
dev_fsm_event(cdev, DEV_EVENT_INTERRUPT);
|
|
dev_fsm_event(cdev, DEV_EVENT_INTERRUPT);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+void io_subchannel_init_config(struct subchannel *sch)
|
|
|
|
+{
|
|
|
|
+ memset(&sch->config, 0, sizeof(sch->config));
|
|
|
|
+ sch->config.csense = 1;
|
|
|
|
+ /* Use subchannel mp mode when there is more than 1 installed CHPID. */
|
|
|
|
+ if ((sch->schib.pmcw.pim & (sch->schib.pmcw.pim - 1)) != 0)
|
|
|
|
+ sch->config.mp = 1;
|
|
|
|
+}
|
|
|
|
+
|
|
static void io_subchannel_init_fields(struct subchannel *sch)
|
|
static void io_subchannel_init_fields(struct subchannel *sch)
|
|
{
|
|
{
|
|
if (cio_is_console(sch->schid))
|
|
if (cio_is_console(sch->schid))
|
|
@@ -1127,18 +1193,34 @@ static void io_subchannel_init_fields(struct subchannel *sch)
|
|
sch->schib.pmcw.dev, sch->schid.ssid,
|
|
sch->schib.pmcw.dev, sch->schid.ssid,
|
|
sch->schid.sch_no, sch->schib.pmcw.pim,
|
|
sch->schid.sch_no, sch->schib.pmcw.pim,
|
|
sch->schib.pmcw.pam, sch->schib.pmcw.pom);
|
|
sch->schib.pmcw.pam, sch->schib.pmcw.pom);
|
|
- /* Initially set up some fields in the pmcw. */
|
|
|
|
- sch->schib.pmcw.ena = 0;
|
|
|
|
- sch->schib.pmcw.csense = 1; /* concurrent sense */
|
|
|
|
- if ((sch->lpm & (sch->lpm - 1)) != 0)
|
|
|
|
- sch->schib.pmcw.mp = 1; /* multipath mode */
|
|
|
|
- /* clean up possible residual cmf stuff */
|
|
|
|
- sch->schib.pmcw.mme = 0;
|
|
|
|
- sch->schib.pmcw.mbfc = 0;
|
|
|
|
- sch->schib.pmcw.mbi = 0;
|
|
|
|
- sch->schib.mba = 0;
|
|
|
|
|
|
+
|
|
|
|
+ io_subchannel_init_config(sch);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static void io_subchannel_do_unreg(struct work_struct *work)
|
|
|
|
+{
|
|
|
|
+ struct subchannel *sch;
|
|
|
|
+
|
|
|
|
+ sch = container_of(work, struct subchannel, work);
|
|
|
|
+ css_sch_device_unregister(sch);
|
|
|
|
+ /* Reset intparm to zeroes. */
|
|
|
|
+ sch->config.intparm = 0;
|
|
|
|
+ cio_commit_config(sch);
|
|
|
|
+ put_device(&sch->dev);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* Schedule unregister if we have no cdev. */
|
|
|
|
+static void io_subchannel_schedule_removal(struct subchannel *sch)
|
|
|
|
+{
|
|
|
|
+ get_device(&sch->dev);
|
|
|
|
+ INIT_WORK(&sch->work, io_subchannel_do_unreg);
|
|
|
|
+ queue_work(slow_path_wq, &sch->work);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * Note: We always return 0 so that we bind to the device even on error.
|
|
|
|
+ * This is needed so that our remove function is called on unregister.
|
|
|
|
+ */
|
|
static int io_subchannel_probe(struct subchannel *sch)
|
|
static int io_subchannel_probe(struct subchannel *sch)
|
|
{
|
|
{
|
|
struct ccw_device *cdev;
|
|
struct ccw_device *cdev;
|
|
@@ -1168,9 +1250,8 @@ static int io_subchannel_probe(struct subchannel *sch)
|
|
ccw_device_register(cdev);
|
|
ccw_device_register(cdev);
|
|
/*
|
|
/*
|
|
* Check if the device is already online. If it is
|
|
* Check if the device is already online. If it is
|
|
- * the reference count needs to be corrected
|
|
|
|
- * (see ccw_device_online and css_init_done for the
|
|
|
|
- * ugly details).
|
|
|
|
|
|
+ * the reference count needs to be corrected since we
|
|
|
|
+ * didn't obtain a reference in ccw_device_set_online.
|
|
*/
|
|
*/
|
|
if (cdev->private->state != DEV_STATE_NOT_OPER &&
|
|
if (cdev->private->state != DEV_STATE_NOT_OPER &&
|
|
cdev->private->state != DEV_STATE_OFFLINE &&
|
|
cdev->private->state != DEV_STATE_OFFLINE &&
|
|
@@ -1179,23 +1260,24 @@ static int io_subchannel_probe(struct subchannel *sch)
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
io_subchannel_init_fields(sch);
|
|
io_subchannel_init_fields(sch);
|
|
- /*
|
|
|
|
- * First check if a fitting device may be found amongst the
|
|
|
|
- * disconnected devices or in the orphanage.
|
|
|
|
- */
|
|
|
|
- dev_id.devno = sch->schib.pmcw.dev;
|
|
|
|
- dev_id.ssid = sch->schid.ssid;
|
|
|
|
|
|
+ rc = cio_commit_config(sch);
|
|
|
|
+ if (rc)
|
|
|
|
+ goto out_schedule;
|
|
rc = sysfs_create_group(&sch->dev.kobj,
|
|
rc = sysfs_create_group(&sch->dev.kobj,
|
|
&io_subchannel_attr_group);
|
|
&io_subchannel_attr_group);
|
|
if (rc)
|
|
if (rc)
|
|
- return rc;
|
|
|
|
|
|
+ goto out_schedule;
|
|
/* Allocate I/O subchannel private data. */
|
|
/* Allocate I/O subchannel private data. */
|
|
sch->private = kzalloc(sizeof(struct io_subchannel_private),
|
|
sch->private = kzalloc(sizeof(struct io_subchannel_private),
|
|
GFP_KERNEL | GFP_DMA);
|
|
GFP_KERNEL | GFP_DMA);
|
|
- if (!sch->private) {
|
|
|
|
- rc = -ENOMEM;
|
|
|
|
|
|
+ if (!sch->private)
|
|
goto out_err;
|
|
goto out_err;
|
|
- }
|
|
|
|
|
|
+ /*
|
|
|
|
+ * First check if a fitting device may be found amongst the
|
|
|
|
+ * disconnected devices or in the orphanage.
|
|
|
|
+ */
|
|
|
|
+ dev_id.devno = sch->schib.pmcw.dev;
|
|
|
|
+ dev_id.ssid = sch->schid.ssid;
|
|
cdev = get_disc_ccwdev_by_dev_id(&dev_id, NULL);
|
|
cdev = get_disc_ccwdev_by_dev_id(&dev_id, NULL);
|
|
if (!cdev)
|
|
if (!cdev)
|
|
cdev = get_orphaned_ccwdev_by_dev_id(to_css(sch->dev.parent),
|
|
cdev = get_orphaned_ccwdev_by_dev_id(to_css(sch->dev.parent),
|
|
@@ -1213,24 +1295,21 @@ static int io_subchannel_probe(struct subchannel *sch)
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
cdev = io_subchannel_create_ccwdev(sch);
|
|
cdev = io_subchannel_create_ccwdev(sch);
|
|
- if (IS_ERR(cdev)) {
|
|
|
|
- rc = PTR_ERR(cdev);
|
|
|
|
|
|
+ if (IS_ERR(cdev))
|
|
goto out_err;
|
|
goto out_err;
|
|
- }
|
|
|
|
rc = io_subchannel_recog(cdev, sch);
|
|
rc = io_subchannel_recog(cdev, sch);
|
|
if (rc) {
|
|
if (rc) {
|
|
spin_lock_irqsave(sch->lock, flags);
|
|
spin_lock_irqsave(sch->lock, flags);
|
|
- sch_set_cdev(sch, NULL);
|
|
|
|
|
|
+ io_subchannel_recog_done(cdev);
|
|
spin_unlock_irqrestore(sch->lock, flags);
|
|
spin_unlock_irqrestore(sch->lock, flags);
|
|
- if (cdev->dev.release)
|
|
|
|
- cdev->dev.release(&cdev->dev);
|
|
|
|
- goto out_err;
|
|
|
|
}
|
|
}
|
|
return 0;
|
|
return 0;
|
|
out_err:
|
|
out_err:
|
|
kfree(sch->private);
|
|
kfree(sch->private);
|
|
sysfs_remove_group(&sch->dev.kobj, &io_subchannel_attr_group);
|
|
sysfs_remove_group(&sch->dev.kobj, &io_subchannel_attr_group);
|
|
- return rc;
|
|
|
|
|
|
+out_schedule:
|
|
|
|
+ io_subchannel_schedule_removal(sch);
|
|
|
|
+ return 0;
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
static int
|
|
@@ -1275,10 +1354,7 @@ static void io_subchannel_verify(struct subchannel *sch)
|
|
|
|
|
|
static int check_for_io_on_path(struct subchannel *sch, int mask)
|
|
static int check_for_io_on_path(struct subchannel *sch, int mask)
|
|
{
|
|
{
|
|
- int cc;
|
|
|
|
-
|
|
|
|
- cc = stsch(sch->schid, &sch->schib);
|
|
|
|
- if (cc)
|
|
|
|
|
|
+ if (cio_update_schib(sch))
|
|
return 0;
|
|
return 0;
|
|
if (scsw_actl(&sch->schib.scsw) && sch->schib.pmcw.lpum == mask)
|
|
if (scsw_actl(&sch->schib.scsw) && sch->schib.pmcw.lpum == mask)
|
|
return 1;
|
|
return 1;
|
|
@@ -1347,15 +1423,13 @@ static int io_subchannel_chp_event(struct subchannel *sch,
|
|
io_subchannel_verify(sch);
|
|
io_subchannel_verify(sch);
|
|
break;
|
|
break;
|
|
case CHP_OFFLINE:
|
|
case CHP_OFFLINE:
|
|
- if (stsch(sch->schid, &sch->schib))
|
|
|
|
- return -ENXIO;
|
|
|
|
- if (!css_sch_is_valid(&sch->schib))
|
|
|
|
|
|
+ if (cio_update_schib(sch))
|
|
return -ENODEV;
|
|
return -ENODEV;
|
|
io_subchannel_terminate_path(sch, mask);
|
|
io_subchannel_terminate_path(sch, mask);
|
|
break;
|
|
break;
|
|
case CHP_ONLINE:
|
|
case CHP_ONLINE:
|
|
- if (stsch(sch->schid, &sch->schib))
|
|
|
|
- return -ENXIO;
|
|
|
|
|
|
+ if (cio_update_schib(sch))
|
|
|
|
+ return -ENODEV;
|
|
sch->lpm |= mask & sch->opm;
|
|
sch->lpm |= mask & sch->opm;
|
|
io_subchannel_verify(sch);
|
|
io_subchannel_verify(sch);
|
|
break;
|
|
break;
|
|
@@ -1610,8 +1684,8 @@ static int io_subchannel_sch_event(struct subchannel *sch, int slow)
|
|
spin_lock_irqsave(sch->lock, flags);
|
|
spin_lock_irqsave(sch->lock, flags);
|
|
|
|
|
|
/* Reset intparm to zeroes. */
|
|
/* Reset intparm to zeroes. */
|
|
- sch->schib.pmcw.intparm = 0;
|
|
|
|
- cio_modify(sch);
|
|
|
|
|
|
+ sch->config.intparm = 0;
|
|
|
|
+ cio_commit_config(sch);
|
|
break;
|
|
break;
|
|
case REPROBE:
|
|
case REPROBE:
|
|
ccw_device_trigger_reprobe(cdev);
|
|
ccw_device_trigger_reprobe(cdev);
|
|
@@ -1652,6 +1726,9 @@ static int ccw_device_console_enable(struct ccw_device *cdev,
|
|
sch->private = cio_get_console_priv();
|
|
sch->private = cio_get_console_priv();
|
|
memset(sch->private, 0, sizeof(struct io_subchannel_private));
|
|
memset(sch->private, 0, sizeof(struct io_subchannel_private));
|
|
io_subchannel_init_fields(sch);
|
|
io_subchannel_init_fields(sch);
|
|
|
|
+ rc = cio_commit_config(sch);
|
|
|
|
+ if (rc)
|
|
|
|
+ return rc;
|
|
sch->driver = &io_subchannel_driver;
|
|
sch->driver = &io_subchannel_driver;
|
|
/* Initialize the ccw_device structure. */
|
|
/* Initialize the ccw_device structure. */
|
|
cdev->dev.parent= &sch->dev;
|
|
cdev->dev.parent= &sch->dev;
|
|
@@ -1723,7 +1800,7 @@ __ccwdev_check_busid(struct device *dev, void *id)
|
|
|
|
|
|
bus_id = id;
|
|
bus_id = id;
|
|
|
|
|
|
- return (strncmp(bus_id, dev_name(dev), BUS_ID_SIZE) == 0);
|
|
|
|
|
|
+ return (strcmp(bus_id, dev_name(dev)) == 0);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -1806,6 +1883,8 @@ ccw_device_remove (struct device *dev)
|
|
"device 0.%x.%04x\n",
|
|
"device 0.%x.%04x\n",
|
|
ret, cdev->private->dev_id.ssid,
|
|
ret, cdev->private->dev_id.ssid,
|
|
cdev->private->dev_id.devno);
|
|
cdev->private->dev_id.devno);
|
|
|
|
+ /* Give up reference obtained in ccw_device_set_online(). */
|
|
|
|
+ put_device(&cdev->dev);
|
|
}
|
|
}
|
|
ccw_device_set_timeout(cdev, 0);
|
|
ccw_device_set_timeout(cdev, 0);
|
|
cdev->drv = NULL;
|
|
cdev->drv = NULL;
|