diff options
Diffstat (limited to 'drivers/s390/cio/device_fsm.c')
| -rw-r--r-- | drivers/s390/cio/device_fsm.c | 1131 |
1 files changed, 499 insertions, 632 deletions
diff --git a/drivers/s390/cio/device_fsm.c b/drivers/s390/cio/device_fsm.c index 9b7f6f548b1..0bc902b3cd8 100644 --- a/drivers/s390/cio/device_fsm.c +++ b/drivers/s390/cio/device_fsm.c @@ -1,19 +1,19 @@ /* - * drivers/s390/cio/device_fsm.c * finite state machine for device handling * - * Copyright (C) 2002 IBM Deutschland Entwicklung GmbH, - * IBM Corporation - * Author(s): Cornelia Huck(cohuck@de.ibm.com) + * Copyright IBM Corp. 2002, 2008 + * Author(s): Cornelia Huck (cornelia.huck@de.ibm.com) * Martin Schwidefsky (schwidefsky@de.ibm.com) */ #include <linux/module.h> -#include <linux/config.h> #include <linux/init.h> +#include <linux/jiffies.h> +#include <linux/string.h> #include <asm/ccwdev.h> -#include <asm/qdio.h> +#include <asm/cio.h> +#include <asm/chpid.h> #include "cio.h" #include "cio_debug.h" @@ -21,54 +21,71 @@ #include "device.h" #include "chsc.h" #include "ioasm.h" -#include "qdio.h" +#include "chp.h" -int -device_is_online(struct subchannel *sch) -{ - struct ccw_device *cdev; - - if (!sch->dev.driver_data) - return 0; - cdev = sch->dev.driver_data; - return (cdev->private->state == DEV_STATE_ONLINE); -} +static int timeout_log_enabled; -int -device_is_disconnected(struct subchannel *sch) +static int __init ccw_timeout_log_setup(char *unused) { - struct ccw_device *cdev; - - if (!sch->dev.driver_data) - return 0; - cdev = sch->dev.driver_data; - return (cdev->private->state == DEV_STATE_DISCONNECTED || - cdev->private->state == DEV_STATE_DISCONNECTED_SENSE_ID); + timeout_log_enabled = 1; + return 1; } -void -device_set_disconnected(struct subchannel *sch) -{ - struct ccw_device *cdev; - - if (!sch->dev.driver_data) - return; - cdev = sch->dev.driver_data; - ccw_device_set_timeout(cdev, 0); - cdev->private->flags.fake_irb = 0; - cdev->private->state = DEV_STATE_DISCONNECTED; -} +__setup("ccw_timeout_log", ccw_timeout_log_setup); -void -device_set_waiting(struct subchannel *sch) +static void ccw_timeout_log(struct ccw_device *cdev) { - struct ccw_device *cdev; + struct schib schib; + struct subchannel *sch; + struct io_subchannel_private *private; + union orb *orb; + int cc; - if (!sch->dev.driver_data) - return; - cdev = sch->dev.driver_data; - ccw_device_set_timeout(cdev, 10*HZ); - cdev->private->state = DEV_STATE_WAIT4IO; + sch = to_subchannel(cdev->dev.parent); + private = to_io_private(sch); + orb = &private->orb; + cc = stsch_err(sch->schid, &schib); + + printk(KERN_WARNING "cio: ccw device timeout occurred at %llx, " + "device information:\n", get_tod_clock()); + printk(KERN_WARNING "cio: orb:\n"); + print_hex_dump(KERN_WARNING, "cio: ", DUMP_PREFIX_NONE, 16, 1, + orb, sizeof(*orb), 0); + printk(KERN_WARNING "cio: ccw device bus id: %s\n", + dev_name(&cdev->dev)); + printk(KERN_WARNING "cio: subchannel bus id: %s\n", + dev_name(&sch->dev)); + printk(KERN_WARNING "cio: subchannel lpm: %02x, opm: %02x, " + "vpm: %02x\n", sch->lpm, sch->opm, sch->vpm); + + if (orb->tm.b) { + printk(KERN_WARNING "cio: orb indicates transport mode\n"); + printk(KERN_WARNING "cio: last tcw:\n"); + print_hex_dump(KERN_WARNING, "cio: ", DUMP_PREFIX_NONE, 16, 1, + (void *)(addr_t)orb->tm.tcw, + sizeof(struct tcw), 0); + } else { + printk(KERN_WARNING "cio: orb indicates command mode\n"); + if ((void *)(addr_t)orb->cmd.cpa == &private->sense_ccw || + (void *)(addr_t)orb->cmd.cpa == cdev->private->iccws) + printk(KERN_WARNING "cio: last channel program " + "(intern):\n"); + else + printk(KERN_WARNING "cio: last channel program:\n"); + + print_hex_dump(KERN_WARNING, "cio: ", DUMP_PREFIX_NONE, 16, 1, + (void *)(addr_t)orb->cmd.cpa, + sizeof(struct ccw1), 0); + } + printk(KERN_WARNING "cio: ccw device state: %d\n", + cdev->private->state); + printk(KERN_WARNING "cio: store subchannel returned: cc=%d\n", cc); + printk(KERN_WARNING "cio: schib:\n"); + print_hex_dump(KERN_WARNING, "cio: ", DUMP_PREFIX_NONE, 16, 1, + &schib, sizeof(schib), 0); + printk(KERN_WARNING "cio: ccw device flags:\n"); + print_hex_dump(KERN_WARNING, "cio: ", DUMP_PREFIX_NONE, 16, 1, + &cdev->private->flags, sizeof(cdev->private->flags), 0); } /* @@ -81,6 +98,8 @@ ccw_device_timeout(unsigned long data) cdev = (struct ccw_device *) data; spin_lock_irq(cdev->ccwlock); + if (timeout_log_enabled) + ccw_timeout_log(cdev); dev_fsm_event(cdev, DEV_EVENT_TIMEOUT); spin_unlock_irq(cdev->ccwlock); } @@ -105,18 +124,6 @@ ccw_device_set_timeout(struct ccw_device *cdev, int expires) add_timer(&cdev->private->timer); } -/* Kill any pending timers after machine check. */ -void -device_kill_pending_timer(struct subchannel *sch) -{ - struct ccw_device *cdev; - - if (!sch->dev.driver_data) - return; - cdev = sch->dev.driver_data; - ccw_device_set_timeout(cdev, 0); -} - /* * Cancel running i/o. This is called repeatedly since halt/clear are * asynchronous operations. We do one try with cio_cancel, two tries @@ -132,27 +139,30 @@ ccw_device_cancel_halt_clear(struct ccw_device *cdev) int ret; sch = to_subchannel(cdev->dev.parent); - ret = stsch(sch->irq, &sch->schib); - if (ret || !sch->schib.pmcw.dnv) + if (cio_update_schib(sch)) return -ENODEV; - if (!sch->schib.pmcw.ena || sch->schib.scsw.actl == 0) - /* Not operational or no activity -> done. */ + if (!sch->schib.pmcw.ena) + /* Not operational -> done. */ return 0; /* Stage 1: cancel io. */ - if (!(sch->schib.scsw.actl & SCSW_ACTL_HALT_PEND) && - !(sch->schib.scsw.actl & SCSW_ACTL_CLEAR_PEND)) { - ret = cio_cancel(sch); - if (ret != -EINVAL) - return ret; - /* cancel io unsuccessful. From now on it is asynchronous. */ + if (!(scsw_actl(&sch->schib.scsw) & SCSW_ACTL_HALT_PEND) && + !(scsw_actl(&sch->schib.scsw) & SCSW_ACTL_CLEAR_PEND)) { + if (!scsw_is_tm(&sch->schib.scsw)) { + ret = cio_cancel(sch); + if (ret != -EINVAL) + return ret; + } + /* cancel io unsuccessful or not applicable (transport mode). + * Continue with asynchronous instructions. */ cdev->private->iretry = 3; /* 3 halt retries. */ } - if (!(sch->schib.scsw.actl & SCSW_ACTL_CLEAR_PEND)) { + if (!(scsw_actl(&sch->schib.scsw) & SCSW_ACTL_CLEAR_PEND)) { /* Stage 2: halt io. */ if (cdev->private->iretry) { cdev->private->iretry--; ret = cio_halt(sch); - return (ret == 0) ? -EBUSY : ret; + if (ret != -EBUSY) + return (ret == 0) ? -EBUSY : ret; } /* halt io unsuccessful. */ cdev->private->iretry = 255; /* 255 clear retries. */ @@ -163,33 +173,27 @@ ccw_device_cancel_halt_clear(struct ccw_device *cdev) ret = cio_clear (sch); return (ret == 0) ? -EBUSY : ret; } - panic("Can't stop i/o on subchannel.\n"); + /* Function was unsuccessful */ + CIO_MSG_EVENT(0, "0.%x.%04x: could not stop I/O\n", + cdev->private->dev_id.ssid, cdev->private->dev_id.devno); + return -EIO; } -static int -ccw_device_handle_oper(struct ccw_device *cdev) +void ccw_device_update_sense_data(struct ccw_device *cdev) { - struct subchannel *sch; + memset(&cdev->id, 0, sizeof(cdev->id)); + cdev->id.cu_type = cdev->private->senseid.cu_type; + cdev->id.cu_model = cdev->private->senseid.cu_model; + cdev->id.dev_type = cdev->private->senseid.dev_type; + cdev->id.dev_model = cdev->private->senseid.dev_model; +} - sch = to_subchannel(cdev->dev.parent); - cdev->private->flags.recog_done = 1; - /* - * Check if cu type and device type still match. If - * not, it is certainly another device and we have to - * de- and re-register. Also check here for non-matching devno. - */ - if (cdev->id.cu_type != cdev->private->senseid.cu_type || - cdev->id.cu_model != cdev->private->senseid.cu_model || - cdev->id.dev_type != cdev->private->senseid.dev_type || - cdev->id.dev_model != cdev->private->senseid.dev_model || - cdev->private->devno != sch->schib.pmcw.dev) { - PREPARE_WORK(&cdev->private->kick_work, - ccw_device_do_unreg_rereg, (void *)cdev); - queue_work(ccw_device_work, &cdev->private->kick_work); - return 0; - } - cdev->private->flags.donotify = 1; - return 1; +int ccw_device_test_sense_data(struct ccw_device *cdev) +{ + return cdev->id.cu_type == cdev->private->senseid.cu_type && + cdev->id.cu_model == cdev->private->senseid.cu_model && + cdev->id.dev_type == cdev->private->senseid.dev_type && + cdev->id.dev_model == cdev->private->senseid.dev_model; } /* @@ -197,18 +201,22 @@ ccw_device_handle_oper(struct ccw_device *cdev) * been varied online on the SE so we have to find out by magic (i. e. driving * the channel subsystem to device selection and updating our path masks). */ -static inline void +static void __recover_lost_chpids(struct subchannel *sch, int old_lpm) { int mask, i; + struct chp_id chpid; + chp_id_init(&chpid); for (i = 0; i<8; i++) { mask = 0x80 >> i; if (!(sch->lpm & mask)) continue; if (old_lpm & mask) continue; - chpid_is_actually_online(sch->schib.pmcw.chpid[i]); + chpid.id = sch->schib.pmcw.chpid[i]; + if (!chp_is_registered(chpid)) + css_schedule_eval_all(); } } @@ -219,81 +227,73 @@ static void ccw_device_recog_done(struct ccw_device *cdev, int state) { struct subchannel *sch; - int notify, old_lpm, same_dev; + int old_lpm; sch = to_subchannel(cdev->dev.parent); - ccw_device_set_timeout(cdev, 0); - cio_disable_subchannel(sch); + if (cio_disable_subchannel(sch)) + state = DEV_STATE_NOT_OPER; /* * Now that we tried recognition, we have performed device selection * through ssch() and the path information is up to date. */ old_lpm = sch->lpm; - stsch(sch->irq, &sch->schib); - sch->lpm = sch->schib.pmcw.pim & - sch->schib.pmcw.pam & - sch->schib.pmcw.pom & - sch->opm; + + /* Check since device may again have become not operational. */ + if (cio_update_schib(sch)) + state = DEV_STATE_NOT_OPER; + else + sch->lpm = sch->schib.pmcw.pam & sch->opm; + if (cdev->private->state == DEV_STATE_DISCONNECTED_SENSE_ID) /* Force reprobe on all chpids. */ old_lpm = 0; if (sch->lpm != old_lpm) __recover_lost_chpids(sch, old_lpm); - if (cdev->private->state == DEV_STATE_DISCONNECTED_SENSE_ID) { - if (state == DEV_STATE_NOT_OPER) { - cdev->private->flags.recog_done = 1; - cdev->private->state = DEV_STATE_DISCONNECTED; - return; - } - /* Boxed devices don't need extra treatment. */ + if (cdev->private->state == DEV_STATE_DISCONNECTED_SENSE_ID && + (state == DEV_STATE_NOT_OPER || state == DEV_STATE_BOXED)) { + cdev->private->flags.recog_done = 1; + cdev->private->state = DEV_STATE_DISCONNECTED; + wake_up(&cdev->private->wait_q); + return; + } + if (cdev->private->flags.resuming) { + cdev->private->state = state; + cdev->private->flags.recog_done = 1; + wake_up(&cdev->private->wait_q); + return; } - notify = 0; - same_dev = 0; /* Keep the compiler quiet... */ switch (state) { case DEV_STATE_NOT_OPER: - CIO_DEBUG(KERN_WARNING, 2, - "SenseID : unknown device %04x on subchannel %04x\n", - cdev->private->devno, sch->irq); break; case DEV_STATE_OFFLINE: - if (cdev->private->state == DEV_STATE_DISCONNECTED_SENSE_ID) { - same_dev = ccw_device_handle_oper(cdev); - notify = 1; + if (!cdev->online) { + ccw_device_update_sense_data(cdev); + break; } - /* fill out sense information */ - cdev->id = (struct ccw_device_id) { - .cu_type = cdev->private->senseid.cu_type, - .cu_model = cdev->private->senseid.cu_model, - .dev_type = cdev->private->senseid.dev_type, - .dev_model = cdev->private->senseid.dev_model, - }; - if (notify) { - cdev->private->state = DEV_STATE_OFFLINE; - if (same_dev) { - /* Get device online again. */ - ccw_device_online(cdev); - wake_up(&cdev->private->wait_q); - } - return; + cdev->private->state = DEV_STATE_OFFLINE; + cdev->private->flags.recog_done = 1; + if (ccw_device_test_sense_data(cdev)) { + cdev->private->flags.donotify = 1; + ccw_device_online(cdev); + wake_up(&cdev->private->wait_q); + } else { + ccw_device_update_sense_data(cdev); + ccw_device_sched_todo(cdev, CDEV_TODO_REBIND); } - /* Issue device info message. */ - CIO_DEBUG(KERN_INFO, 2, "SenseID : device %04x reports: " - "CU Type/Mod = %04X/%02X, Dev Type/Mod = " - "%04X/%02X\n", cdev->private->devno, - cdev->id.cu_type, cdev->id.cu_model, - cdev->id.dev_type, cdev->id.dev_model); - break; + return; case DEV_STATE_BOXED: - CIO_DEBUG(KERN_WARNING, 2, - "SenseID : boxed device %04x on subchannel %04x\n", - cdev->private->devno, sch->irq); + if (cdev->id.cu_type != 0) { /* device was recognized before */ + cdev->private->flags.recog_done = 1; + cdev->private->state = DEV_STATE_BOXED; + wake_up(&cdev->private->wait_q); + return; + } break; } cdev->private->state = state; io_subchannel_recog_done(cdev); - if (state != DEV_STATE_NOT_OPER) - wake_up(&cdev->private->wait_q); + wake_up(&cdev->private->wait_q); } /* @@ -315,22 +315,54 @@ ccw_device_sense_id_done(struct ccw_device *cdev, int err) } } -static void -ccw_device_oper_notify(void *data) +/** + * ccw_device_notify() - inform the device's driver about an event + * @cdev: device for which an event occurred + * @event: event that occurred + * + * Returns: + * -%EINVAL if the device is offline or has no driver. + * -%EOPNOTSUPP if the device's driver has no notifier registered. + * %NOTIFY_OK if the driver wants to keep the device. + * %NOTIFY_BAD if the driver doesn't want to keep the device. + */ +int ccw_device_notify(struct ccw_device *cdev, int event) { - struct ccw_device *cdev; - struct subchannel *sch; - int ret; - - cdev = (struct ccw_device *)data; - sch = to_subchannel(cdev->dev.parent); - ret = (sch->driver && sch->driver->notify) ? - sch->driver->notify(&sch->dev, CIO_OPER) : 0; - if (!ret) - /* Driver doesn't want device back. */ - ccw_device_do_unreg_rereg((void *)cdev); + int ret = -EINVAL; + + if (!cdev->drv) + goto out; + if (!cdev->online) + goto out; + CIO_MSG_EVENT(2, "notify called for 0.%x.%04x, event=%d\n", + cdev->private->dev_id.ssid, cdev->private->dev_id.devno, + event); + if (!cdev->drv->notify) { + ret = -EOPNOTSUPP; + goto out; + } + if (cdev->drv->notify(cdev, event)) + ret = NOTIFY_OK; else - wake_up(&cdev->private->wait_q); + ret = NOTIFY_BAD; +out: + return ret; +} + +static void ccw_device_oper_notify(struct ccw_device *cdev) +{ + struct subchannel *sch = to_subchannel(cdev->dev.parent); + + if (ccw_device_notify(cdev, CIO_OPER) == NOTIFY_OK) { + /* Reenable channel measurements, if needed. */ + ccw_device_sched_todo(cdev, CDEV_TODO_ENABLE_CMF); + /* Save indication for new paths. */ + cdev->private->path_new_mask = sch->vpm; + return; + } + /* Driver doesn't want device back. */ + ccw_device_set_notoper(cdev); + ccw_device_sched_todo(cdev, CDEV_TODO_REBIND); } /* @@ -343,6 +375,8 @@ ccw_device_done(struct ccw_device *cdev, int state) sch = to_subchannel(cdev->dev.parent); + ccw_device_set_timeout(cdev, 0); + if (state != DEV_STATE_ONLINE) cio_disable_subchannel(sch); @@ -351,74 +385,52 @@ ccw_device_done(struct ccw_device *cdev, int state) cdev->private->state = state; - - if (state == DEV_STATE_BOXED) - CIO_DEBUG(KERN_WARNING, 2, - "Boxed device %04x on subchannel %04x\n", - cdev->private->devno, sch->irq); - - if (cdev->private->flags.donotify) { + switch (state) { + case DEV_STATE_BOXED: + CIO_MSG_EVENT(0, "Boxed device %04x on subchannel %04x\n", + cdev->private->dev_id.devno, sch->schid.sch_no); + if (cdev->online && + ccw_device_notify(cdev, CIO_BOXED) != NOTIFY_OK) + ccw_device_sched_todo(cdev, CDEV_TODO_UNREG); cdev->private->flags.donotify = 0; - PREPARE_WORK(&cdev->private->kick_work, ccw_device_oper_notify, - (void *)cdev); - queue_work(ccw_device_notify_work, &cdev->private->kick_work); - } - wake_up(&cdev->private->wait_q); - - if (css_init_done && state != DEV_STATE_ONLINE) - put_device (&cdev->dev); -} - -/* - * Function called from device_pgid.c after sense path ground has completed. - */ -void -ccw_device_sense_pgid_done(struct ccw_device *cdev, int err) -{ - struct subchannel *sch; - - sch = to_subchannel(cdev->dev.parent); - switch (err) { - case 0: - /* Start Path Group verification. */ - sch->vpm = 0; /* Start with no path groups set. */ - cdev->private->state = DEV_STATE_VERIFY; - ccw_device_verify_start(cdev); break; - case -ETIME: /* Sense path group id stopped by timeout. */ - case -EUSERS: /* device is reserved for someone else. */ - ccw_device_done(cdev, DEV_STATE_BOXED); + case DEV_STATE_NOT_OPER: + CIO_MSG_EVENT(0, "Device %04x gone on subchannel %04x\n", + cdev->private->dev_id.devno, sch->schid.sch_no); + if (ccw_device_notify(cdev, CIO_GONE) != NOTIFY_OK) + ccw_device_sched_todo(cdev, CDEV_TODO_UNREG); + else + ccw_device_set_disconnected(cdev); + cdev->private->flags.donotify = 0; break; - case -EOPNOTSUPP: /* path grouping not supported, just set online. */ - cdev->private->options.pgroup = 0; - ccw_device_done(cdev, DEV_STATE_ONLINE); + case DEV_STATE_DISCONNECTED: + CIO_MSG_EVENT(0, "Disconnected device %04x on subchannel " + "%04x\n", cdev->private->dev_id.devno, + sch->schid.sch_no); + if (ccw_device_notify(cdev, CIO_NO_PATH) != NOTIFY_OK) { + cdev->private->state = DEV_STATE_NOT_OPER; + ccw_device_sched_todo(cdev, CDEV_TODO_UNREG); + } else + ccw_device_set_disconnected(cdev); + cdev->private->flags.donotify = 0; break; default: - ccw_device_done(cdev, DEV_STATE_NOT_OPER); break; } + + if (cdev->private->flags.donotify) { + cdev->private->flags.donotify = 0; + ccw_device_oper_notify(cdev); + } + wake_up(&cdev->private->wait_q); } /* * Start device recognition. */ -int -ccw_device_recognition(struct ccw_device *cdev) +void ccw_device_recognition(struct ccw_device *cdev) { - struct subchannel *sch; - int ret; - - if ((cdev->private->state != DEV_STATE_NOT_OPER) && - (cdev->private->state != DEV_STATE_BOXED)) - return -EINVAL; - sch = to_subchannel(cdev->dev.parent); - ret = cio_enable_subchannel(sch, sch->schib.pmcw.isc); - if (ret != 0) - /* Couldn't enable the subchannel for i/o. Sick device. */ - return ret; - - /* After 60s the device recognition is considered to have failed. */ - ccw_device_set_timeout(cdev, 60*HZ); + struct subchannel *sch = to_subchannel(cdev->dev.parent); /* * We used to start here with a sense pgid to find out whether a device @@ -430,103 +442,129 @@ ccw_device_recognition(struct ccw_device *cdev) */ cdev->private->flags.recog_done = 0; cdev->private->state = DEV_STATE_SENSE_ID; + if (cio_enable_subchannel(sch, (u32) (addr_t) sch)) { + ccw_device_recog_done(cdev, DEV_STATE_NOT_OPER); + return; + } ccw_device_sense_id_start(cdev); - return 0; } /* - * Handle timeout in device recognition. + * Handle events for states that use the ccw request infrastructure. */ -static void -ccw_device_recog_timeout(struct ccw_device *cdev, enum dev_event dev_event) +static void ccw_device_request_event(struct ccw_device *cdev, enum dev_event e) { - int ret; - - ret = ccw_device_cancel_halt_clear(cdev); - switch (ret) { - case 0: - ccw_device_recog_done(cdev, DEV_STATE_BOXED); + switch (e) { + case DEV_EVENT_NOTOPER: + ccw_request_notoper(cdev); break; - case -ENODEV: - ccw_device_recog_done(cdev, DEV_STATE_NOT_OPER); + case DEV_EVENT_INTERRUPT: + ccw_request_handler(cdev); + break; + case DEV_EVENT_TIMEOUT: + ccw_request_timeout(cdev); break; default: - ccw_device_set_timeout(cdev, 3*HZ); + break; } } +static void ccw_device_report_path_events(struct ccw_device *cdev) +{ + struct subchannel *sch = to_subchannel(cdev->dev.parent); + int path_event[8]; + int chp, mask; + + for (chp = 0, mask = 0x80; chp < 8; chp++, mask >>= 1) { + path_event[chp] = PE_NONE; + if (mask & cdev->private->path_gone_mask & ~(sch->vpm)) + path_event[chp] |= PE_PATH_GONE; + if (mask & cdev->private->path_new_mask & sch->vpm) + path_event[chp] |= PE_PATH_AVAILABLE; + if (mask & cdev->private->pgid_reset_mask & sch->vpm) + path_event[chp] |= PE_PATHGROUP_ESTABLISHED; + } + if (cdev->online && cdev->drv->path_event) + cdev->drv->path_event(cdev, path_event); +} -static void -ccw_device_nopath_notify(void *data) +static void ccw_device_reset_path_events(struct ccw_device *cdev) { - struct ccw_device *cdev; - struct subchannel *sch; - int ret; + cdev->private->path_gone_mask = 0; + cdev->private->path_new_mask = 0; + cdev->private->pgid_reset_mask = 0; +} - cdev = (struct ccw_device *)data; - sch = to_subchannel(cdev->dev.parent); - /* Extra sanity. */ - if (sch->lpm) - return; - ret = (sch->driver && sch->driver->notify) ? - sch->driver->notify(&sch->dev, CIO_NO_PATH) : 0; - if (!ret) { - if (get_device(&sch->dev)) { - /* Driver doesn't want to keep device. */ - cio_disable_subchannel(sch); - if (get_device(&cdev->dev)) { - PREPARE_WORK(&cdev->private->kick_work, - ccw_device_call_sch_unregister, - (void *)cdev); - queue_work(ccw_device_work, - &cdev->private->kick_work); - } else - put_device(&sch->dev); - } - } else { - cio_disable_subchannel(sch); - ccw_device_set_timeout(cdev, 0); - cdev->private->flags.fake_irb = 0; - cdev->private->state = DEV_STATE_DISCONNECTED; - wake_up(&cdev->private->wait_q); +static void create_fake_irb(struct irb *irb, int type) +{ + memset(irb, 0, sizeof(*irb)); + if (type == FAKE_CMD_IRB) { + struct cmd_scsw *scsw = &irb->scsw.cmd; + scsw->cc = 1; + scsw->fctl = SCSW_FCTL_START_FUNC; + scsw->actl = SCSW_ACTL_START_PEND; + scsw->stctl = SCSW_STCTL_STATUS_PEND; + } else if (type == FAKE_TM_IRB) { + struct tm_scsw *scsw = &irb->scsw.tm; + scsw->x = 1; + scsw->cc = 1; + scsw->fctl = SCSW_FCTL_START_FUNC; + scsw->actl = SCSW_ACTL_START_PEND; + scsw->stctl = SCSW_STCTL_STATUS_PEND; } } -void -ccw_device_verify_done(struct ccw_device *cdev, int err) +void ccw_device_verify_done(struct ccw_device *cdev, int err) { - cdev->private->flags.doverify = 0; + struct subchannel *sch; + + sch = to_subchannel(cdev->dev.parent); + /* Update schib - pom may have changed. */ + if (cio_update_schib(sch)) { + err = -ENODEV; + goto callback; + } + /* Update lpm with verified path mask. */ + sch->lpm = sch->vpm; + /* Repeat path verification? */ + if (cdev->private->flags.doverify) { + ccw_device_verify_start(cdev); + return; + } +callback: switch (err) { - case -EOPNOTSUPP: /* path grouping not supported, just set online. */ - cdev->private->options.pgroup = 0; case 0: ccw_device_done(cdev, DEV_STATE_ONLINE); /* Deliver fake irb to device driver, if needed. */ if (cdev->private->flags.fake_irb) { - memset(&cdev->private->irb, 0, sizeof(struct irb)); - cdev->private->irb.scsw = (struct scsw) { - .cc = 1, - .fctl = SCSW_FCTL_START_FUNC, - .actl = SCSW_ACTL_START_PEND, - .stctl = SCSW_STCTL_STATUS_PEND, - }; + create_fake_irb(&cdev->private->irb, + cdev->private->flags.fake_irb); cdev->private->flags.fake_irb = 0; if (cdev->handler) cdev->handler(cdev, cdev->private->intparm, &cdev->private->irb); memset(&cdev->private->irb, 0, sizeof(struct irb)); } + ccw_device_report_path_events(cdev); break; case -ETIME: + case -EUSERS: + /* Reset oper notify indication after verify error. */ + cdev->private->flags.donotify = 0; ccw_device_done(cdev, DEV_STATE_BOXED); break; + case -EACCES: + /* Reset oper notify indication after verify error. */ + cdev->private->flags.donotify = 0; + ccw_device_done(cdev, DEV_STATE_DISCONNECTED); + break; default: - PREPARE_WORK(&cdev->private->kick_work, - ccw_device_nopath_notify, (void *)cdev); - queue_work(ccw_device_notify_work, &cdev->private->kick_work); + /* Reset oper notify indication after verify error. */ + cdev->private->flags.donotify = 0; ccw_device_done(cdev, DEV_STATE_NOT_OPER); break; } + ccw_device_reset_path_events(cdev); } /* @@ -542,24 +580,16 @@ ccw_device_online(struct ccw_device *cdev) (cdev->private->state != DEV_STATE_BOXED)) return -EINVAL; sch = to_subchannel(cdev->dev.parent); - if (css_init_done && !get_device(&cdev->dev)) - return -ENODEV; - ret = cio_enable_subchannel(sch, sch->schib.pmcw.isc); + ret = cio_enable_subchannel(sch, (u32)(addr_t)sch); if (ret != 0) { /* Couldn't enable the subchannel for i/o. Sick device. */ if (ret == -ENODEV) dev_fsm_event(cdev, DEV_EVENT_NOTOPER); return ret; } - /* Do we want to do path grouping? */ - if (!cdev->private->options.pgroup) { - /* No, set state online immediately. */ - ccw_device_done(cdev, DEV_STATE_ONLINE); - return 0; - } - /* Do a SensePGID first. */ - cdev->private->state = DEV_STATE_SENSE_PGID; - ccw_device_sense_pgid_start(cdev); + /* Start initial path verification. */ + cdev->private->state = DEV_STATE_VERIFY; + ccw_device_verify_start(cdev); return 0; } @@ -574,6 +604,7 @@ ccw_device_disband_done(struct ccw_device *cdev, int err) ccw_device_done(cdev, DEV_STATE_BOXED); break; default: + cdev->private->flags.donotify = 0; ccw_device_done(cdev, DEV_STATE_NOT_OPER); break; } @@ -587,18 +618,30 @@ ccw_device_offline(struct ccw_device *cdev) { struct subchannel *sch; + /* Allow ccw_device_offline while disconnected. */ + if (cdev->private->state == DEV_STATE_DISCONNECTED || + cdev->private->state == DEV_STATE_NOT_OPER) { + cdev->private->flags.donotify = 0; + ccw_device_done(cdev, DEV_STATE_NOT_OPER); + return 0; + } + if (cdev->private->state == DEV_STATE_BOXED) { + ccw_device_done(cdev, DEV_STATE_BOXED); + return 0; + } + if (ccw_device_is_orphan(cdev)) { + ccw_device_done(cdev, DEV_STATE_OFFLINE); + return 0; + } sch = to_subchannel(cdev->dev.parent); - if (stsch(sch->irq, &sch->schib) || !sch->schib.pmcw.dnv) + if (cio_update_schib(sch)) return -ENODEV; - if (cdev->private->state != DEV_STATE_ONLINE) { - if (sch->schib.scsw.actl != 0) - return -EBUSY; - return -EINVAL; - } - if (sch->schib.scsw.actl != 0) + if (scsw_actl(&sch->schib.scsw) != 0) return -EBUSY; + if (cdev->private->state != DEV_STATE_ONLINE) + return -EINVAL; /* Are we doing path grouping? */ - if (!cdev->private->options.pgroup) { + if (!cdev->private->flags.pgroup) { /* No, set state offline immediately. */ ccw_device_done(cdev, DEV_STATE_OFFLINE); return 0; @@ -610,82 +653,26 @@ ccw_device_offline(struct ccw_device *cdev) } /* - * Handle timeout in device online/offline process. + * Handle not operational event in non-special state. */ -static void -ccw_device_onoff_timeout(struct ccw_device *cdev, enum dev_event dev_event) -{ - int ret; - - ret = ccw_device_cancel_halt_clear(cdev); - switch (ret) { - case 0: - ccw_device_done(cdev, DEV_STATE_BOXED); - break; - case -ENODEV: - ccw_device_done(cdev, DEV_STATE_NOT_OPER); - break; - default: - ccw_device_set_timeout(cdev, 3*HZ); - } -} - -/* - * Handle not oper event in device recognition. - */ -static void -ccw_device_recog_notoper(struct ccw_device *cdev, enum dev_event dev_event) -{ - ccw_device_recog_done(cdev, DEV_STATE_NOT_OPER); -} - -/* - * Handle not operational event while offline. - */ -static void -ccw_device_offline_notoper(struct ccw_device *cdev, enum dev_event dev_event) +static void ccw_device_generic_notoper(struct ccw_device *cdev, + enum dev_event dev_event) { - struct subchannel *sch; - - cdev->private->state = DEV_STATE_NOT_OPER; - sch = to_subchannel(cdev->dev.parent); - if (get_device(&cdev->dev)) { - PREPARE_WORK(&cdev->private->kick_work, - ccw_device_call_sch_unregister, (void *)cdev); - queue_work(ccw_device_work, &cdev->private->kick_work); - } - wake_up(&cdev->private->wait_q); + if (ccw_device_notify(cdev, CIO_GONE) != NOTIFY_OK) + ccw_device_sched_todo(cdev, CDEV_TODO_UNREG); + else + ccw_device_set_disconnected(cdev); } /* - * Handle not operational event while online. + * Handle path verification event in offline state. */ -static void -ccw_device_online_notoper(struct ccw_device *cdev, enum dev_event dev_event) +static void ccw_device_offline_verify(struct ccw_device *cdev, + enum dev_event dev_event) { - struct subchannel *sch; + struct subchannel *sch = to_subchannel(cdev->dev.parent); - sch = to_subchannel(cdev->dev.parent); - if (sch->driver->notify && - sch->driver->notify(&sch->dev, sch->lpm ? CIO_GONE : CIO_NO_PATH)) { - ccw_device_set_timeout(cdev, 0); - cdev->private->flags.fake_irb = 0; - cdev->private->state = DEV_STATE_DISCONNECTED; - wake_up(&cdev->private->wait_q); - return; - } - cdev->private->state = DEV_STATE_NOT_OPER; - cio_disable_subchannel(sch); - if (sch->schib.scsw.actl != 0) { - // FIXME: not-oper indication to device driver ? - ccw_device_call_handler(cdev); - } - if (get_device(&cdev->dev)) { - PREPARE_WORK(&cdev->private->kick_work, - ccw_device_call_sch_unregister, (void *)cdev); - queue_work(ccw_device_work, &cdev->private->kick_work); - } - wake_up(&cdev->private->wait_q); + css_schedule_eval(sch->schid); } /* @@ -696,8 +683,6 @@ ccw_device_online_verify(struct ccw_device *cdev, enum dev_event dev_event) { struct subchannel *sch; - if (!cdev->private->options.pgroup) - return; if (cdev->private->state == DEV_STATE_W4SENSE) { cdev->private->flags.doverify = 1; return; @@ -707,13 +692,17 @@ ccw_device_online_verify(struct ccw_device *cdev, enum dev_event dev_event) * Since we might not just be coming from an interrupt from the * subchannel we have to update the schib. */ - stsch(sch->irq, &sch->schib); + if (cio_update_schib(sch)) { + ccw_device_verify_done(cdev, -ENODEV); + return; + } - if (sch->schib.scsw.actl != 0 || - (cdev->private->irb.scsw.stctl & SCSW_STCTL_STATUS_PEND)) { + if (scsw_actl(&sch->schib.scsw) != 0 || + (scsw_stctl(&sch->schib.scsw) & SCSW_STCTL_STATUS_PEND) || + (scsw_stctl(&cdev->private->irb.scsw) & SCSW_STCTL_STATUS_PEND)) { /* * No final status yet or final status not yet delivered - * to the device driver. Can't do path verfication now, + * to the device driver. Can't do path verification now, * delay until final status was delivered. */ cdev->private->flags.doverify = 1; @@ -725,24 +714,41 @@ ccw_device_online_verify(struct ccw_device *cdev, enum dev_event dev_event) } /* + * Handle path verification event in boxed state. + */ +static void ccw_device_boxed_verify(struct ccw_device *cdev, + enum dev_event dev_event) +{ + struct subchannel *sch = to_subchannel(cdev->dev.parent); + + if (cdev->online) { + if (cio_enable_subchannel(sch, (u32) (addr_t) sch)) + ccw_device_done(cdev, DEV_STATE_NOT_OPER); + else + ccw_device_online_verify(cdev, dev_event); + } else + css_schedule_eval(sch->schid); +} + +/* * Got an interrupt for a normal io (state online). */ static void ccw_device_irq(struct ccw_device *cdev, enum dev_event dev_event) { struct irb *irb; + int is_cmd; - irb = (struct irb *) __LC_IRB; + irb = &__get_cpu_var(cio_irb); + is_cmd = !scsw_is_tm(&irb->scsw); /* Check for unsolicited interrupt. */ - if ((irb->scsw.stctl == - (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) - && (!irb->scsw.cc)) { - if ((irb->scsw.dstat & DEV_STAT_UNIT_CHECK) && + if (!scsw_is_solicited(&irb->scsw)) { + if (is_cmd && (irb->scsw.cmd.dstat & DEV_STAT_UNIT_CHECK) && !irb->esw.esw0.erw.cons) { /* Unit check but no sense data. Need basic sense. */ if (ccw_device_do_sense(cdev, irb) != 0) goto call_handler_unsol; - memcpy(irb, &cdev->private->irb, sizeof(struct irb)); + memcpy(&cdev->private->irb, irb, sizeof(struct irb)); cdev->private->state = DEV_STATE_W4SENSE; cdev->private->intparm = 0; return; @@ -750,11 +756,13 @@ ccw_device_irq(struct ccw_device *cdev, enum dev_event dev_event) call_handler_unsol: if (cdev->handler) cdev->handler (cdev, 0, irb); + if (cdev->private->flags.doverify) + ccw_device_online_verify(cdev, 0); return; } /* Accumulate status and find out if a basic sense is needed. */ ccw_device_accumulate_irb(cdev, irb); - if (cdev->private->flags.dosense) { + if (is_cmd && cdev->private->flags.dosense) { if (ccw_device_do_sense(cdev, irb) == 0) { cdev->private->state = DEV_STATE_W4SENSE; } @@ -775,24 +783,16 @@ ccw_device_online_timeout(struct ccw_device *cdev, enum dev_event dev_event) int ret; ccw_device_set_timeout(cdev, 0); + cdev->private->iretry = 255; ret = ccw_device_cancel_halt_clear(cdev); if (ret == -EBUSY) { ccw_device_set_timeout(cdev, 3*HZ); cdev->private->state = DEV_STATE_TIMEOUT_KILL; return; } - if (ret == -ENODEV) { - struct subchannel *sch; - - sch = to_subchannel(cdev->dev.parent); - if (!sch->lpm) { - PREPARE_WORK(&cdev->private->kick_work, - ccw_device_nopath_notify, (void *)cdev); - queue_work(ccw_device_notify_work, - &cdev->private->kick_work); - } else - dev_fsm_event(cdev, DEV_EVENT_NOTOPER); - } else if (cdev->handler) + if (ret) + dev_fsm_event(cdev, DEV_EVENT_NOTOPER); + else if (cdev->handler) cdev->handler(cdev, cdev->private->intparm, ERR_PTR(-ETIMEDOUT)); } @@ -800,26 +800,40 @@ ccw_device_online_timeout(struct ccw_device *cdev, enum dev_event dev_event) /* * Got an interrupt for a basic sense. */ -void +static void ccw_device_w4sense(struct ccw_device *cdev, enum dev_event dev_event) { struct irb *irb; - irb = (struct irb *) __LC_IRB; + irb = &__get_cpu_var(cio_irb); /* Check for unsolicited interrupt. */ - if (irb->scsw.stctl == - (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) { - if (irb->scsw.cc == 1) + if (scsw_stctl(&irb->scsw) == + (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) { + if (scsw_cc(&irb->scsw) == 1) /* Basic sense hasn't started. Try again. */ ccw_device_do_sense(cdev, irb); else { - printk("Huh? %s(%s): unsolicited interrupt...\n", - __FUNCTION__, cdev->dev.bus_id); + CIO_MSG_EVENT(0, "0.%x.%04x: unsolicited " + "interrupt during w4sense...\n", + cdev->private->dev_id.ssid, + cdev->private->dev_id.devno); if (cdev->handler) cdev->handler (cdev, 0, irb); } return; } + /* + * Check if a halt or clear has been issued in the meanwhile. If yes, + * only deliver the halt/clear interrupt to the device driver as if it + * had killed the original request. + */ + if (scsw_fctl(&irb->scsw) & + (SCSW_FCTL_CLEAR_FUNC | SCSW_FCTL_HALT_FUNC)) { + cdev->private->flags.dosense = 0; + memset(&cdev->private->irb, 0, sizeof(struct irb)); + ccw_device_accumulate_irb(cdev, irb); + goto call_handler; + } /* Add basic sense info to irb. */ ccw_device_accumulate_basic_sense(cdev, irb); if (cdev->private->flags.dosense) { @@ -827,7 +841,10 @@ ccw_device_w4sense(struct ccw_device *cdev, enum dev_event dev_event) ccw_device_do_sense(cdev, irb); return; } +call_handler: cdev->private->state = DEV_STATE_ONLINE; + /* In case sensing interfered with setting the device online */ + wake_up(&cdev->private->wait_q); /* Call the handler. */ if (ccw_device_call_handler(cdev) && cdev->private->flags.doverify) /* Start delayed path verification. */ @@ -835,37 +852,15 @@ ccw_device_w4sense(struct ccw_device *cdev, enum dev_event dev_event) } static void -ccw_device_clear_verify(struct ccw_device *cdev, enum dev_event dev_event) -{ - struct irb *irb; - - irb = (struct irb *) __LC_IRB; - /* Accumulate status. We don't do basic sense. */ - ccw_device_accumulate_irb(cdev, irb); - /* Try to start delayed device verification. */ - ccw_device_online_verify(cdev, 0); - /* Note: Don't call handler for cio initiated clear! */ -} - -static void ccw_device_killing_irq(struct ccw_device *cdev, enum dev_event dev_event) { - struct subchannel *sch; - - sch = to_subchannel(cdev->dev.parent); ccw_device_set_timeout(cdev, 0); + /* Start delayed path verification. */ + ccw_device_online_verify(cdev, 0); /* OK, i/o is dead now. Call interrupt handler. */ - cdev->private->state = DEV_STATE_ONLINE; if (cdev->handler) cdev->handler(cdev, cdev->private->intparm, - ERR_PTR(-ETIMEDOUT)); - if (!sch->lpm) { - PREPARE_WORK(&cdev->private->kick_work, - ccw_device_nopath_notify, (void *)cdev); - queue_work(ccw_device_notify_work, &cdev->private->kick_work); - } else if (cdev->private->flags.doverify) - /* Start delayed path verification. */ - ccw_device_online_verify(cdev, 0); + ERR_PTR(-EIO)); } static void @@ -878,128 +873,36 @@ ccw_device_killing_timeout(struct ccw_device *cdev, enum dev_event dev_event) ccw_device_set_timeout(cdev, 3*HZ); return; } - if (ret == -ENODEV) { - struct subchannel *sch; - - sch = to_subchannel(cdev->dev.parent); - if (!sch->lpm) { - PREPARE_WORK(&cdev->private->kick_work, - ccw_device_nopath_notify, (void *)cdev); - queue_work(ccw_device_notify_work, - &cdev->private->kick_work); - } else - dev_fsm_event(cdev, DEV_EVENT_NOTOPER); - return; - } - //FIXME: Can we get here? - cdev->private->state = DEV_STATE_ONLINE; + /* Start delayed path verification. */ + ccw_device_online_verify(cdev, 0); if (cdev->handler) cdev->handler(cdev, cdev->private->intparm, - ERR_PTR(-ETIMEDOUT)); -} - -static void -ccw_device_wait4io_irq(struct ccw_device *cdev, enum dev_event dev_event) -{ - struct irb *irb; - struct subchannel *sch; - - irb = (struct irb *) __LC_IRB; - /* - * Accumulate status and find out if a basic sense is needed. - * This is fine since we have already adapted the lpm. - */ - ccw_device_accumulate_irb(cdev, irb); - if (cdev->private->flags.dosense) { - if (ccw_device_do_sense(cdev, irb) == 0) { - cdev->private->state = DEV_STATE_W4SENSE; - } - return; - } - - /* Iff device is idle, reset timeout. */ - sch = to_subchannel(cdev->dev.parent); - if (!stsch(sch->irq, &sch->schib)) - if (sch->schib.scsw.actl == 0) - ccw_device_set_timeout(cdev, 0); - /* Call the handler. */ - ccw_device_call_handler(cdev); - if (!sch->lpm) { - PREPARE_WORK(&cdev->private->kick_work, - ccw_device_nopath_notify, (void *)cdev); - queue_work(ccw_device_notify_work, &cdev->private->kick_work); - } else if (cdev->private->flags.doverify) - ccw_device_online_verify(cdev, 0); + ERR_PTR(-EIO)); } -static void -ccw_device_wait4io_timeout(struct ccw_device *cdev, enum dev_event dev_event) +void ccw_device_kill_io(struct ccw_device *cdev) { int ret; - struct subchannel *sch; - sch = to_subchannel(cdev->dev.parent); - ccw_device_set_timeout(cdev, 0); + cdev->private->iretry = 255; ret = ccw_device_cancel_halt_clear(cdev); if (ret == -EBUSY) { ccw_device_set_timeout(cdev, 3*HZ); cdev->private->state = DEV_STATE_TIMEOUT_KILL; return; } - if (ret == -ENODEV) { - if (!sch->lpm) { - PREPARE_WORK(&cdev->private->kick_work, - ccw_device_nopath_notify, (void *)cdev); - queue_work(ccw_device_notify_work, - &cdev->private->kick_work); - } else - dev_fsm_event(cdev, DEV_EVENT_NOTOPER); - return; - } + /* Start delayed path verification. */ + ccw_device_online_verify(cdev, 0); if (cdev->handler) cdev->handler(cdev, cdev->private->intparm, - ERR_PTR(-ETIMEDOUT)); - if (!sch->lpm) { - PREPARE_WORK(&cdev->private->kick_work, - ccw_device_nopath_notify, (void *)cdev); - queue_work(ccw_device_notify_work, &cdev->private->kick_work); - } else if (cdev->private->flags.doverify) - /* Start delayed path verification. */ - ccw_device_online_verify(cdev, 0); -} - -static void -ccw_device_wait4io_verify(struct ccw_device *cdev, enum dev_event dev_event) -{ - /* When the I/O has terminated, we have to start verification. */ - if (cdev->private->options.pgroup) - cdev->private->flags.doverify = 1; + ERR_PTR(-EIO)); } static void -ccw_device_stlck_done(struct ccw_device *cdev, enum dev_event dev_event) +ccw_device_delay_verify(struct ccw_device *cdev, enum dev_event dev_event) { - struct irb *irb; - - switch (dev_event) { - case DEV_EVENT_INTERRUPT: - irb = (struct irb *) __LC_IRB; - /* Check for unsolicited interrupt. */ - if ((irb->scsw.stctl == - (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) && - (!irb->scsw.cc)) - /* FIXME: we should restart stlck here, but this - * is extremely unlikely ... */ - goto out_wakeup; - - ccw_device_accumulate_irb(cdev, irb); - /* We don't care about basic sense etc. */ - break; - default: /* timeout */ - break; - } -out_wakeup: - wake_up(&cdev->private->wait_q); + /* Start verification after current task finished. */ + cdev->private->flags.doverify = 1; } static void @@ -1008,60 +911,54 @@ ccw_device_start_id(struct ccw_device *cdev, enum dev_event dev_event) struct subchannel *sch; sch = to_subchannel(cdev->dev.parent); - if (cio_enable_subchannel(sch, sch->schib.pmcw.isc) != 0) + if (cio_enable_subchannel(sch, (u32)(addr_t)sch) != 0) /* Couldn't enable the subchannel for i/o. Sick device. */ return; - - /* After 60s the device recognition is considered to have failed. */ - ccw_device_set_timeout(cdev, 60*HZ); - cdev->private->state = DEV_STATE_DISCONNECTED_SENSE_ID; ccw_device_sense_id_start(cdev); } -void -device_trigger_reprobe(struct subchannel *sch) +void ccw_device_trigger_reprobe(struct ccw_device *cdev) { - struct ccw_device *cdev; + struct subchannel *sch; - if (!sch->dev.driver_data) - return; - cdev = sch->dev.driver_data; if (cdev->private->state != DEV_STATE_DISCONNECTED) return; + sch = to_subchannel(cdev->dev.parent); /* Update some values. */ - if (stsch(sch->irq, &sch->schib)) + if (cio_update_schib(sch)) return; - /* * The pim, pam, pom values may not be accurate, but they are the best * we have before performing device selection :/ */ - sch->lpm = sch->schib.pmcw.pim & - sch->schib.pmcw.pam & - sch->schib.pmcw.pom & - sch->opm; - /* Re-set some bits in the pmcw that were lost. */ - sch->schib.pmcw.isc = 3; - sch->schib.pmcw.csense = 1; - sch->schib.pmcw.ena = 0; - if ((sch->lpm & (sch->lpm - 1)) != 0) - sch->schib.pmcw.mp = 1; - sch->schib.pmcw.intparm = (__u32)(unsigned long)sch; + sch->lpm = sch->schib.pmcw.pam & sch->opm; + /* + * Use the initial configuration since we can't be shure that the old + * paths are valid. + */ + io_subchannel_init_config(sch); + if (cio_commit_config(sch)) + return; + /* We should also udate ssd info, but this has to wait. */ - ccw_device_start_id(cdev, 0); + /* Check if this is another device which appeared on the same sch. */ + if (sch->schib.pmcw.dev != cdev->private->dev_id.devno) + css_schedule_eval(sch->schid); + else + ccw_device_start_id(cdev, 0); } -static void -ccw_device_offline_irq(struct ccw_device *cdev, enum dev_event dev_event) +static void ccw_device_disabled_irq(struct ccw_device *cdev, + enum dev_event dev_event) { struct subchannel *sch; sch = to_subchannel(cdev->dev.parent); /* - * An interrupt in state offline means a previous disable was not - * successful. Try again. + * An interrupt in a disabled state means a previous disable was not + * successful - should not happen, but we try to disable again. */ cio_disable_subchannel(sch); } @@ -1074,15 +971,19 @@ ccw_device_change_cmfstate(struct ccw_device *cdev, enum dev_event dev_event) dev_fsm_event(cdev, dev_event); } +static void ccw_device_update_cmfblock(struct ccw_device *cdev, + enum dev_event dev_event) +{ + cmf_retry_copy_block(cdev); + cdev->private->state = DEV_STATE_ONLINE; + dev_fsm_event(cdev, dev_event); +} static void ccw_device_quiesce_done(struct ccw_device *cdev, enum dev_event dev_event) { ccw_device_set_timeout(cdev, 0); - if (dev_event == DEV_EVENT_NOTOPER) - cdev->private->state = DEV_STATE_NOT_OPER; - else - cdev->private->state = DEV_STATE_OFFLINE; + cdev->private->state = DEV_STATE_NOT_OPER; wake_up(&cdev->private->wait_q); } @@ -1092,17 +993,11 @@ ccw_device_quiesce_timeout(struct ccw_device *cdev, enum dev_event dev_event) int ret; ret = ccw_device_cancel_halt_clear(cdev); - switch (ret) { - case 0: - cdev->private->state = DEV_STATE_OFFLINE; - wake_up(&cdev->private->wait_q); - break; - case -ENODEV: + if (ret == -EBUSY) { + ccw_device_set_timeout(cdev, HZ/10); + } else { cdev->private->state = DEV_STATE_NOT_OPER; wake_up(&cdev->private->wait_q); - break; - default: - ccw_device_set_timeout(cdev, HZ/10); } } @@ -1116,93 +1011,70 @@ ccw_device_nop(struct ccw_device *cdev, enum dev_event dev_event) } /* - * Bug operation action. - */ -static void -ccw_device_bug(struct ccw_device *cdev, enum dev_event dev_event) -{ - printk(KERN_EMERG "dev_jumptable[%i][%i] == NULL\n", - cdev->private->state, dev_event); - BUG(); -} - -/* * device statemachine */ fsm_func_t *dev_jumptable[NR_DEV_STATES][NR_DEV_EVENTS] = { [DEV_STATE_NOT_OPER] = { [DEV_EVENT_NOTOPER] = ccw_device_nop, - [DEV_EVENT_INTERRUPT] = ccw_device_bug, + [DEV_EVENT_INTERRUPT] = ccw_device_disabled_irq, [DEV_EVENT_TIMEOUT] = ccw_device_nop, [DEV_EVENT_VERIFY] = ccw_device_nop, }, [DEV_STATE_SENSE_PGID] = { - [DEV_EVENT_NOTOPER] = ccw_device_online_notoper, - [DEV_EVENT_INTERRUPT] = ccw_device_sense_pgid_irq, - [DEV_EVENT_TIMEOUT] = ccw_device_onoff_timeout, + [DEV_EVENT_NOTOPER] = ccw_device_request_event, + [DEV_EVENT_INTERRUPT] = ccw_device_request_event, + [DEV_EVENT_TIMEOUT] = ccw_device_request_event, [DEV_EVENT_VERIFY] = ccw_device_nop, }, [DEV_STATE_SENSE_ID] = { - [DEV_EVENT_NOTOPER] = ccw_device_recog_notoper, - [DEV_EVENT_INTERRUPT] = ccw_device_sense_id_irq, - [DEV_EVENT_TIMEOUT] = ccw_device_recog_timeout, + [DEV_EVENT_NOTOPER] = ccw_device_request_event, + [DEV_EVENT_INTERRUPT] = ccw_device_request_event, + [DEV_EVENT_TIMEOUT] = ccw_device_request_event, [DEV_EVENT_VERIFY] = ccw_device_nop, }, [DEV_STATE_OFFLINE] = { - [DEV_EVENT_NOTOPER] = ccw_device_offline_notoper, - [DEV_EVENT_INTERRUPT] = ccw_device_offline_irq, + [DEV_EVENT_NOTOPER] = ccw_device_generic_notoper, + [DEV_EVENT_INTERRUPT] = ccw_device_disabled_irq, [DEV_EVENT_TIMEOUT] = ccw_device_nop, - [DEV_EVENT_VERIFY] = ccw_device_nop, + [DEV_EVENT_VERIFY] = ccw_device_offline_verify, }, [DEV_STATE_VERIFY] = { - [DEV_EVENT_NOTOPER] = ccw_device_online_notoper, - [DEV_EVENT_INTERRUPT] = ccw_device_verify_irq, - [DEV_EVENT_TIMEOUT] = ccw_device_onoff_timeout, - [DEV_EVENT_VERIFY] = ccw_device_nop, + [DEV_EVENT_NOTOPER] = ccw_device_request_event, + [DEV_EVENT_INTERRUPT] = ccw_device_request_event, + [DEV_EVENT_TIMEOUT] = ccw_device_request_event, + [DEV_EVENT_VERIFY] = ccw_device_delay_verify, }, [DEV_STATE_ONLINE] = { - [DEV_EVENT_NOTOPER] = ccw_device_online_notoper, + [DEV_EVENT_NOTOPER] = ccw_device_generic_notoper, [DEV_EVENT_INTERRUPT] = ccw_device_irq, [DEV_EVENT_TIMEOUT] = ccw_device_online_timeout, [DEV_EVENT_VERIFY] = ccw_device_online_verify, }, [DEV_STATE_W4SENSE] = { - [DEV_EVENT_NOTOPER] = ccw_device_online_notoper, + [DEV_EVENT_NOTOPER] = ccw_device_generic_notoper, [DEV_EVENT_INTERRUPT] = ccw_device_w4sense, [DEV_EVENT_TIMEOUT] = ccw_device_nop, [DEV_EVENT_VERIFY] = ccw_device_online_verify, }, [DEV_STATE_DISBAND_PGID] = { - [DEV_EVENT_NOTOPER] = ccw_device_online_notoper, - [DEV_EVENT_INTERRUPT] = ccw_device_disband_irq, - [DEV_EVENT_TIMEOUT] = ccw_device_onoff_timeout, + [DEV_EVENT_NOTOPER] = ccw_device_request_event, + [DEV_EVENT_INTERRUPT] = ccw_device_request_event, + [DEV_EVENT_TIMEOUT] = ccw_device_request_event, [DEV_EVENT_VERIFY] = ccw_device_nop, }, [DEV_STATE_BOXED] = { - [DEV_EVENT_NOTOPER] = ccw_device_offline_notoper, - [DEV_EVENT_INTERRUPT] = ccw_device_stlck_done, - [DEV_EVENT_TIMEOUT] = ccw_device_stlck_done, - [DEV_EVENT_VERIFY] = ccw_device_nop, - }, - /* states to wait for i/o completion before doing something */ - [DEV_STATE_CLEAR_VERIFY] = { - [DEV_EVENT_NOTOPER] = ccw_device_online_notoper, - [DEV_EVENT_INTERRUPT] = ccw_device_clear_verify, + [DEV_EVENT_NOTOPER] = ccw_device_generic_notoper, + [DEV_EVENT_INTERRUPT] = ccw_device_nop, [DEV_EVENT_TIMEOUT] = ccw_device_nop, - [DEV_EVENT_VERIFY] = ccw_device_nop, + [DEV_EVENT_VERIFY] = ccw_device_boxed_verify, }, + /* states to wait for i/o completion before doing something */ [DEV_STATE_TIMEOUT_KILL] = { - [DEV_EVENT_NOTOPER] = ccw_device_online_notoper, + [DEV_EVENT_NOTOPER] = ccw_device_generic_notoper, [DEV_EVENT_INTERRUPT] = ccw_device_killing_irq, [DEV_EVENT_TIMEOUT] = ccw_device_killing_timeout, [DEV_EVENT_VERIFY] = ccw_device_nop, //FIXME }, - [DEV_STATE_WAIT4IO] = { - [DEV_EVENT_NOTOPER] = ccw_device_online_notoper, - [DEV_EVENT_INTERRUPT] = ccw_device_wait4io_irq, - [DEV_EVENT_TIMEOUT] = ccw_device_wait4io_timeout, - [DEV_EVENT_VERIFY] = ccw_device_wait4io_verify, - }, [DEV_STATE_QUIESCE] = { [DEV_EVENT_NOTOPER] = ccw_device_quiesce_done, [DEV_EVENT_INTERRUPT] = ccw_device_quiesce_done, @@ -1213,13 +1085,13 @@ fsm_func_t *dev_jumptable[NR_DEV_STATES][NR_DEV_EVENTS] = { [DEV_STATE_DISCONNECTED] = { [DEV_EVENT_NOTOPER] = ccw_device_nop, [DEV_EVENT_INTERRUPT] = ccw_device_start_id, - [DEV_EVENT_TIMEOUT] = ccw_device_bug, - [DEV_EVENT_VERIFY] = ccw_device_nop, + [DEV_EVENT_TIMEOUT] = ccw_device_nop, + [DEV_EVENT_VERIFY] = ccw_device_start_id, }, [DEV_STATE_DISCONNECTED_SENSE_ID] = { - [DEV_EVENT_NOTOPER] = ccw_device_recog_notoper, - [DEV_EVENT_INTERRUPT] = ccw_device_sense_id_irq, - [DEV_EVENT_TIMEOUT] = ccw_device_recog_timeout, + [DEV_EVENT_NOTOPER] = ccw_device_request_event, + [DEV_EVENT_INTERRUPT] = ccw_device_request_event, + [DEV_EVENT_TIMEOUT] = ccw_device_request_event, [DEV_EVENT_VERIFY] = ccw_device_nop, }, [DEV_STATE_CMFCHANGE] = { @@ -1228,23 +1100,18 @@ fsm_func_t *dev_jumptable[NR_DEV_STATES][NR_DEV_EVENTS] = { [DEV_EVENT_TIMEOUT] = ccw_device_change_cmfstate, [DEV_EVENT_VERIFY] = ccw_device_change_cmfstate, }, + [DEV_STATE_CMFUPDATE] = { + [DEV_EVENT_NOTOPER] = ccw_device_update_cmfblock, + [DEV_EVENT_INTERRUPT] = ccw_device_update_cmfblock, + [DEV_EVENT_TIMEOUT] = ccw_device_update_cmfblock, + [DEV_EVENT_VERIFY] = ccw_device_update_cmfblock, + }, + [DEV_STATE_STEAL_LOCK] = { + [DEV_EVENT_NOTOPER] = ccw_device_request_event, + [DEV_EVENT_INTERRUPT] = ccw_device_request_event, + [DEV_EVENT_TIMEOUT] = ccw_device_request_event, + [DEV_EVENT_VERIFY] = ccw_device_nop, + }, }; -/* - * io_subchannel_irq is called for "real" interrupts or for status - * pending conditions on msch. - */ -void -io_subchannel_irq (struct device *pdev) -{ - struct ccw_device *cdev; - - cdev = to_subchannel(pdev)->dev.driver_data; - - CIO_TRACE_EVENT (3, "IRQ"); - CIO_TRACE_EVENT (3, pdev->bus_id); - if (cdev) - dev_fsm_event(cdev, DEV_EVENT_INTERRUPT); -} - EXPORT_SYMBOL_GPL(ccw_device_set_timeout); |
