diff options
Diffstat (limited to 'drivers/s390/cio/device.c')
| -rw-r--r-- | drivers/s390/cio/device.c | 2056 |
1 files changed, 1314 insertions, 742 deletions
diff --git a/drivers/s390/cio/device.c b/drivers/s390/cio/device.c index fec004f62bc..dfef5e63cb7 100644 --- a/drivers/s390/cio/device.c +++ b/drivers/s390/cio/device.c @@ -1,13 +1,15 @@ /* - * drivers/s390/cio/device.c * bus driver for ccw devices * - * Copyright (C) 2002 IBM Deutschland Entwicklung GmbH, - * IBM Corporation + * Copyright IBM Corp. 2002, 2008 * Author(s): Arnd Bergmann (arndb@de.ibm.com) * Cornelia Huck (cornelia.huck@de.ibm.com) * Martin Schwidefsky (schwidefsky@de.ibm.com) */ + +#define KMSG_COMPONENT "cio" +#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt + #include <linux/module.h> #include <linux/init.h> #include <linux/spinlock.h> @@ -17,25 +19,35 @@ #include <linux/list.h> #include <linux/device.h> #include <linux/workqueue.h> +#include <linux/delay.h> #include <linux/timer.h> +#include <linux/kernel_stat.h> #include <asm/ccwdev.h> #include <asm/cio.h> #include <asm/param.h> /* HZ */ #include <asm/cmb.h> +#include <asm/isc.h> +#include "chp.h" #include "cio.h" #include "cio_debug.h" #include "css.h" #include "device.h" #include "ioasm.h" #include "io_sch.h" +#include "blacklist.h" +#include "chsc.h" static struct timer_list recovery_timer; static DEFINE_SPINLOCK(recovery_lock); static int recovery_phase; static const unsigned long recovery_delay[] = { 3, 30, 300 }; +static atomic_t ccw_device_init_count = ATOMIC_INIT(0); +static DECLARE_WAIT_QUEUE_HEAD(ccw_device_init_wq); +static struct bus_type ccw_bus_type; + /******************* bus type handling ***********************/ /* The Linux driver model distinguishes between a bus type and @@ -120,90 +132,77 @@ static int ccw_uevent(struct device *dev, struct kobj_uevent_env *env) return ret; } -struct bus_type ccw_bus_type; - static void io_subchannel_irq(struct subchannel *); static int io_subchannel_probe(struct subchannel *); static int io_subchannel_remove(struct subchannel *); -static int io_subchannel_notify(struct subchannel *, int); -static void io_subchannel_verify(struct subchannel *); -static void io_subchannel_ioterm(struct subchannel *); static void io_subchannel_shutdown(struct subchannel *); +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); + +static struct css_device_id io_subchannel_ids[] = { + { .match_flags = 0x1, .type = SUBCHANNEL_TYPE_IO, }, + { /* end of list */ }, +}; +MODULE_DEVICE_TABLE(css, io_subchannel_ids); + +static int io_subchannel_prepare(struct subchannel *sch) +{ + struct ccw_device *cdev; + /* + * Don't allow suspend while a ccw device registration + * is still outstanding. + */ + cdev = sch_get_cdev(sch); + if (cdev && !device_is_registered(&cdev->dev)) + return -EAGAIN; + return 0; +} + +static int io_subchannel_settle(void) +{ + 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 = { - .owner = THIS_MODULE, - .subchannel_type = SUBCHANNEL_TYPE_IO, - .name = "io_subchannel", + .drv = { + .owner = THIS_MODULE, + .name = "io_subchannel", + }, + .subchannel_type = io_subchannel_ids, .irq = io_subchannel_irq, - .notify = io_subchannel_notify, - .verify = io_subchannel_verify, - .termination = io_subchannel_ioterm, + .sch_event = io_subchannel_sch_event, + .chp_event = io_subchannel_chp_event, .probe = io_subchannel_probe, .remove = io_subchannel_remove, .shutdown = io_subchannel_shutdown, + .prepare = io_subchannel_prepare, + .settle = io_subchannel_settle, }; -struct workqueue_struct *ccw_device_work; -struct workqueue_struct *ccw_device_notify_work; -wait_queue_head_t ccw_device_init_wq; -atomic_t ccw_device_init_count; - -static void recovery_func(unsigned long data); - -static int __init -init_ccw_bus_type (void) +int __init io_subchannel_init(void) { int ret; - init_waitqueue_head(&ccw_device_init_wq); - 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; /* FIXME: better errno ? */ - ccw_device_notify_work = create_singlethread_workqueue("cio_notify"); - if (!ccw_device_notify_work) { - ret = -ENOMEM; /* FIXME: better errno ? */ - goto out_err; - } - slow_path_wq = create_singlethread_workqueue("kslowcrw"); - if (!slow_path_wq) { - ret = -ENOMEM; /* FIXME: better errno ? */ - 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); - wait_event(ccw_device_init_wq, - atomic_read(&ccw_device_init_count) == 0); - flush_workqueue(ccw_device_work); - return 0; -out_err: - if (ccw_device_work) - destroy_workqueue(ccw_device_work); - if (ccw_device_notify_work) - destroy_workqueue(ccw_device_notify_work); - if (slow_path_wq) - destroy_workqueue(slow_path_wq); return ret; } -static void __exit -cleanup_ccw_bus_type (void) -{ - css_driver_unregister(&io_subchannel_driver); - bus_unregister(&ccw_bus_type); - destroy_workqueue(ccw_device_notify_work); - destroy_workqueue(ccw_device_work); -} - -subsys_initcall(init_ccw_bus_type); -module_exit(cleanup_ccw_bus_type); /************************ device handling **************************/ @@ -295,68 +294,19 @@ int ccw_device_is_orphan(struct ccw_device *cdev) static void ccw_device_unregister(struct ccw_device *cdev) { - if (test_and_clear_bit(1, &cdev->private->registered)) + if (device_is_registered(&cdev->dev)) { + /* Undo device_add(). */ device_del(&cdev->dev); -} - -static void ccw_device_remove_orphan_cb(struct device *dev) -{ - struct ccw_device *cdev = to_ccwdev(dev); - - ccw_device_unregister(cdev); - put_device(&cdev->dev); -} - -static void ccw_device_remove_sch_cb(struct device *dev) -{ - struct subchannel *sch; - - sch = to_subchannel(dev); - css_sch_device_unregister(sch); - /* Reset intparm to zeroes. */ - sch->schib.pmcw.intparm = 0; - cio_modify(sch); - put_device(&sch->dev); -} - -static void -ccw_device_remove_disconnected(struct ccw_device *cdev) -{ - unsigned long flags; - int rc; - - /* - * Forced offline in disconnected state means - * 'throw away device'. - */ - if (ccw_device_is_orphan(cdev)) { - /* - * Deregister ccw device. - * Unfortunately, we cannot do this directly from the - * attribute method. - */ - spin_lock_irqsave(cdev->ccwlock, flags); - cdev->private->state = DEV_STATE_NOT_OPER; - spin_unlock_irqrestore(cdev->ccwlock, flags); - rc = device_schedule_callback(&cdev->dev, - ccw_device_remove_orphan_cb); - if (rc) - CIO_MSG_EVENT(2, "Couldn't unregister orphan " - "0.%x.%04x\n", - cdev->private->dev_id.ssid, - cdev->private->dev_id.devno); - return; } - /* Deregister subchannel, which will kill the ccw device. */ - rc = device_schedule_callback(cdev->dev.parent, - ccw_device_remove_sch_cb); - if (rc) - CIO_MSG_EVENT(2, "Couldn't unregister disconnected device " - "0.%x.%04x\n", - cdev->private->dev_id.ssid, - cdev->private->dev_id.devno); + if (cdev->private->flags.initialized) { + cdev->private->flags.initialized = 0; + /* Release reference from device_initialize(). */ + put_device(&cdev->dev); + } } +static void io_subchannel_quiesce(struct subchannel *); + /** * ccw_device_set_offline() - disable a ccw device for I/O * @cdev: target ccw device @@ -370,7 +320,8 @@ ccw_device_remove_disconnected(struct ccw_device *cdev) */ int ccw_device_set_offline(struct ccw_device *cdev) { - int ret; + struct subchannel *sch; + int ret, state; if (!cdev) return -ENODEV; @@ -382,28 +333,54 @@ int ccw_device_set_offline(struct ccw_device *cdev) if (ret != 0) return ret; } - cdev->online = 0; spin_lock_irq(cdev->ccwlock); - ret = ccw_device_offline(cdev); - if (ret == -ENODEV) { - if (cdev->private->state != DEV_STATE_NOT_OPER) { - cdev->private->state = DEV_STATE_OFFLINE; - dev_fsm_event(cdev, DEV_EVENT_NOTOPER); - } + sch = to_subchannel(cdev->dev.parent); + cdev->online = 0; + /* Wait until a final state or DISCONNECTED is reached */ + while (!dev_fsm_final_state(cdev) && + cdev->private->state != DEV_STATE_DISCONNECTED) { spin_unlock_irq(cdev->ccwlock); - return ret; + wait_event(cdev->private->wait_q, (dev_fsm_final_state(cdev) || + cdev->private->state == DEV_STATE_DISCONNECTED)); + spin_lock_irq(cdev->ccwlock); } - spin_unlock_irq(cdev->ccwlock); - if (ret == 0) - wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev)); - else { - CIO_MSG_EVENT(2, "ccw_device_offline returned %d, " - "device 0.%x.%04x\n", - ret, cdev->private->dev_id.ssid, + do { + ret = ccw_device_offline(cdev); + if (!ret) + break; + CIO_MSG_EVENT(0, "ccw_device_offline returned %d, device " + "0.%x.%04x\n", ret, cdev->private->dev_id.ssid, cdev->private->dev_id.devno); - cdev->online = 1; + if (ret != -EBUSY) + goto error; + state = cdev->private->state; + spin_unlock_irq(cdev->ccwlock); + io_subchannel_quiesce(sch); + spin_lock_irq(cdev->ccwlock); + cdev->private->state = state; + } while (ret == -EBUSY); + spin_unlock_irq(cdev->ccwlock); + wait_event(cdev->private->wait_q, (dev_fsm_final_state(cdev) || + cdev->private->state == DEV_STATE_DISCONNECTED)); + /* Inform the user if set offline failed. */ + if (cdev->private->state == DEV_STATE_BOXED) { + pr_warning("%s: The device entered boxed state while " + "being set offline\n", dev_name(&cdev->dev)); + } else if (cdev->private->state == DEV_STATE_NOT_OPER) { + pr_warning("%s: The device stopped operating while " + "being set offline\n", dev_name(&cdev->dev)); } - return ret; + /* Give up reference from ccw_device_set_online(). */ + put_device(&cdev->dev); + return 0; + +error: + cdev->private->state = DEV_STATE_OFFLINE; + dev_fsm_event(cdev, DEV_EVENT_NOTOPER); + spin_unlock_irq(cdev->ccwlock); + /* Give up reference from ccw_device_set_online(). */ + put_device(&cdev->dev); + return -ENODEV; } /** @@ -421,11 +398,15 @@ int ccw_device_set_offline(struct ccw_device *cdev) int ccw_device_set_online(struct ccw_device *cdev) { int ret; + int ret2; if (!cdev) return -ENODEV; if (cdev->online || !cdev->drv) return -EINVAL; + /* Hold on to an extra reference while device is online. */ + if (!get_device(&cdev->dev)) + return -ENODEV; spin_lock_irq(cdev->ccwlock); ret = ccw_device_online(cdev); @@ -433,117 +414,175 @@ int ccw_device_set_online(struct ccw_device *cdev) if (ret == 0) wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev)); else { - CIO_MSG_EVENT(2, "ccw_device_online returned %d, " + CIO_MSG_EVENT(0, "ccw_device_online returned %d, " "device 0.%x.%04x\n", ret, cdev->private->dev_id.ssid, cdev->private->dev_id.devno); + /* Give up online reference since onlining failed. */ + put_device(&cdev->dev); return ret; } - if (cdev->private->state != DEV_STATE_ONLINE) + spin_lock_irq(cdev->ccwlock); + /* Check if online processing was successful */ + if ((cdev->private->state != DEV_STATE_ONLINE) && + (cdev->private->state != DEV_STATE_W4SENSE)) { + spin_unlock_irq(cdev->ccwlock); + /* Inform the user that set online failed. */ + if (cdev->private->state == DEV_STATE_BOXED) { + pr_warning("%s: Setting the device online failed " + "because it is boxed\n", + dev_name(&cdev->dev)); + } else if (cdev->private->state == DEV_STATE_NOT_OPER) { + pr_warning("%s: Setting the device online failed " + "because it is not operational\n", + dev_name(&cdev->dev)); + } + /* Give up online reference since onlining failed. */ + put_device(&cdev->dev); return -ENODEV; - if (!cdev->drv->set_online || cdev->drv->set_online(cdev) == 0) { - cdev->online = 1; - return 0; } + spin_unlock_irq(cdev->ccwlock); + if (cdev->drv->set_online) + ret = cdev->drv->set_online(cdev); + if (ret) + goto rollback; + spin_lock_irq(cdev->ccwlock); - ret = ccw_device_offline(cdev); + cdev->online = 1; spin_unlock_irq(cdev->ccwlock); - if (ret == 0) - wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev)); - else - CIO_MSG_EVENT(2, "ccw_device_offline returned %d, " - "device 0.%x.%04x\n", - ret, cdev->private->dev_id.ssid, - cdev->private->dev_id.devno); - return (ret == 0) ? -ENODEV : ret; + return 0; + +rollback: + spin_lock_irq(cdev->ccwlock); + /* Wait until a final state or DISCONNECTED is reached */ + while (!dev_fsm_final_state(cdev) && + cdev->private->state != DEV_STATE_DISCONNECTED) { + spin_unlock_irq(cdev->ccwlock); + wait_event(cdev->private->wait_q, (dev_fsm_final_state(cdev) || + cdev->private->state == DEV_STATE_DISCONNECTED)); + spin_lock_irq(cdev->ccwlock); + } + ret2 = ccw_device_offline(cdev); + if (ret2) + goto error; + spin_unlock_irq(cdev->ccwlock); + wait_event(cdev->private->wait_q, (dev_fsm_final_state(cdev) || + cdev->private->state == DEV_STATE_DISCONNECTED)); + /* Give up online reference since onlining failed. */ + put_device(&cdev->dev); + return ret; + +error: + CIO_MSG_EVENT(0, "rollback ccw_device_offline returned %d, " + "device 0.%x.%04x\n", + ret2, cdev->private->dev_id.ssid, + cdev->private->dev_id.devno); + cdev->private->state = DEV_STATE_OFFLINE; + spin_unlock_irq(cdev->ccwlock); + /* Give up online reference since onlining failed. */ + put_device(&cdev->dev); + return ret; } -static void online_store_handle_offline(struct ccw_device *cdev) +static int online_store_handle_offline(struct ccw_device *cdev) { - if (cdev->private->state == DEV_STATE_DISCONNECTED) - ccw_device_remove_disconnected(cdev); - else if (cdev->drv && cdev->drv->set_offline) - ccw_device_set_offline(cdev); + if (cdev->private->state == DEV_STATE_DISCONNECTED) { + spin_lock_irq(cdev->ccwlock); + ccw_device_sched_todo(cdev, CDEV_TODO_UNREG_EVAL); + spin_unlock_irq(cdev->ccwlock); + return 0; + } + if (cdev->drv && cdev->drv->set_offline) + return ccw_device_set_offline(cdev); + return -EINVAL; } static int online_store_recog_and_online(struct ccw_device *cdev) { - int ret; - /* Do device recognition, if needed. */ - if (cdev->id.cu_type == 0) { - ret = ccw_device_recognition(cdev); - if (ret) { - CIO_MSG_EVENT(0, "Couldn't start recognition " - "for device 0.%x.%04x (ret=%d)\n", - cdev->private->dev_id.ssid, - cdev->private->dev_id.devno, ret); - return ret; - } + if (cdev->private->state == DEV_STATE_BOXED) { + spin_lock_irq(cdev->ccwlock); + ccw_device_recognition(cdev); + spin_unlock_irq(cdev->ccwlock); wait_event(cdev->private->wait_q, cdev->private->flags.recog_done); + if (cdev->private->state != DEV_STATE_OFFLINE) + /* recognition failed */ + return -EAGAIN; } if (cdev->drv && cdev->drv->set_online) - ccw_device_set_online(cdev); - return 0; + return ccw_device_set_online(cdev); + return -EINVAL; } -static void online_store_handle_online(struct ccw_device *cdev, int force) + +static int online_store_handle_online(struct ccw_device *cdev, int force) { int ret; ret = online_store_recog_and_online(cdev); - if (ret) - return; + if (ret && !force) + return ret; if (force && cdev->private->state == DEV_STATE_BOXED) { ret = ccw_device_stlck(cdev); - if (ret) { - dev_warn(&cdev->dev, - "ccw_device_stlck returned %d!\n", ret); - return; - } + if (ret) + return ret; if (cdev->id.cu_type == 0) cdev->private->state = DEV_STATE_NOT_OPER; - online_store_recog_and_online(cdev); + ret = online_store_recog_and_online(cdev); + if (ret) + return ret; } - + return 0; } static ssize_t online_store (struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct ccw_device *cdev = to_ccwdev(dev); - int i, force; - char *tmp; + int force, ret; + unsigned long i; + /* Prevent conflict between multiple on-/offline processing requests. */ if (atomic_cmpxchg(&cdev->private->onoff, 0, 1) != 0) return -EAGAIN; - - if (cdev->drv && !try_module_get(cdev->drv->owner)) { - atomic_set(&cdev->private->onoff, 0); - return -EINVAL; + /* Prevent conflict between internal I/Os and on-/offline processing. */ + if (!dev_fsm_final_state(cdev) && + cdev->private->state != DEV_STATE_DISCONNECTED) { + ret = -EAGAIN; + goto out; + } + /* Prevent conflict between pending work and on-/offline processing.*/ + if (work_pending(&cdev->private->todo_work)) { + ret = -EAGAIN; + goto out; } if (!strncmp(buf, "force\n", count)) { force = 1; i = 1; + ret = 0; } else { force = 0; - i = simple_strtoul(buf, &tmp, 16); + ret = kstrtoul(buf, 16, &i); } + if (ret) + goto out; + device_lock(dev); switch (i) { case 0: - online_store_handle_offline(cdev); + ret = online_store_handle_offline(cdev); break; case 1: - online_store_handle_online(cdev, force); + ret = online_store_handle_online(cdev, force); break; default: - count = -EINVAL; + ret = -EINVAL; } - if (cdev->drv) - module_put(cdev->drv->owner); + device_unlock(dev); + +out: atomic_set(&cdev->private->onoff, 0); - return count; + return (ret < 0) ? ret : count; } static ssize_t @@ -571,28 +610,53 @@ available_show (struct device *dev, struct device_attribute *attr, char *buf) } } +static ssize_t +initiate_logging(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct subchannel *sch = to_subchannel(dev); + int rc; + + rc = chsc_siosl(sch->schid); + if (rc < 0) { + pr_warning("Logging for subchannel 0.%x.%04x failed with " + "errno=%d\n", + sch->schid.ssid, sch->schid.sch_no, rc); + return rc; + } + pr_notice("Logging for subchannel 0.%x.%04x was triggered\n", + sch->schid.ssid, sch->schid.sch_no); + return count; +} + +static ssize_t vpm_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct subchannel *sch = to_subchannel(dev); + + return sprintf(buf, "%02x\n", sch->vpm); +} + static DEVICE_ATTR(chpids, 0444, chpids_show, NULL); static DEVICE_ATTR(pimpampom, 0444, pimpampom_show, NULL); static DEVICE_ATTR(devtype, 0444, devtype_show, NULL); static DEVICE_ATTR(cutype, 0444, cutype_show, NULL); static DEVICE_ATTR(modalias, 0444, modalias_show, NULL); static DEVICE_ATTR(online, 0644, online_show, online_store); -extern struct device_attribute dev_attr_cmb_enable; static DEVICE_ATTR(availability, 0444, available_show, NULL); +static DEVICE_ATTR(logging, 0200, NULL, initiate_logging); +static DEVICE_ATTR(vpm, 0444, vpm_show, NULL); -static struct attribute * subch_attrs[] = { +static struct attribute *io_subchannel_attrs[] = { &dev_attr_chpids.attr, &dev_attr_pimpampom.attr, + &dev_attr_logging.attr, + &dev_attr_vpm.attr, NULL, }; -static struct attribute_group subch_attr_group = { - .attrs = subch_attrs, -}; - -struct attribute_group *subch_attr_groups[] = { - &subch_attr_group, - NULL, +static struct attribute_group io_subchannel_attr_group = { + .attrs = io_subchannel_attrs, }; static struct attribute * ccwdev_attrs[] = { @@ -609,111 +673,56 @@ static struct attribute_group ccwdev_attr_group = { .attrs = ccwdev_attrs, }; -static struct attribute_group *ccwdev_attr_groups[] = { +static const struct attribute_group *ccwdev_attr_groups[] = { &ccwdev_attr_group, NULL, }; -/* this is a simple abstraction for device_register that sets the - * correct bus type and adds the bus specific files */ -static int ccw_device_register(struct ccw_device *cdev) +static int ccw_device_add(struct ccw_device *cdev) { struct device *dev = &cdev->dev; - int ret; dev->bus = &ccw_bus_type; - - if ((ret = device_add(dev))) - return ret; - - set_bit(1, &cdev->private->registered); - return ret; -} - -struct match_data { - struct ccw_dev_id dev_id; - struct ccw_device * sibling; -}; - -static int -match_devno(struct device * dev, void * data) -{ - struct match_data * d = data; - struct ccw_device * cdev; - - cdev = to_ccwdev(dev); - if ((cdev->private->state == DEV_STATE_DISCONNECTED) && - !ccw_device_is_orphan(cdev) && - ccw_dev_id_is_equal(&cdev->private->dev_id, &d->dev_id) && - (cdev != d->sibling)) - return 1; - return 0; -} - -static struct ccw_device * get_disc_ccwdev_by_dev_id(struct ccw_dev_id *dev_id, - struct ccw_device *sibling) -{ - struct device *dev; - struct match_data data; - - data.dev_id = *dev_id; - data.sibling = sibling; - dev = bus_find_device(&ccw_bus_type, NULL, &data, match_devno); - - return dev ? to_ccwdev(dev) : NULL; + return device_add(dev); } -static int match_orphan(struct device *dev, void *data) +static int match_dev_id(struct device *dev, void *data) { - struct ccw_dev_id *dev_id; - struct ccw_device *cdev; + struct ccw_device *cdev = to_ccwdev(dev); + struct ccw_dev_id *dev_id = data; - dev_id = data; - cdev = to_ccwdev(dev); return ccw_dev_id_is_equal(&cdev->private->dev_id, dev_id); } -static struct ccw_device * -get_orphaned_ccwdev_by_dev_id(struct channel_subsystem *css, - struct ccw_dev_id *dev_id) +/** + * get_ccwdev_by_dev_id() - obtain device from a ccw device id + * @dev_id: id of the device to be searched + * + * This function searches all devices attached to the ccw bus for a device + * matching @dev_id. + * Returns: + * If a device is found its reference count is increased and returned; + * else %NULL is returned. + */ +struct ccw_device *get_ccwdev_by_dev_id(struct ccw_dev_id *dev_id) { struct device *dev; - dev = device_find_child(&css->pseudo_subchannel->dev, dev_id, - match_orphan); + dev = bus_find_device(&ccw_bus_type, NULL, dev_id, match_dev_id); return dev ? to_ccwdev(dev) : NULL; } +EXPORT_SYMBOL_GPL(get_ccwdev_by_dev_id); -static void -ccw_device_add_changed(struct work_struct *work) +static void ccw_device_do_unbind_bind(struct ccw_device *cdev) { - struct ccw_device_private *priv; - struct ccw_device *cdev; + int ret; - priv = container_of(work, struct ccw_device_private, kick_work); - cdev = priv->cdev; - if (device_add(&cdev->dev)) { - put_device(&cdev->dev); - return; + if (device_is_registered(&cdev->dev)) { + device_release_driver(&cdev->dev); + ret = device_attach(&cdev->dev); + WARN_ON(ret == -ENODEV); } - set_bit(1, &cdev->private->registered); -} - -void ccw_device_do_unreg_rereg(struct work_struct *work) -{ - struct ccw_device_private *priv; - struct ccw_device *cdev; - struct subchannel *sch; - - priv = container_of(work, struct ccw_device_private, kick_work); - cdev = priv->cdev; - sch = to_subchannel(cdev->dev.parent); - - ccw_device_unregister(cdev); - PREPARE_WORK(&cdev->private->kick_work, - ccw_device_add_changed); - queue_work(ccw_device_work, &cdev->private->kick_work); } static void @@ -722,6 +731,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); } @@ -741,23 +752,51 @@ static struct ccw_device * io_subchannel_allocate_dev(struct subchannel *sch) return ERR_PTR(-ENOMEM); } +static void ccw_device_todo(struct work_struct *work); + static int io_subchannel_initialize_dev(struct subchannel *sch, struct ccw_device *cdev) { - cdev->private->cdev = cdev; - atomic_set(&cdev->private->onoff, 0); + struct ccw_device_private *priv = cdev->private; + int ret; + + priv->cdev = cdev; + priv->int_class = IRQIO_CIO; + priv->state = DEV_STATE_NOT_OPER; + priv->dev_id.devno = sch->schib.pmcw.dev; + priv->dev_id.ssid = sch->schid.ssid; + priv->schid = sch->schid; + + INIT_WORK(&priv->todo_work, ccw_device_todo); + INIT_LIST_HEAD(&priv->cmb_list); + init_waitqueue_head(&priv->wait_q); + init_timer(&priv->timer); + + atomic_set(&priv->onoff, 0); + cdev->ccwlock = sch->lock; cdev->dev.parent = &sch->dev; cdev->dev.release = ccw_device_release; - INIT_WORK(&cdev->private->kick_work, NULL); cdev->dev.groups = ccwdev_attr_groups; /* Do first half of device_register. */ device_initialize(&cdev->dev); + ret = dev_set_name(&cdev->dev, "0.%x.%04x", cdev->private->dev_id.ssid, + cdev->private->dev_id.devno); + if (ret) + goto out_put; if (!get_device(&sch->dev)) { - if (cdev->dev.release) - cdev->dev.release(&cdev->dev); - return -ENODEV; + ret = -ENODEV; + goto out_put; } + priv->flags.initialized = 1; + spin_lock_irq(sch->lock); + sch_set_cdev(sch, cdev); + spin_unlock_irq(sch->lock); return 0; + +out_put: + /* Release reference from device_initialize(). */ + put_device(&cdev->dev); + return ret; } static struct ccw_device * io_subchannel_create_ccwdev(struct subchannel *sch) @@ -768,66 +807,13 @@ static struct ccw_device * io_subchannel_create_ccwdev(struct subchannel *sch) cdev = io_subchannel_allocate_dev(sch); if (!IS_ERR(cdev)) { ret = io_subchannel_initialize_dev(sch, cdev); - if (ret) { - kfree(cdev); + if (ret) cdev = ERR_PTR(ret); - } } return cdev; } -static int io_subchannel_recog(struct ccw_device *, struct subchannel *); - -static void sch_attach_device(struct subchannel *sch, - struct ccw_device *cdev) -{ - css_update_ssd_info(sch); - spin_lock_irq(sch->lock); - sch_set_cdev(sch, cdev); - cdev->private->schid = sch->schid; - cdev->ccwlock = sch->lock; - device_trigger_reprobe(sch); - spin_unlock_irq(sch->lock); -} - -static void sch_attach_disconnected_device(struct subchannel *sch, - struct ccw_device *cdev) -{ - struct subchannel *other_sch; - int ret; - - other_sch = to_subchannel(get_device(cdev->dev.parent)); - ret = device_move(&cdev->dev, &sch->dev); - if (ret) { - CIO_MSG_EVENT(2, "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); - 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); -} - -static void sch_attach_orphaned_device(struct subchannel *sch, - struct ccw_device *cdev) -{ - int ret; - - /* Try to move the ccw device to its new subchannel. */ - 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); - return; - } - sch_attach_device(sch, cdev); -} +static void io_subchannel_recog(struct ccw_device *, struct subchannel *); static void sch_create_and_recog_new_device(struct subchannel *sch) { @@ -840,83 +826,28 @@ static void sch_create_and_recog_new_device(struct subchannel *sch) css_sch_device_unregister(sch); return; } - spin_lock_irq(sch->lock); - sch_set_cdev(sch, cdev); - spin_unlock_irq(sch->lock); /* Start recognition for the new ccw device. */ - if (io_subchannel_recog(cdev, 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); - } -} - - -void ccw_device_move_to_orphanage(struct work_struct *work) -{ - struct ccw_device_private *priv; - struct ccw_device *cdev; - struct ccw_device *replacing_cdev; - struct subchannel *sch; - int ret; - struct channel_subsystem *css; - struct ccw_dev_id dev_id; - - priv = container_of(work, struct ccw_device_private, kick_work); - cdev = priv->cdev; - sch = to_subchannel(cdev->dev.parent); - css = to_css(sch->dev.parent); - dev_id.devno = sch->schib.pmcw.dev; - dev_id.ssid = sch->schid.ssid; - - /* - * Move the orphaned ccw device to the orphanage so the replacing - * ccw device can take its place on the subchannel. - */ - 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); - return; - } - cdev->ccwlock = css->pseudo_subchannel->lock; - /* - * Search for the replacing ccw device - * - among the disconnected devices - * - in the orphanage - */ - replacing_cdev = get_disc_ccwdev_by_dev_id(&dev_id, cdev); - if (replacing_cdev) { - sch_attach_disconnected_device(sch, replacing_cdev); - return; - } - replacing_cdev = get_orphaned_ccwdev_by_dev_id(css, &dev_id); - if (replacing_cdev) { - sch_attach_orphaned_device(sch, replacing_cdev); - return; - } - sch_create_and_recog_new_device(sch); + io_subchannel_recog(cdev, sch); } /* * Register recognized device. */ -static void -io_subchannel_register(struct work_struct *work) +static void io_subchannel_register(struct ccw_device *cdev) { - struct ccw_device_private *priv; - struct ccw_device *cdev; struct subchannel *sch; - int ret; + int ret, adjust_init_count = 1; unsigned long flags; - priv = container_of(work, struct ccw_device_private, kick_work); - cdev = priv->cdev; 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); /* * io_subchannel_register() will also be called after device @@ -924,64 +855,56 @@ io_subchannel_register(struct work_struct *work) * be registered). We need to reprobe since we may now have sense id * information. */ - if (klist_node_attached(&cdev->dev.knode_parent)) { + if (device_is_registered(&cdev->dev)) { if (!cdev->drv) { ret = device_reprobe(&cdev->dev); if (ret) /* We can't do much here. */ - CIO_MSG_EVENT(2, "device_reprobe() returned" + CIO_MSG_EVENT(0, "device_reprobe() returned" " %d for 0.%x.%04x\n", ret, cdev->private->dev_id.ssid, cdev->private->dev_id.devno); } + adjust_init_count = 0; goto out; } /* * Now we know this subchannel will stay, we can throw * our delayed uevent. */ - sch->dev.uevent_suppress = 0; + dev_set_uevent_suppress(&sch->dev, 0); kobject_uevent(&sch->dev.kobj, KOBJ_ADD); /* make it known to the system */ - ret = ccw_device_register(cdev); + ret = ccw_device_add(cdev); if (ret) { 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); - 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: 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)) +out_err: + if (adjust_init_count && atomic_dec_and_test(&ccw_device_init_count)) wake_up(&ccw_device_init_wq); } -static void ccw_device_call_sch_unregister(struct work_struct *work) +static void ccw_device_call_sch_unregister(struct ccw_device *cdev) { - struct ccw_device_private *priv; - struct ccw_device *cdev; struct subchannel *sch; - priv = container_of(work, struct ccw_device_private, kick_work); - cdev = priv->cdev; + /* Get subchannel reference for local processing. */ + if (!get_device(cdev->dev.parent)) + return; sch = to_subchannel(cdev->dev.parent); css_sch_device_unregister(sch); - /* Reset intparm to zeroes. */ - sch->schib.pmcw.intparm = 0; - cio_modify(sch); - put_device(&cdev->dev); + /* Release subchannel reference for local processing. */ put_device(&sch->dev); } @@ -991,119 +914,111 @@ static void ccw_device_call_sch_unregister(struct work_struct *work) void io_subchannel_recog_done(struct ccw_device *cdev) { - struct subchannel *sch; - if (css_init_done == 0) { cdev->private->flags.recog_done = 1; return; } switch (cdev->private->state) { + case DEV_STATE_BOXED: + /* Device did not respond in time. */ case DEV_STATE_NOT_OPER: cdev->private->flags.recog_done = 1; /* Remove device found not operational. */ - if (!get_device(&cdev->dev)) - break; - sch = to_subchannel(cdev->dev.parent); - PREPARE_WORK(&cdev->private->kick_work, - ccw_device_call_sch_unregister); - queue_work(slow_path_wq, &cdev->private->kick_work); + ccw_device_sched_todo(cdev, CDEV_TODO_UNREG); if (atomic_dec_and_test(&ccw_device_init_count)) wake_up(&ccw_device_init_wq); break; - case DEV_STATE_BOXED: - /* Device did not respond in time. */ case DEV_STATE_OFFLINE: /* * We can't register the device in interrupt context so * we schedule a work item. */ - if (!get_device(&cdev->dev)) - break; - PREPARE_WORK(&cdev->private->kick_work, - io_subchannel_register); - queue_work(slow_path_wq, &cdev->private->kick_work); + ccw_device_sched_todo(cdev, CDEV_TODO_REGISTER); break; } } -static int -io_subchannel_recog(struct ccw_device *cdev, struct subchannel *sch) +static void io_subchannel_recog(struct ccw_device *cdev, struct subchannel *sch) { - int rc; - struct ccw_device_private *priv; - - sch_set_cdev(sch, cdev); - sch->driver = &io_subchannel_driver; - cdev->ccwlock = sch->lock; - - /* Init private data. */ - priv = cdev->private; - priv->dev_id.devno = sch->schib.pmcw.dev; - priv->dev_id.ssid = sch->schid.ssid; - priv->schid = sch->schid; - priv->state = DEV_STATE_NOT_OPER; - INIT_LIST_HEAD(&priv->cmb_list); - init_waitqueue_head(&priv->wait_q); - init_timer(&priv->timer); - - /* Set an initial name for the device. */ - snprintf (cdev->dev.bus_id, BUS_ID_SIZE, "0.%x.%04x", - sch->schid.ssid, sch->schib.pmcw.dev); - /* Increase counter of devices currently in recognition. */ atomic_inc(&ccw_device_init_count); /* Start async. device sensing. */ spin_lock_irq(sch->lock); - rc = ccw_device_recognition(cdev); + ccw_device_recognition(cdev); spin_unlock_irq(sch->lock); - if (rc) { - if (atomic_dec_and_test(&ccw_device_init_count)) - wake_up(&ccw_device_init_wq); - } - return rc; } -static void ccw_device_move_to_sch(struct work_struct *work) +static int ccw_device_move_to_sch(struct ccw_device *cdev, + struct subchannel *sch) { - struct ccw_device_private *priv; - int rc; - struct subchannel *sch; - struct ccw_device *cdev; - struct subchannel *former_parent; + struct subchannel *old_sch; + int rc, old_enabled = 0; + + old_sch = to_subchannel(cdev->dev.parent); + /* Obtain child reference for new parent. */ + if (!get_device(&sch->dev)) + return -ENODEV; + + if (!sch_is_pseudo_sch(old_sch)) { + spin_lock_irq(old_sch->lock); + old_enabled = old_sch->schib.pmcw.ena; + rc = 0; + if (old_enabled) + rc = cio_disable_subchannel(old_sch); + spin_unlock_irq(old_sch->lock); + if (rc == -EBUSY) { + /* Release child reference for new parent. */ + put_device(&sch->dev); + return rc; + } + } - 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)); mutex_lock(&sch->reg_mutex); - /* Try to move the ccw device to its new subchannel. */ - rc = device_move(&cdev->dev, &sch->dev); + rc = device_move(&cdev->dev, &sch->dev, DPM_ORDER_PARENT_BEFORE_DEV); mutex_unlock(&sch->reg_mutex); if (rc) { - CIO_MSG_EVENT(2, "Moving device 0.%x.%04x to subchannel " - "0.%x.%04x failed (ret=%d)!\n", + CIO_MSG_EVENT(0, "device_move(0.%x.%04x,0.%x.%04x)=%d\n", cdev->private->dev_id.ssid, cdev->private->dev_id.devno, sch->schid.ssid, - sch->schid.sch_no, rc); - css_sch_device_unregister(sch); - goto out; + sch->schib.pmcw.dev, rc); + if (old_enabled) { + /* Try to reenable the old subchannel. */ + spin_lock_irq(old_sch->lock); + cio_enable_subchannel(old_sch, (u32)(addr_t)old_sch); + spin_unlock_irq(old_sch->lock); + } + /* Release child reference for new parent. */ + put_device(&sch->dev); + return rc; } - if (former_parent) { - spin_lock_irq(former_parent->lock); - sch_set_cdev(former_parent, NULL); - spin_unlock_irq(former_parent->lock); - css_sch_device_unregister(former_parent); - /* Reset intparm to zeroes. */ - former_parent->schib.pmcw.intparm = 0; - cio_modify(former_parent); + /* Clean up old subchannel. */ + if (!sch_is_pseudo_sch(old_sch)) { + spin_lock_irq(old_sch->lock); + sch_set_cdev(old_sch, NULL); + spin_unlock_irq(old_sch->lock); + css_schedule_eval(old_sch->schid); } - sch_attach_device(sch, cdev); -out: - if (former_parent) - put_device(&former_parent->dev); - put_device(&cdev->dev); + /* Release child reference for old parent. */ + put_device(&old_sch->dev); + /* Initialize new subchannel. */ + spin_lock_irq(sch->lock); + cdev->private->schid = sch->schid; + cdev->ccwlock = sch->lock; + if (!sch_is_pseudo_sch(sch)) + sch_set_cdev(sch, cdev); + spin_unlock_irq(sch->lock); + if (!sch_is_pseudo_sch(sch)) + css_update_ssd_info(sch); + return 0; +} + +static int ccw_device_move_to_orph(struct ccw_device *cdev) +{ + struct subchannel *sch = to_subchannel(cdev->dev.parent); + struct channel_subsystem *css = to_css(sch->dev.parent); + + return ccw_device_move_to_sch(cdev, css->pseudo_subchannel); } static void io_subchannel_irq(struct subchannel *sch) @@ -1112,121 +1027,121 @@ static void io_subchannel_irq(struct subchannel *sch) cdev = sch_get_cdev(sch); - CIO_TRACE_EVENT(3, "IRQ"); - CIO_TRACE_EVENT(3, sch->dev.bus_id); + CIO_TRACE_EVENT(6, "IRQ"); + CIO_TRACE_EVENT(6, dev_name(&sch->dev)); if (cdev) dev_fsm_event(cdev, DEV_EVENT_INTERRUPT); + else + inc_irq_stat(IRQIO_CIO); } -static int -io_subchannel_probe (struct subchannel *sch) +void io_subchannel_init_config(struct subchannel *sch) { + memset(&sch->config, 0, sizeof(sch->config)); + sch->config.csense = 1; +} + +static void io_subchannel_init_fields(struct subchannel *sch) +{ + if (cio_is_console(sch->schid)) + sch->opm = 0xff; + else + sch->opm = chp_get_sch_opm(sch); + sch->lpm = sch->schib.pmcw.pam & sch->opm; + sch->isc = cio_is_console(sch->schid) ? CONSOLE_ISC : IO_SCH_ISC; + + CIO_MSG_EVENT(6, "Detected device %04x on subchannel 0.%x.%04X" + " - PIM = %02X, PAM = %02X, POM = %02X\n", + sch->schib.pmcw.dev, sch->schid.ssid, + sch->schid.sch_no, sch->schib.pmcw.pim, + sch->schib.pmcw.pam, sch->schib.pmcw.pom); + + io_subchannel_init_config(sch); +} + +/* + * 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) +{ + struct io_subchannel_private *io_priv; struct ccw_device *cdev; int rc; - unsigned long flags; - struct ccw_dev_id dev_id; - cdev = sch_get_cdev(sch); - if (cdev) { - /* - * This subchannel already has an associated ccw_device. - * Register it and exit. This happens for all early - * device, e.g. the console. - */ - cdev->dev.groups = ccwdev_attr_groups; - device_initialize(&cdev->dev); - ccw_device_register(cdev); + if (cio_is_console(sch->schid)) { + rc = sysfs_create_group(&sch->dev.kobj, + &io_subchannel_attr_group); + if (rc) + CIO_MSG_EVENT(0, "Failed to create io subchannel " + "attributes for subchannel " + "0.%x.%04x (rc=%d)\n", + sch->schid.ssid, sch->schid.sch_no, rc); /* - * 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 console subchannel already has an associated ccw_device. + * Throw the delayed uevent for the subchannel, register + * the ccw_device and exit. */ - if (cdev->private->state != DEV_STATE_NOT_OPER && - cdev->private->state != DEV_STATE_OFFLINE && - cdev->private->state != DEV_STATE_BOXED) - get_device(&cdev->dev); + dev_set_uevent_suppress(&sch->dev, 0); + kobject_uevent(&sch->dev.kobj, KOBJ_ADD); + cdev = sch_get_cdev(sch); + rc = ccw_device_add(cdev); + if (rc) { + /* Release online reference. */ + put_device(&cdev->dev); + goto out_schedule; + } + if (atomic_dec_and_test(&ccw_device_init_count)) + wake_up(&ccw_device_init_wq); return 0; } - /* - * 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; + io_subchannel_init_fields(sch); + rc = cio_commit_config(sch); + if (rc) + goto out_schedule; + rc = sysfs_create_group(&sch->dev.kobj, + &io_subchannel_attr_group); + if (rc) + goto out_schedule; /* Allocate I/O subchannel private data. */ - sch->private = kzalloc(sizeof(struct io_subchannel_private), - GFP_KERNEL | GFP_DMA); - if (!sch->private) - return -ENOMEM; - cdev = get_disc_ccwdev_by_dev_id(&dev_id, NULL); - if (!cdev) - cdev = get_orphaned_ccwdev_by_dev_id(to_css(sch->dev.parent), - &dev_id); - if (cdev) { - /* - * Schedule moving the device until when we have a registered - * subchannel to move to and succeed the probe. We can - * unregister later again, when the probe is through. - */ - cdev->private->sch = sch; - PREPARE_WORK(&cdev->private->kick_work, - ccw_device_move_to_sch); - queue_work(slow_path_wq, &cdev->private->kick_work); - return 0; - } - cdev = io_subchannel_create_ccwdev(sch); - if (IS_ERR(cdev)) { - kfree(sch->private); - return PTR_ERR(cdev); - } - rc = io_subchannel_recog(cdev, sch); - if (rc) { - spin_lock_irqsave(sch->lock, flags); - sch_set_cdev(sch, NULL); - spin_unlock_irqrestore(sch->lock, flags); - if (cdev->dev.release) - cdev->dev.release(&cdev->dev); - kfree(sch->private); - } + io_priv = kzalloc(sizeof(*io_priv), GFP_KERNEL | GFP_DMA); + if (!io_priv) + goto out_schedule; - return rc; + set_io_private(sch, io_priv); + css_schedule_eval(sch->schid); + return 0; + +out_schedule: + spin_lock_irq(sch->lock); + css_sched_sch_todo(sch, SCH_TODO_UNREG); + spin_unlock_irq(sch->lock); + return 0; } static int io_subchannel_remove (struct subchannel *sch) { + struct io_subchannel_private *io_priv = to_io_private(sch); struct ccw_device *cdev; - unsigned long flags; cdev = sch_get_cdev(sch); if (!cdev) - return 0; + goto out_free; + io_subchannel_quiesce(sch); /* Set ccw device to not operational and drop reference. */ - spin_lock_irqsave(cdev->ccwlock, flags); + spin_lock_irq(cdev->ccwlock); sch_set_cdev(sch, NULL); + set_io_private(sch, NULL); cdev->private->state = DEV_STATE_NOT_OPER; - spin_unlock_irqrestore(cdev->ccwlock, flags); + spin_unlock_irq(cdev->ccwlock); ccw_device_unregister(cdev); - put_device(&cdev->dev); - kfree(sch->private); +out_free: + kfree(io_priv); + sysfs_remove_group(&sch->dev.kobj, &io_subchannel_attr_group); return 0; } -static int io_subchannel_notify(struct subchannel *sch, int event) -{ - struct ccw_device *cdev; - - cdev = sch_get_cdev(sch); - if (!cdev) - return 0; - if (!cdev->drv) - return 0; - if (!cdev->online) - return 0; - return cdev->drv->notify ? cdev->drv->notify(cdev, event) : 0; -} - static void io_subchannel_verify(struct subchannel *sch) { struct ccw_device *cdev; @@ -1236,121 +1151,545 @@ static void io_subchannel_verify(struct subchannel *sch) dev_fsm_event(cdev, DEV_EVENT_VERIFY); } -static void io_subchannel_ioterm(struct subchannel *sch) +static void io_subchannel_terminate_path(struct subchannel *sch, u8 mask) { struct ccw_device *cdev; cdev = sch_get_cdev(sch); if (!cdev) return; - /* Internal I/O will be retried by the interrupt handler. */ - if (cdev->private->flags.intretry) - return; - cdev->private->state = DEV_STATE_CLEAR_VERIFY; - if (cdev->handler) - cdev->handler(cdev, cdev->private->intparm, - ERR_PTR(-EIO)); + if (cio_update_schib(sch)) + goto err; + /* Check for I/O on path. */ + if (scsw_actl(&sch->schib.scsw) == 0 || sch->schib.pmcw.lpum != mask) + goto out; + if (cdev->private->state == DEV_STATE_ONLINE) { + ccw_device_kill_io(cdev); + goto out; + } + if (cio_clear(sch)) + goto err; +out: + /* Trigger path verification. */ + dev_fsm_event(cdev, DEV_EVENT_VERIFY); + return; + +err: + dev_fsm_event(cdev, DEV_EVENT_NOTOPER); } -static void -io_subchannel_shutdown(struct subchannel *sch) +static int io_subchannel_chp_event(struct subchannel *sch, + struct chp_link *link, int event) +{ + struct ccw_device *cdev = sch_get_cdev(sch); + int mask; + + mask = chp_ssd_get_mask(&sch->ssd_info, link); + if (!mask) + return 0; + switch (event) { + case CHP_VARY_OFF: + sch->opm &= ~mask; + sch->lpm &= ~mask; + if (cdev) + cdev->private->path_gone_mask |= mask; + io_subchannel_terminate_path(sch, mask); + break; + case CHP_VARY_ON: + sch->opm |= mask; + sch->lpm |= mask; + if (cdev) + cdev->private->path_new_mask |= mask; + io_subchannel_verify(sch); + break; + case CHP_OFFLINE: + if (cio_update_schib(sch)) + return -ENODEV; + if (cdev) + cdev->private->path_gone_mask |= mask; + io_subchannel_terminate_path(sch, mask); + break; + case CHP_ONLINE: + if (cio_update_schib(sch)) + return -ENODEV; + sch->lpm |= mask & sch->opm; + if (cdev) + cdev->private->path_new_mask |= mask; + io_subchannel_verify(sch); + break; + } + return 0; +} + +static void io_subchannel_quiesce(struct subchannel *sch) { struct ccw_device *cdev; int ret; + spin_lock_irq(sch->lock); cdev = sch_get_cdev(sch); - if (cio_is_console(sch->schid)) - return; + goto out_unlock; if (!sch->schib.pmcw.ena) - /* Nothing to do. */ - return; + goto out_unlock; ret = cio_disable_subchannel(sch); if (ret != -EBUSY) - /* Subchannel is disabled, we're done. */ - return; - cdev->private->state = DEV_STATE_QUIESCE; + goto out_unlock; if (cdev->handler) - cdev->handler(cdev, cdev->private->intparm, - ERR_PTR(-EIO)); - ret = ccw_device_cancel_halt_clear(cdev); - if (ret == -EBUSY) { - ccw_device_set_timeout(cdev, HZ/10); - wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev)); + cdev->handler(cdev, cdev->private->intparm, ERR_PTR(-EIO)); + while (ret == -EBUSY) { + cdev->private->state = DEV_STATE_QUIESCE; + cdev->private->iretry = 255; + ret = ccw_device_cancel_halt_clear(cdev); + if (ret == -EBUSY) { + ccw_device_set_timeout(cdev, HZ/10); + spin_unlock_irq(sch->lock); + wait_event(cdev->private->wait_q, + cdev->private->state != DEV_STATE_QUIESCE); + spin_lock_irq(sch->lock); + } + ret = cio_disable_subchannel(sch); + } +out_unlock: + spin_unlock_irq(sch->lock); +} + +static void io_subchannel_shutdown(struct subchannel *sch) +{ + io_subchannel_quiesce(sch); +} + +static int device_is_disconnected(struct ccw_device *cdev) +{ + if (!cdev) + return 0; + return (cdev->private->state == DEV_STATE_DISCONNECTED || + cdev->private->state == DEV_STATE_DISCONNECTED_SENSE_ID); +} + +static int recovery_check(struct device *dev, void *data) +{ + struct ccw_device *cdev = to_ccwdev(dev); + int *redo = data; + + spin_lock_irq(cdev->ccwlock); + switch (cdev->private->state) { + case DEV_STATE_DISCONNECTED: + CIO_MSG_EVENT(3, "recovery: trigger 0.%x.%04x\n", + cdev->private->dev_id.ssid, + cdev->private->dev_id.devno); + dev_fsm_event(cdev, DEV_EVENT_VERIFY); + *redo = 1; + break; + case DEV_STATE_DISCONNECTED_SENSE_ID: + *redo = 1; + break; } + spin_unlock_irq(cdev->ccwlock); + + return 0; +} + +static void recovery_work_func(struct work_struct *unused) +{ + int redo = 0; + + bus_for_each_dev(&ccw_bus_type, NULL, &redo, recovery_check); + if (redo) { + spin_lock_irq(&recovery_lock); + if (!timer_pending(&recovery_timer)) { + if (recovery_phase < ARRAY_SIZE(recovery_delay) - 1) + recovery_phase++; + mod_timer(&recovery_timer, jiffies + + recovery_delay[recovery_phase] * HZ); + } + spin_unlock_irq(&recovery_lock); + } else + CIO_MSG_EVENT(4, "recovery: end\n"); +} + +static DECLARE_WORK(recovery_work, recovery_work_func); + +static void recovery_func(unsigned long data) +{ + /* + * We can't do our recovery in softirq context and it's not + * performance critical, so we schedule it. + */ + schedule_work(&recovery_work); +} + +static void ccw_device_schedule_recovery(void) +{ + unsigned long flags; + + CIO_MSG_EVENT(4, "recovery: schedule\n"); + spin_lock_irqsave(&recovery_lock, flags); + if (!timer_pending(&recovery_timer) || (recovery_phase != 0)) { + recovery_phase = 0; + mod_timer(&recovery_timer, jiffies + recovery_delay[0] * HZ); + } + spin_unlock_irqrestore(&recovery_lock, flags); +} + +static int purge_fn(struct device *dev, void *data) +{ + struct ccw_device *cdev = to_ccwdev(dev); + struct ccw_dev_id *id = &cdev->private->dev_id; + + spin_lock_irq(cdev->ccwlock); + if (is_blacklisted(id->ssid, id->devno) && + (cdev->private->state == DEV_STATE_OFFLINE) && + (atomic_cmpxchg(&cdev->private->onoff, 0, 1) == 0)) { + CIO_MSG_EVENT(3, "ccw: purging 0.%x.%04x\n", id->ssid, + id->devno); + ccw_device_sched_todo(cdev, CDEV_TODO_UNREG); + atomic_set(&cdev->private->onoff, 0); + } + spin_unlock_irq(cdev->ccwlock); + /* Abort loop in case of pending signal. */ + if (signal_pending(current)) + return -EINTR; + + return 0; +} + +/** + * ccw_purge_blacklisted - purge unused, blacklisted devices + * + * Unregister all ccw devices that are offline and on the blacklist. + */ +int ccw_purge_blacklisted(void) +{ + CIO_MSG_EVENT(2, "ccw: purging blacklisted devices\n"); + bus_for_each_dev(&ccw_bus_type, NULL, NULL, purge_fn); + return 0; +} + +void ccw_device_set_disconnected(struct ccw_device *cdev) +{ + if (!cdev) + return; + ccw_device_set_timeout(cdev, 0); + cdev->private->flags.fake_irb = 0; + cdev->private->state = DEV_STATE_DISCONNECTED; + if (cdev->online) + ccw_device_schedule_recovery(); +} + +void ccw_device_set_notoper(struct ccw_device *cdev) +{ + struct subchannel *sch = to_subchannel(cdev->dev.parent); + + CIO_TRACE_EVENT(2, "notoper"); + CIO_TRACE_EVENT(2, dev_name(&sch->dev)); + ccw_device_set_timeout(cdev, 0); cio_disable_subchannel(sch); + cdev->private->state = DEV_STATE_NOT_OPER; } -#ifdef CONFIG_CCW_CONSOLE -static struct ccw_device console_cdev; -static struct ccw_device_private console_private; -static int console_cdev_in_use; +enum io_sch_action { + IO_SCH_UNREG, + IO_SCH_ORPH_UNREG, + IO_SCH_ATTACH, + IO_SCH_UNREG_ATTACH, + IO_SCH_ORPH_ATTACH, + IO_SCH_REPROBE, + IO_SCH_VERIFY, + IO_SCH_DISC, + IO_SCH_NOP, +}; -static DEFINE_SPINLOCK(ccw_console_lock); +static enum io_sch_action sch_get_action(struct subchannel *sch) +{ + struct ccw_device *cdev; -spinlock_t * cio_get_console_lock(void) + cdev = sch_get_cdev(sch); + if (cio_update_schib(sch)) { + /* Not operational. */ + if (!cdev) + return IO_SCH_UNREG; + if (ccw_device_notify(cdev, CIO_GONE) != NOTIFY_OK) + return IO_SCH_UNREG; + return IO_SCH_ORPH_UNREG; + } + /* Operational. */ + if (!cdev) + return IO_SCH_ATTACH; + if (sch->schib.pmcw.dev != cdev->private->dev_id.devno) { + 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) != NOTIFY_OK) + return IO_SCH_UNREG; + return IO_SCH_DISC; + } + if (device_is_disconnected(cdev)) + return IO_SCH_REPROBE; + if (cdev->online && !cdev->private->flags.resuming) + return IO_SCH_VERIFY; + if (cdev->private->state == DEV_STATE_NOT_OPER) + return IO_SCH_UNREG_ATTACH; + return IO_SCH_NOP; +} + +/** + * io_subchannel_sch_event - process subchannel event + * @sch: subchannel + * @process: non-zero if function is called in process context + * + * An unspecified event occurred for this subchannel. Adjust data according + * to the current operational state of the subchannel and device. Return + * zero when the event has been handled sufficiently or -EAGAIN when this + * function should be called again in process context. + */ +static int io_subchannel_sch_event(struct subchannel *sch, int process) { - return &ccw_console_lock; + unsigned long flags; + struct ccw_device *cdev; + struct ccw_dev_id dev_id; + enum io_sch_action action; + int rc = -EAGAIN; + + spin_lock_irqsave(sch->lock, flags); + if (!device_is_registered(&sch->dev)) + goto out_unlock; + if (work_pending(&sch->todo_work)) + goto out_unlock; + cdev = sch_get_cdev(sch); + if (cdev && work_pending(&cdev->private->todo_work)) + goto out_unlock; + action = sch_get_action(sch); + CIO_MSG_EVENT(2, "event: sch 0.%x.%04x, process=%d, action=%d\n", + sch->schid.ssid, sch->schid.sch_no, process, + action); + /* Perform immediate actions while holding the lock. */ + switch (action) { + case IO_SCH_REPROBE: + /* Trigger device recognition. */ + ccw_device_trigger_reprobe(cdev); + rc = 0; + goto out_unlock; + case IO_SCH_VERIFY: + /* Trigger path verification. */ + io_subchannel_verify(sch); + rc = 0; + goto out_unlock; + case IO_SCH_DISC: + ccw_device_set_disconnected(cdev); + rc = 0; + goto out_unlock; + case IO_SCH_ORPH_UNREG: + case IO_SCH_ORPH_ATTACH: + ccw_device_set_disconnected(cdev); + break; + case IO_SCH_UNREG_ATTACH: + case IO_SCH_UNREG: + if (!cdev) + break; + if (cdev->private->state == DEV_STATE_SENSE_ID) { + /* + * Note: delayed work triggered by this event + * and repeated calls to sch_event are synchronized + * by the above check for work_pending(cdev). + */ + dev_fsm_event(cdev, DEV_EVENT_NOTOPER); + } else + ccw_device_set_notoper(cdev); + break; + case IO_SCH_NOP: + rc = 0; + goto out_unlock; + default: + break; + } + spin_unlock_irqrestore(sch->lock, flags); + /* All other actions require process context. */ + if (!process) + goto out; + /* Handle attached ccw device. */ + switch (action) { + case IO_SCH_ORPH_UNREG: + case IO_SCH_ORPH_ATTACH: + /* Move ccw device to orphanage. */ + rc = ccw_device_move_to_orph(cdev); + if (rc) + goto out; + break; + case IO_SCH_UNREG_ATTACH: + spin_lock_irqsave(sch->lock, flags); + if (cdev->private->flags.resuming) { + /* Device will be handled later. */ + rc = 0; + goto out_unlock; + } + sch_set_cdev(sch, NULL); + spin_unlock_irqrestore(sch->lock, flags); + /* Unregister ccw device. */ + ccw_device_unregister(cdev); + break; + default: + break; + } + /* Handle subchannel. */ + switch (action) { + case IO_SCH_ORPH_UNREG: + case IO_SCH_UNREG: + if (!cdev || !cdev->private->flags.resuming) + css_sch_device_unregister(sch); + break; + case IO_SCH_ORPH_ATTACH: + case IO_SCH_UNREG_ATTACH: + case IO_SCH_ATTACH: + dev_id.ssid = sch->schid.ssid; + dev_id.devno = sch->schib.pmcw.dev; + cdev = get_ccwdev_by_dev_id(&dev_id); + if (!cdev) { + sch_create_and_recog_new_device(sch); + break; + } + rc = ccw_device_move_to_sch(cdev, sch); + if (rc) { + /* Release reference from get_ccwdev_by_dev_id() */ + put_device(&cdev->dev); + goto out; + } + spin_lock_irqsave(sch->lock, flags); + ccw_device_trigger_reprobe(cdev); + spin_unlock_irqrestore(sch->lock, flags); + /* Release reference from get_ccwdev_by_dev_id() */ + put_device(&cdev->dev); + break; + default: + break; + } + return 0; + +out_unlock: + spin_unlock_irqrestore(sch->lock, flags); +out: + return rc; } -static int -ccw_device_console_enable (struct ccw_device *cdev, struct subchannel *sch) +static void ccw_device_set_int_class(struct ccw_device *cdev) { + struct ccw_driver *cdrv = cdev->drv; + + /* Note: we interpret class 0 in this context as an uninitialized + * field since it translates to a non-I/O interrupt class. */ + if (cdrv->int_class != 0) + cdev->private->int_class = cdrv->int_class; + else + cdev->private->int_class = IRQIO_CIO; +} + +#ifdef CONFIG_CCW_CONSOLE +int __init ccw_device_enable_console(struct ccw_device *cdev) +{ + struct subchannel *sch = to_subchannel(cdev->dev.parent); int rc; - /* Attach subchannel private data. */ - sch->private = cio_get_console_priv(); - memset(sch->private, 0, sizeof(struct io_subchannel_private)); - /* Initialize the ccw_device structure. */ - cdev->dev.parent= &sch->dev; - rc = io_subchannel_recog(cdev, sch); + if (!cdev->drv || !cdev->handler) + return -EINVAL; + + io_subchannel_init_fields(sch); + rc = cio_commit_config(sch); if (rc) return rc; - + sch->driver = &io_subchannel_driver; + io_subchannel_recog(cdev, sch); /* Now wait for the async. recognition to come to an end. */ spin_lock_irq(cdev->ccwlock); while (!dev_fsm_final_state(cdev)) - wait_cons_dev(); - rc = -EIO; - if (cdev->private->state != DEV_STATE_OFFLINE) + ccw_device_wait_idle(cdev); + + /* Hold on to an extra reference while device is online. */ + get_device(&cdev->dev); + rc = ccw_device_online(cdev); + if (rc) goto out_unlock; - ccw_device_online(cdev); + while (!dev_fsm_final_state(cdev)) - wait_cons_dev(); - if (cdev->private->state != DEV_STATE_ONLINE) - goto out_unlock; - rc = 0; + ccw_device_wait_idle(cdev); + + if (cdev->private->state == DEV_STATE_ONLINE) + cdev->online = 1; + else + rc = -EIO; out_unlock: spin_unlock_irq(cdev->ccwlock); - return 0; + if (rc) /* Give up online reference since onlining failed. */ + put_device(&cdev->dev); + return rc; } -struct ccw_device * -ccw_device_probe_console(void) +struct ccw_device * __init ccw_device_create_console(struct ccw_driver *drv) { + struct io_subchannel_private *io_priv; + struct ccw_device *cdev; struct subchannel *sch; - int ret; - if (xchg(&console_cdev_in_use, 1) != 0) - return ERR_PTR(-EBUSY); sch = cio_probe_console(); - if (IS_ERR(sch)) { - console_cdev_in_use = 0; - return (void *) sch; + if (IS_ERR(sch)) + return ERR_CAST(sch); + + io_priv = kzalloc(sizeof(*io_priv), GFP_KERNEL | GFP_DMA); + if (!io_priv) { + put_device(&sch->dev); + return ERR_PTR(-ENOMEM); } - memset(&console_cdev, 0, sizeof(struct ccw_device)); - memset(&console_private, 0, sizeof(struct ccw_device_private)); - console_cdev.private = &console_private; - console_private.cdev = &console_cdev; - ret = ccw_device_console_enable(&console_cdev, sch); - if (ret) { - cio_release_console(); - console_cdev_in_use = 0; - return ERR_PTR(ret); + set_io_private(sch, io_priv); + cdev = io_subchannel_create_ccwdev(sch); + if (IS_ERR(cdev)) { + put_device(&sch->dev); + kfree(io_priv); + return cdev; } - console_cdev.online = 1; - return &console_cdev; + cdev->drv = drv; + ccw_device_set_int_class(cdev); + return cdev; +} + +void __init ccw_device_destroy_console(struct ccw_device *cdev) +{ + struct subchannel *sch = to_subchannel(cdev->dev.parent); + struct io_subchannel_private *io_priv = to_io_private(sch); + + set_io_private(sch, NULL); + put_device(&sch->dev); + put_device(&cdev->dev); + kfree(io_priv); } + +/** + * ccw_device_wait_idle() - busy wait for device to become idle + * @cdev: ccw device + * + * Poll until activity control is zero, that is, no function or data + * transfer is pending/active. + * Called with device lock being held. + */ +void ccw_device_wait_idle(struct ccw_device *cdev) +{ + struct subchannel *sch = to_subchannel(cdev->dev.parent); + + while (1) { + cio_tsch(sch); + if (sch->schib.scsw.cmd.actl == 0) + break; + udelay_simple(100); + } +} + +static int ccw_device_pm_restore(struct device *dev); + +int ccw_device_force_console(struct ccw_device *cdev) +{ + return ccw_device_pm_restore(&cdev->dev); +} +EXPORT_SYMBOL_GPL(ccw_device_force_console); #endif /* @@ -1363,7 +1702,7 @@ __ccwdev_check_busid(struct device *dev, void *id) bus_id = id; - return (strncmp(bus_id, dev->bus_id, BUS_ID_SIZE) == 0); + return (strcmp(bus_id, dev_name(dev)) == 0); } @@ -1382,15 +1721,9 @@ struct ccw_device *get_ccwdev_by_busid(struct ccw_driver *cdrv, const char *bus_id) { struct device *dev; - struct device_driver *drv; - - drv = get_driver(&cdrv->driver); - if (!drv) - return NULL; - dev = driver_find_device(drv, NULL, (void *)bus_id, + dev = driver_find_device(&cdrv->driver, NULL, (void *)bus_id, __ccwdev_check_busid); - put_driver(drv); return dev ? to_ccwdev(dev) : NULL; } @@ -1413,19 +1746,18 @@ ccw_device_probe (struct device *dev) int ret; cdev->drv = cdrv; /* to let the driver call _set_online */ - + ccw_device_set_int_class(cdev); ret = cdrv->probe ? cdrv->probe(cdev) : -ENODEV; - if (ret) { cdev->drv = NULL; + cdev->private->int_class = IRQIO_CIO; return ret; } return 0; } -static int -ccw_device_remove (struct device *dev) +static int ccw_device_remove(struct device *dev) { struct ccw_device *cdev = to_ccwdev(dev); struct ccw_driver *cdrv = cdev->drv; @@ -1433,23 +1765,28 @@ ccw_device_remove (struct device *dev) if (cdrv->remove) cdrv->remove(cdev); + + spin_lock_irq(cdev->ccwlock); if (cdev->online) { cdev->online = 0; - spin_lock_irq(cdev->ccwlock); ret = ccw_device_offline(cdev); spin_unlock_irq(cdev->ccwlock); if (ret == 0) wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev)); else - //FIXME: we can't fail! - CIO_MSG_EVENT(2, "ccw_device_offline returned %d, " + CIO_MSG_EVENT(0, "ccw_device_offline returned %d, " "device 0.%x.%04x\n", ret, cdev->private->dev_id.ssid, cdev->private->dev_id.devno); + /* Give up reference obtained in ccw_device_set_online(). */ + put_device(&cdev->dev); + spin_lock_irq(cdev->ccwlock); } ccw_device_set_timeout(cdev, 0); cdev->drv = NULL; + cdev->private->int_class = IRQIO_CIO; + spin_unlock_irq(cdev->ccwlock); return 0; } @@ -1463,13 +1800,243 @@ static void ccw_device_shutdown(struct device *dev) disable_cmf(cdev); } -struct bus_type ccw_bus_type = { +static int ccw_device_pm_prepare(struct device *dev) +{ + struct ccw_device *cdev = to_ccwdev(dev); + + if (work_pending(&cdev->private->todo_work)) + return -EAGAIN; + /* Fail while device is being set online/offline. */ + if (atomic_read(&cdev->private->onoff)) + return -EAGAIN; + + if (cdev->online && cdev->drv && cdev->drv->prepare) + return cdev->drv->prepare(cdev); + + return 0; +} + +static void ccw_device_pm_complete(struct device *dev) +{ + struct ccw_device *cdev = to_ccwdev(dev); + + if (cdev->online && cdev->drv && cdev->drv->complete) + cdev->drv->complete(cdev); +} + +static int ccw_device_pm_freeze(struct device *dev) +{ + struct ccw_device *cdev = to_ccwdev(dev); + struct subchannel *sch = to_subchannel(cdev->dev.parent); + int ret, cm_enabled; + + /* Fail suspend while device is in transistional state. */ + if (!dev_fsm_final_state(cdev)) + return -EAGAIN; + if (!cdev->online) + return 0; + if (cdev->drv && cdev->drv->freeze) { + ret = cdev->drv->freeze(cdev); + if (ret) + return ret; + } + + spin_lock_irq(sch->lock); + cm_enabled = cdev->private->cmb != NULL; + spin_unlock_irq(sch->lock); + if (cm_enabled) { + /* Don't have the css write on memory. */ + ret = ccw_set_cmf(cdev, 0); + if (ret) + return ret; + } + /* From here on, disallow device driver I/O. */ + spin_lock_irq(sch->lock); + ret = cio_disable_subchannel(sch); + spin_unlock_irq(sch->lock); + + return ret; +} + +static int ccw_device_pm_thaw(struct device *dev) +{ + struct ccw_device *cdev = to_ccwdev(dev); + struct subchannel *sch = to_subchannel(cdev->dev.parent); + int ret, cm_enabled; + + if (!cdev->online) + return 0; + + spin_lock_irq(sch->lock); + /* Allow device driver I/O again. */ + ret = cio_enable_subchannel(sch, (u32)(addr_t)sch); + cm_enabled = cdev->private->cmb != NULL; + spin_unlock_irq(sch->lock); + if (ret) + return ret; + + if (cm_enabled) { + ret = ccw_set_cmf(cdev, 1); + if (ret) + return ret; + } + + if (cdev->drv && cdev->drv->thaw) + ret = cdev->drv->thaw(cdev); + + return ret; +} + +static void __ccw_device_pm_restore(struct ccw_device *cdev) +{ + struct subchannel *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); + goto out_unlock; + } + /* + * While we were sleeping, devices may have gone or become + * available again. Kick re-detection. + */ + cdev->private->flags.resuming = 1; + cdev->private->path_new_mask = LPM_ANYPATH; + css_sched_sch_todo(sch, SCH_TODO_EVAL); + spin_unlock_irq(sch->lock); + css_wait_for_slow_path(); + + /* 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); + 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) == NOTIFY_OK) + return 0; + ccw_device_sched_todo(cdev, CDEV_TODO_UNREG); + return -ENODEV; +} + +static int resume_handle_disc(struct ccw_device *cdev) +{ + cdev->private->state = DEV_STATE_DISCONNECTED; + if (ccw_device_notify(cdev, CIO_GONE) == NOTIFY_OK) + return 0; + ccw_device_sched_todo(cdev, CDEV_TODO_UNREG); + return -ENODEV; +} + +static int ccw_device_pm_restore(struct device *dev) +{ + struct ccw_device *cdev = to_ccwdev(dev); + 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)) + goto out_restore; + + /* 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); + if (ret) + goto out_unlock; + goto out_restore; + default: + 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)) { + ccw_device_update_sense_data(cdev); + ccw_device_sched_todo(cdev, CDEV_TODO_REBIND); + ret = -ENODEV; + goto out_unlock; + } + if (!cdev->online) + goto out_unlock; + + 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)); + 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; + } + + /* 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, + cdev->private->dev_id.devno, ret); + ret = 0; + } + } + +out_restore: + spin_unlock_irq(sch->lock); + if (cdev->online && cdev->drv && cdev->drv->restore) + ret = cdev->drv->restore(cdev); + return ret; + +out_unlock: + spin_unlock_irq(sch->lock); + return ret; +} + +static const struct dev_pm_ops ccw_pm_ops = { + .prepare = ccw_device_pm_prepare, + .complete = ccw_device_pm_complete, + .freeze = ccw_device_pm_freeze, + .thaw = ccw_device_pm_thaw, + .restore = ccw_device_pm_restore, +}; + +static struct bus_type ccw_bus_type = { .name = "ccw", .match = ccw_bus_match, .uevent = ccw_uevent, .probe = ccw_device_probe, .remove = ccw_device_remove, .shutdown = ccw_device_shutdown, + .pm = &ccw_pm_ops, }; /** @@ -1485,8 +2052,6 @@ int ccw_driver_register(struct ccw_driver *cdriver) struct device_driver *drv = &cdriver->driver; drv->bus = &ccw_bus_type; - drv->name = cdriver->name; - drv->owner = cdriver->owner; return driver_register(drv); } @@ -1502,80 +2067,91 @@ void ccw_driver_unregister(struct ccw_driver *cdriver) driver_unregister(&cdriver->driver); } -/* Helper func for qdio. */ -struct subchannel_id -ccw_device_get_subchannel_id(struct ccw_device *cdev) +static void ccw_device_todo(struct work_struct *work) { + struct ccw_device_private *priv; + struct ccw_device *cdev; struct subchannel *sch; + enum cdev_todo todo; + priv = container_of(work, struct ccw_device_private, todo_work); + cdev = priv->cdev; sch = to_subchannel(cdev->dev.parent); - return sch->schid; -} - -static int recovery_check(struct device *dev, void *data) -{ - struct ccw_device *cdev = to_ccwdev(dev); - int *redo = data; - + /* Find out todo. */ spin_lock_irq(cdev->ccwlock); - switch (cdev->private->state) { - case DEV_STATE_DISCONNECTED: - CIO_MSG_EVENT(3, "recovery: trigger 0.%x.%04x\n", - cdev->private->dev_id.ssid, - cdev->private->dev_id.devno); - dev_fsm_event(cdev, DEV_EVENT_VERIFY); - *redo = 1; + todo = priv->todo; + priv->todo = CDEV_TODO_NOTHING; + CIO_MSG_EVENT(4, "cdev_todo: cdev=0.%x.%04x todo=%d\n", + priv->dev_id.ssid, priv->dev_id.devno, todo); + spin_unlock_irq(cdev->ccwlock); + /* Perform todo. */ + switch (todo) { + case CDEV_TODO_ENABLE_CMF: + cmf_reenable(cdev); break; - case DEV_STATE_DISCONNECTED_SENSE_ID: - *redo = 1; + case CDEV_TODO_REBIND: + ccw_device_do_unbind_bind(cdev); + break; + case CDEV_TODO_REGISTER: + io_subchannel_register(cdev); + break; + case CDEV_TODO_UNREG_EVAL: + if (!sch_is_pseudo_sch(sch)) + css_schedule_eval(sch->schid); + /* fall-through */ + case CDEV_TODO_UNREG: + if (sch_is_pseudo_sch(sch)) + ccw_device_unregister(cdev); + else + ccw_device_call_sch_unregister(cdev); + break; + default: break; } - spin_unlock_irq(cdev->ccwlock); - - return 0; -} - -static void recovery_work_func(struct work_struct *unused) -{ - int redo = 0; - - bus_for_each_dev(&ccw_bus_type, NULL, &redo, recovery_check); - if (redo) { - spin_lock_irq(&recovery_lock); - if (!timer_pending(&recovery_timer)) { - if (recovery_phase < ARRAY_SIZE(recovery_delay) - 1) - recovery_phase++; - mod_timer(&recovery_timer, jiffies + - recovery_delay[recovery_phase] * HZ); - } - spin_unlock_irq(&recovery_lock); - } else - CIO_MSG_EVENT(2, "recovery: end\n"); + /* Release workqueue ref. */ + put_device(&cdev->dev); } -static DECLARE_WORK(recovery_work, recovery_work_func); - -static void recovery_func(unsigned long data) +/** + * ccw_device_sched_todo - schedule ccw device operation + * @cdev: ccw device + * @todo: todo + * + * Schedule the operation identified by @todo to be performed on the slow path + * workqueue. Do nothing if another operation with higher priority is already + * scheduled. Needs to be called with ccwdev lock held. + */ +void ccw_device_sched_todo(struct ccw_device *cdev, enum cdev_todo todo) { - /* - * We can't do our recovery in softirq context and it's not - * performance critical, so we schedule it. - */ - schedule_work(&recovery_work); + CIO_MSG_EVENT(4, "cdev_todo: sched cdev=0.%x.%04x todo=%d\n", + cdev->private->dev_id.ssid, cdev->private->dev_id.devno, + todo); + if (cdev->private->todo >= todo) + return; + cdev->private->todo = todo; + /* Get workqueue ref. */ + if (!get_device(&cdev->dev)) + return; + if (!queue_work(cio_work_q, &cdev->private->todo_work)) { + /* Already queued, release workqueue ref. */ + put_device(&cdev->dev); + } } -void ccw_device_schedule_recovery(void) +/** + * ccw_device_siosl() - initiate logging + * @cdev: ccw device + * + * This function is used to invoke model-dependent logging within the channel + * subsystem. + */ +int ccw_device_siosl(struct ccw_device *cdev) { - unsigned long flags; + struct subchannel *sch = to_subchannel(cdev->dev.parent); - CIO_MSG_EVENT(2, "recovery: schedule\n"); - spin_lock_irqsave(&recovery_lock, flags); - if (!timer_pending(&recovery_timer) || (recovery_phase != 0)) { - recovery_phase = 0; - mod_timer(&recovery_timer, jiffies + recovery_delay[0] * HZ); - } - spin_unlock_irqrestore(&recovery_lock, flags); + return chsc_siosl(sch->schid); } +EXPORT_SYMBOL_GPL(ccw_device_siosl); MODULE_LICENSE("GPL"); EXPORT_SYMBOL(ccw_device_set_online); @@ -1583,7 +2159,3 @@ EXPORT_SYMBOL(ccw_device_set_offline); 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(ccw_device_notify_work); -EXPORT_SYMBOL_GPL(ccw_device_get_subchannel_id); |
