[S390] cio: trigger subchannel event at resume time
[deliverable/linux.git] / drivers / s390 / cio / device.c
index a6c7d5426fb2ba4dd2bd24d349562f84a4893fcf..c6abb75c4615e2ff04d78edee05cce26a2d0c1c1 100644 (file)
@@ -136,7 +136,6 @@ static int io_subchannel_sch_event(struct subchannel *, int);
 static int io_subchannel_chp_event(struct subchannel *, struct chp_link *,
                                   int);
 static void recovery_func(unsigned long data);
-struct workqueue_struct *ccw_device_work;
 wait_queue_head_t ccw_device_init_wq;
 atomic_t ccw_device_init_count;
 
@@ -159,11 +158,16 @@ static int io_subchannel_prepare(struct subchannel *sch)
        return 0;
 }
 
-static void io_subchannel_settle(void)
+static int io_subchannel_settle(void)
 {
-       wait_event(ccw_device_init_wq,
-                  atomic_read(&ccw_device_init_count) == 0);
-       flush_workqueue(ccw_device_work);
+       int ret;
+
+       ret = wait_event_interruptible(ccw_device_init_wq,
+                               atomic_read(&ccw_device_init_count) == 0);
+       if (ret)
+               return -EINTR;
+       flush_workqueue(cio_work_q);
+       return 0;
 }
 
 static struct css_driver io_subchannel_driver = {
@@ -188,27 +192,13 @@ int __init io_subchannel_init(void)
        atomic_set(&ccw_device_init_count, 0);
        setup_timer(&recovery_timer, recovery_func, 0);
 
-       ccw_device_work = create_singlethread_workqueue("cio");
-       if (!ccw_device_work)
-               return -ENOMEM;
-       slow_path_wq = create_singlethread_workqueue("kslowcrw");
-       if (!slow_path_wq) {
-               ret = -ENOMEM;
-               goto out_err;
-       }
-       if ((ret = bus_register (&ccw_bus_type)))
-               goto out_err;
-
+       ret = bus_register(&ccw_bus_type);
+       if (ret)
+               return ret;
        ret = css_driver_register(&io_subchannel_driver);
        if (ret)
-               goto out_err;
+               bus_unregister(&ccw_bus_type);
 
-       return 0;
-out_err:
-       if (ccw_device_work)
-               destroy_workqueue(ccw_device_work);
-       if (slow_path_wq)
-               destroy_workqueue(slow_path_wq);
        return ret;
 }
 
@@ -1348,7 +1338,7 @@ static enum io_sch_action sch_get_action(struct subchannel *sch)
                /* Not operational. */
                if (!cdev)
                        return IO_SCH_UNREG;
-               if (!ccw_device_notify(cdev, CIO_GONE))
+               if (ccw_device_notify(cdev, CIO_GONE) != NOTIFY_OK)
                        return IO_SCH_UNREG;
                return IO_SCH_ORPH_UNREG;
        }
@@ -1356,12 +1346,12 @@ static enum io_sch_action sch_get_action(struct subchannel *sch)
        if (!cdev)
                return IO_SCH_ATTACH;
        if (sch->schib.pmcw.dev != cdev->private->dev_id.devno) {
-               if (!ccw_device_notify(cdev, CIO_GONE))
+               if (ccw_device_notify(cdev, CIO_GONE) != NOTIFY_OK)
                        return IO_SCH_UNREG_ATTACH;
                return IO_SCH_ORPH_ATTACH;
        }
        if ((sch->schib.pmcw.pam & sch->opm) == 0) {
-               if (!ccw_device_notify(cdev, CIO_NO_PATH))
+               if (ccw_device_notify(cdev, CIO_NO_PATH) != NOTIFY_OK)
                        return IO_SCH_UNREG;
                return IO_SCH_DISC;
        }
@@ -1410,6 +1400,12 @@ static int io_subchannel_sch_event(struct subchannel *sch, int process)
                rc = 0;
                goto out_unlock;
        case IO_SCH_VERIFY:
+               if (cdev->private->flags.resuming == 1) {
+                       if (cio_enable_subchannel(sch, (u32)(addr_t)sch)) {
+                               ccw_device_set_notoper(cdev);
+                               break;
+                       }
+               }
                /* Trigger path verification. */
                io_subchannel_verify(sch);
                rc = 0;
@@ -1448,7 +1444,8 @@ static int io_subchannel_sch_event(struct subchannel *sch, int process)
                break;
        case IO_SCH_UNREG_ATTACH:
                /* Unregister ccw device. */
-               ccw_device_unregister(cdev);
+               if (!cdev->private->flags.resuming)
+                       ccw_device_unregister(cdev);
                break;
        default:
                break;
@@ -1457,7 +1454,8 @@ static int io_subchannel_sch_event(struct subchannel *sch, int process)
        switch (action) {
        case IO_SCH_ORPH_UNREG:
        case IO_SCH_UNREG:
-               css_sch_device_unregister(sch);
+               if (!cdev || !cdev->private->flags.resuming)
+                       css_sch_device_unregister(sch);
                break;
        case IO_SCH_ORPH_ATTACH:
        case IO_SCH_UNREG_ATTACH:
@@ -1779,26 +1777,42 @@ static void __ccw_device_pm_restore(struct ccw_device *cdev)
 {
        struct subchannel *sch = to_subchannel(cdev->dev.parent);
 
-       if (cio_is_console(sch->schid))
-               goto out;
+       spin_lock_irq(sch->lock);
+       if (cio_is_console(sch->schid)) {
+               cio_enable_subchannel(sch, (u32)(addr_t)sch);
+               goto out_unlock;
+       }
        /*
         * While we were sleeping, devices may have gone or become
         * available again. Kick re-detection.
         */
-       spin_lock_irq(sch->lock);
        cdev->private->flags.resuming = 1;
+       css_schedule_eval(sch->schid);
+       spin_unlock_irq(sch->lock);
+       css_complete_work();
+
+       /* cdev may have been moved to a different subchannel. */
+       sch = to_subchannel(cdev->dev.parent);
+       spin_lock_irq(sch->lock);
+       if (cdev->private->state != DEV_STATE_ONLINE &&
+           cdev->private->state != DEV_STATE_OFFLINE)
+               goto out_unlock;
+
        ccw_device_recognition(cdev);
        spin_unlock_irq(sch->lock);
        wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev) ||
                   cdev->private->state == DEV_STATE_DISCONNECTED);
-out:
+       spin_lock_irq(sch->lock);
+
+out_unlock:
        cdev->private->flags.resuming = 0;
+       spin_unlock_irq(sch->lock);
 }
 
 static int resume_handle_boxed(struct ccw_device *cdev)
 {
        cdev->private->state = DEV_STATE_BOXED;
-       if (ccw_device_notify(cdev, CIO_BOXED))
+       if (ccw_device_notify(cdev, CIO_BOXED) == NOTIFY_OK)
                return 0;
        ccw_device_sched_todo(cdev, CDEV_TODO_UNREG);
        return -ENODEV;
@@ -1807,7 +1821,7 @@ static int resume_handle_boxed(struct ccw_device *cdev)
 static int resume_handle_disc(struct ccw_device *cdev)
 {
        cdev->private->state = DEV_STATE_DISCONNECTED;
-       if (ccw_device_notify(cdev, CIO_GONE))
+       if (ccw_device_notify(cdev, CIO_GONE) == NOTIFY_OK)
                return 0;
        ccw_device_sched_todo(cdev, CDEV_TODO_UNREG);
        return -ENODEV;
@@ -1816,40 +1830,31 @@ static int resume_handle_disc(struct ccw_device *cdev)
 static int ccw_device_pm_restore(struct device *dev)
 {
        struct ccw_device *cdev = to_ccwdev(dev);
-       struct subchannel *sch = to_subchannel(cdev->dev.parent);
-       int ret = 0, cm_enabled;
+       struct subchannel *sch;
+       int ret = 0;
 
        __ccw_device_pm_restore(cdev);
+       sch = to_subchannel(cdev->dev.parent);
        spin_lock_irq(sch->lock);
-       if (cio_is_console(sch->schid)) {
-               cio_enable_subchannel(sch, (u32)(addr_t)sch);
-               spin_unlock_irq(sch->lock);
+       if (cio_is_console(sch->schid))
                goto out_restore;
-       }
-       cdev->private->flags.donotify = 0;
+
        /* check recognition results */
        switch (cdev->private->state) {
        case DEV_STATE_OFFLINE:
+       case DEV_STATE_ONLINE:
+               cdev->private->flags.donotify = 0;
                break;
        case DEV_STATE_BOXED:
                ret = resume_handle_boxed(cdev);
-               spin_unlock_irq(sch->lock);
                if (ret)
-                       goto out;
+                       goto out_unlock;
                goto out_restore;
-       case DEV_STATE_DISCONNECTED:
-               goto out_disc_unlock;
        default:
-               goto out_unreg_unlock;
-       }
-       /* check if the device id has changed */
-       if (sch->schib.pmcw.dev != cdev->private->dev_id.devno) {
-               CIO_MSG_EVENT(0, "resume: sch 0.%x.%04x: failed (devno "
-                             "changed from %04x to %04x)\n",
-                             sch->schid.ssid, sch->schid.sch_no,
-                             cdev->private->dev_id.devno,
-                             sch->schib.pmcw.dev);
-               goto out_unreg_unlock;
+               ret = resume_handle_disc(cdev);
+               if (ret)
+                       goto out_unlock;
+               goto out_restore;
        }
        /* check if the device type has changed */
        if (!ccw_device_test_sense_data(cdev)) {
@@ -1858,24 +1863,30 @@ static int ccw_device_pm_restore(struct device *dev)
                ret = -ENODEV;
                goto out_unlock;
        }
-       if (!cdev->online) {
-               ret = 0;
+       if (!cdev->online)
                goto out_unlock;
-       }
-       ret = ccw_device_online(cdev);
-       if (ret)
-               goto out_disc_unlock;
 
-       cm_enabled = cdev->private->cmb != NULL;
+       if (ccw_device_online(cdev)) {
+               ret = resume_handle_disc(cdev);
+               if (ret)
+                       goto out_unlock;
+               goto out_restore;
+       }
        spin_unlock_irq(sch->lock);
-
        wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev));
-       if (cdev->private->state != DEV_STATE_ONLINE) {
-               spin_lock_irq(sch->lock);
-               goto out_disc_unlock;
+       spin_lock_irq(sch->lock);
+
+       if (ccw_device_notify(cdev, CIO_OPER) == NOTIFY_BAD) {
+               ccw_device_sched_todo(cdev, CDEV_TODO_UNREG);
+               ret = -ENODEV;
+               goto out_unlock;
        }
-       if (cm_enabled) {
+
+       /* reenable cmf, if needed */
+       if (cdev->private->cmb) {
+               spin_unlock_irq(sch->lock);
                ret = ccw_set_cmf(cdev, 1);
+               spin_lock_irq(sch->lock);
                if (ret) {
                        CIO_MSG_EVENT(2, "resume: cdev 0.%x.%04x: cmf failed "
                                      "(rc=%d)\n", cdev->private->dev_id.ssid,
@@ -1885,21 +1896,11 @@ static int ccw_device_pm_restore(struct device *dev)
        }
 
 out_restore:
+       spin_unlock_irq(sch->lock);
        if (cdev->online && cdev->drv && cdev->drv->restore)
                ret = cdev->drv->restore(cdev);
-out:
        return ret;
 
-out_disc_unlock:
-       ret = resume_handle_disc(cdev);
-       spin_unlock_irq(sch->lock);
-       if (ret)
-               return ret;
-       goto out_restore;
-
-out_unreg_unlock:
-       ccw_device_sched_todo(cdev, CDEV_TODO_UNREG_EVAL);
-       ret = -ENODEV;
 out_unlock:
        spin_unlock_irq(sch->lock);
        return ret;
@@ -2028,7 +2029,7 @@ void ccw_device_sched_todo(struct ccw_device *cdev, enum cdev_todo todo)
        /* Get workqueue ref. */
        if (!get_device(&cdev->dev))
                return;
-       if (!queue_work(slow_path_wq, &cdev->private->todo_work)) {
+       if (!queue_work(cio_work_q, &cdev->private->todo_work)) {
                /* Already queued, release workqueue ref. */
                put_device(&cdev->dev);
        }
@@ -2041,5 +2042,4 @@ EXPORT_SYMBOL(ccw_driver_register);
 EXPORT_SYMBOL(ccw_driver_unregister);
 EXPORT_SYMBOL(get_ccwdev_by_busid);
 EXPORT_SYMBOL(ccw_bus_type);
-EXPORT_SYMBOL(ccw_device_work);
 EXPORT_SYMBOL_GPL(ccw_device_get_subchannel_id);
This page took 0.032441 seconds and 5 git commands to generate.