/*
* 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)
* Martin Schwidefsky (schwidefsky@de.ibm.com)
*/
#include <linux/module.h>
#include <linux/config.h>
#include <linux/init.h>
#include <asm/ccwdev.h>
#include <asm/qdio.h>
#include "cio.h"
#include "cio_debug.h"
#include "css.h"
#include "device.h"
#include "ioasm.h"
#include "qdio.h"
int
device_is_disconnected(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_DISCONNECTED ||
cdev->private->state == DEV_STATE_DISCONNECTED_SENSE_ID);
}
void
device_set_disconnected(struct subchannel *sch)
{
struct ccw_device *cdev;
if (!sch->dev.driver_data)
return;
cdev = sch->dev.driver_data;
cdev->private->state = DEV_STATE_DISCONNECTED;
}
void
device_set_waiting(struct subchannel *sch)
{
struct ccw_device *cdev;
if (!sch->dev.driver_data)
return;
cdev = sch->dev.driver_data;
ccw_device_set_timeout(cdev, 10*HZ);
cdev->private->state = DEV_STATE_WAIT4IO;
}
/*
* Timeout function. It just triggers a DEV_EVENT_TIMEOUT.
*/
static void
ccw_device_timeout(unsigned long data)
{
struct ccw_device *cdev;
cdev = (struct ccw_device *) data;
spin_lock_irq(cdev->ccwlock);
dev_fsm_event(cdev, DEV_EVENT_TIMEOUT);
spin_unlock_irq(cdev->ccwlock);
}
/*
* Set timeout
*/
void
ccw_device_set_timeout(struct ccw_device *cdev, int expires)
{
if (expires == 0) {
if (timer_pending(&cdev->private->timer))
del_timer(&cdev->private->timer);
return;
}
if (timer_pending(&cdev->private->timer)) {
if (mod_timer(&cdev->private->timer, jiffies + expires))
return;
}
cdev->private->timer.function = ccw_device_timeout;
cdev->private->timer.data = (unsigned long) cdev;
cdev->private->timer.expires = jiffies + expires;
add_timer(&cdev->private->timer);
}
/*
* Cancel running i/o. This is called repeatedly since halt/clear are
* asynchronous operations. We do one try with cio_cancel, two tries
* with cio_halt, 255 tries with cio_clear. If everythings fails panic.
* Returns 0 if device now idle, -ENODEV for device not operational and
* -EBUSY if an interrupt is expected (either from halt/clear or from a
* status pending).
*/
static int
ccw_device_cancel_halt_clear(struct ccw_device *cdev)
{
struct subchannel *sch;
int ret;
sch = to_subchannel(cdev->dev.parent);
ret = stsch(sch->irq, &sch->schib);
if (ret || !sch->schib.pmcw.dnv)
return -ENODEV;
if (!sch->schib.pmcw.ena || sch->schib.scsw.actl == 0)
/* Not operational or no activity -> 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. */
cdev->private->iretry = 3; /* 3 halt retries. */
}
if (!(sch->schib.scsw.actl & SCSW_ACTL_CLEAR_PEND)) {
/* Stage 2: halt io. */
if (cdev->private->iretry) {
cdev->private->iretry--;
ret = cio_halt(sch);
return (ret == 0) ? -EBUSY : ret;
}
/* halt io unsuccessful. */
cdev->private->iretry = 255; /* 255 clear retries. */
}
/* Stage 3: clear io. */
if (cdev->private->iretry) {
cdev->private->iretry--;
ret = cio_clear (sch);
return (ret == 0) ? -EBUSY : ret;
}
panic("Can't stop i/o on subchannel.\n");
}
static void
ccw_device_handle_oper(struct ccw_device *cdev)
{
struct subchannel *sch;
sch = to_subchannel(cdev->dev.parent);
/*
* Check if cu type and device type still match. If
* not, it is certainly another device and we have to
* de- and re-register.
*/
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) {
PREPARE_WORK(&cdev->private->kick_work,
ccw_device_do_unreg_rereg, (void *)&cdev->dev);
queue_work(ccw_device_work, &cdev->private->kick_work);
return;
}
cdev->private->flags.donotify = 1;
/* Get device online again. */
ccw_device_online(cdev);
}
/*
* Stop device recognition.
*/
static void
ccw_device_recog_done(struct ccw_device *cdev, int state)
{
struct subchannel *sch;
int notify;
sch = to_subchannel(cdev->dev.parent);
ccw_device_set_timeout(cdev, 0);
cio_disable_subchannel(sch);
if (cdev->private->state == DEV_STATE_DISCONNECTED_SENSE_ID) {
if (state == DEV_STATE_NOT_OPER) {
cdev->private->state = DEV_STATE_DISCONNECTED;
return;
}
/* Boxed devices don't need extra treatment. */
}
notify = 0;
switch (state) {
case DEV_STATE_NOT_OPER:
CIO_DEBUG(KERN_WARNING, 2,
"SenseID : unknown device %s on subchannel %s\n",
cdev->dev.bus_id, sch->dev.bus_id);
break;
case DEV_STATE_OFFLINE:
if (cdev->private->state == DEV_STATE_DISCONNECTED_SENSE_ID)
notify = 1;
else /* 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,
};
/* Issue device info message. */
CIO_DEBUG(KERN_INFO, 2, "SenseID : device %s reports: "
"CU Type/Mod = %04X/%02X, Dev Type/Mod = "
"%04X/%02X\n", cdev->dev.bus_id,
cdev->id.cu_type, cdev->id.cu_model,
cdev->id.dev_type, cdev->id.dev_model);
break;
case DEV_STATE_BOXED:
CIO_DEBUG(KERN_WARNING, 2,
"SenseID : boxed device %s on subchannel %s\n",
cdev->dev.bus_id, sch->dev.bus_id);
break;
}
cdev->private->state = state;
if (notify && state == DEV_STATE_OFFLINE)
ccw_device_handle_oper(cdev);
else
io_subchannel_recog_done(cdev);
if (state != DEV_STATE_NOT_OPER)
wake_up(&cdev->private->wait_q);
}
/*
* Function called from device_id.c after sense id has completed.
*/
void
ccw_device_sense_id_done(struct ccw_device *cdev, int err)
{
switch (err) {
case 0:
ccw_device_recog_done(cdev, DEV_STATE_OFFLINE);
break;
case -ETIME: /* Sense id stopped by timeout. */
ccw_device_recog_done(cdev, DEV_STATE_BOXED);
break;
default:
ccw_device_recog_done(cdev, DEV_STATE_NOT_OPER);
break;
}
}
static void
ccw_device_oper_notify(void *data)
{
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->dev);
else
wake_up(&cdev->private->wait_q);
}
/*
* Finished with online/offline processing.
*/
static void
ccw_device_done(struct ccw_device *cdev, int state)
{
struct subchannel *sch;
sch = to_subchannel(cdev->dev.parent);
if (state != DEV_STATE_ONLINE)
cio_disable_subchannel(sch);
/* Reset device status. */
memset(&cdev->private->irb, 0, sizeof(struct irb));
cdev->private->state = state;
if (state == DEV_STATE_BOXED) {
CIO_DEBUG(KERN_WARNING, 2,
"Boxed device %s on subchannel %s\n",
cdev->dev.bus_id, sch->dev.bus_id);
INIT_WORK(&cdev->private->kick_work,
ccw_device_add_stlck, (void *) cdev);
queue_work(ccw_device_work, &cdev->private->kick_work);
}
if (cdev->private->flags.donotify) {
cdev->private->flags.donotify = 0;
PREPARE_WORK(&cdev->private->kick_work, ccw_device_oper_notify,
(void *)cdev);
queue_work(ccw_device_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);
break;
case -EOPNOTSUPP: /* path grouping not supported, just set online. */
cdev->private->options.pgroup = 0;
ccw_device_done(cdev, DEV_STATE_ONLINE);
break;
default:
ccw_device_done(cdev, DEV_STATE_NOT_OPER);
break;
}
}
/*
* Start device recognition.
*/
int
ccw_device_recognition(struct ccw_device *cdev)
{
struct subchannel *sch;
int ret;
if (cdev->private->state != DEV_STATE_NOT_OPER)
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);
/*
* We used to start here with a sense pgid to find out whether a device
* is locked by someone else. Unfortunately, the sense pgid command
* code has other meanings on devices predating the path grouping
* algorithm, so we start with sense id and box the device after an
* timeout (or if sense pgid during path verification detects the device
* is locked, as may happen on newer devices).
*/
cdev->private->state = DEV_STATE_SENSE_ID;
ccw_device_sense_id_start(cdev);
return 0;
}
/*
* Handle timeout in device recognition.
*/
static void
ccw_device_recog_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_recog_done(cdev, DEV_STATE_BOXED);
break;
case -ENODEV:
ccw_device_recog_done(cdev, DEV_STATE_NOT_OPER);
break;
default:
ccw_device_set_timeout(cdev, 3*HZ);
}
}
void
ccw_device_verify_done(struct ccw_device *cdev, int err)
{
cdev->private->flags.doverify = 0;
switch (err) {
case 0:
ccw_device_done(cdev, DEV_STATE_ONLINE);
break;
case -ETIME:
ccw_device_done(cdev, DEV_STATE_BOXED);
break;
default:
ccw_device_done(cdev, DEV_STATE_NOT_OPER);
break;
}
}
/*
* Get device online.
*/
int
ccw_device_online(struct ccw_device *cdev)
{
struct subchannel *sch;
int ret;
if (cdev->private->state != DEV_STATE_OFFLINE)
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);
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);
return 0;
}
void
ccw_device_disband_done(struct ccw_device *cdev, int err)
{
switch (err) {
case 0:
ccw_device_done(cdev, DEV_STATE_OFFLINE);
break;
case -ETIME:
ccw_device_done(cdev, DEV_STATE_BOXED);
break;
default:
ccw_device_done(cdev, DEV_STATE_NOT_OPER);
break;
}
}
/*
* Shutdown device.
*/
int
ccw_device_offline(struct ccw_device *cdev)
{
struct subchannel *sch;
sch = to_subchannel(cdev->dev.parent);
if (cdev->private->state != DEV_STATE_ONLINE) {
if (sch->schib.scsw.actl != 0)
return -EBUSY;
return -EINVAL;
}
if (sch->schib.scsw.actl != 0)
return -EBUSY;
/* Are we doing path grouping? */
if (!cdev->private->options.pgroup) {
/* No, set state offline immediately. */
ccw_device_done(cdev, DEV_STATE_OFFLINE);
return 0;
}
/* Start Set Path Group commands. */
cdev->private->state = DEV_STATE_DISBAND_PGID;
ccw_device_disband_start(cdev);
return 0;
}
/*
* Handle timeout in device online/offline process.
*/
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)
{
if (cdev->private->state == DEV_STATE_DISCONNECTED_SENSE_ID)
cdev->private->state = DEV_STATE_DISCONNECTED;
else
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)
{
cdev->private->state = DEV_STATE_NOT_OPER;
device_unregister(&cdev->dev);
wake_up(&cdev->private->wait_q);
}
/*
* Handle not operational event while online.
*/
static void
ccw_device_online_notoper(struct ccw_device *cdev, enum dev_event dev_event)
{
struct subchannel *sch;
sch = to_subchannel(cdev->dev.parent);
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);
}
device_unregister(&cdev->dev);
wake_up(&cdev->private->wait_q);
}
static void
ccw_device_disconnected_notoper(struct ccw_device *cdev,
enum dev_event dev_event)
{
struct subchannel *sch;
sch = to_subchannel(cdev->dev.parent);
cdev->private->state = DEV_STATE_NOT_OPER;
cio_disable_subchannel(sch);
device_unregister(&cdev->dev);
wake_up(&cdev->private->wait_q);
}
/*
* Handle path verification event.
*/
static void
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;
}
sch = to_subchannel(cdev->dev.parent);
if (sch->schib.scsw.actl != 0 ||
(cdev->private->irb.scsw.stctl & SCSW_STCTL_STATUS_PEND)) {
/*
* No final status yet or final status not yet delivered
* to the device driver. Can't do path verfication now,
* delay until final status was delivered.
*/
cdev->private->flags.doverify = 1;
return;
}
/* Device is idle, we can do the path verification. */
cdev->private->state = DEV_STATE_VERIFY;
ccw_device_verify_start(cdev);
}
/*
* 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;
irb = (struct irb *) __LC_IRB;
/* Check for unsolicited interrupt. */
if (irb->scsw.stctl ==
(SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) {
if (cdev->handler)
cdev->handler (cdev, 0, irb);
return;
}
/* Accumulate status and find out if a basic sense is needed. */
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;
}
/* Call the handler. */
if (ccw_device_call_handler(cdev) && cdev->private->flags.doverify)
/* Start delayed path verification. */
ccw_device_online_verify(cdev, 0);
}
/*
* Got an timeout in online state.
*/
static void
ccw_device_online_timeout(struct ccw_device *cdev, enum dev_event dev_event)
{
int ret;
ccw_device_set_timeout(cdev, 0);
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)
dev_fsm_event(cdev, DEV_EVENT_NOTOPER);
else if (cdev->handler)
cdev->handler(cdev, cdev->private->intparm,
ERR_PTR(-ETIMEDOUT));
}
/*
* Got an interrupt for a basic sense.
*/
void
ccw_device_w4sense(struct ccw_device *cdev, enum dev_event dev_event)
{
struct irb *irb;
irb = (struct irb *) __LC_IRB;
/* Check for unsolicited interrupt. */
if (irb->scsw.stctl ==
(SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) {
if (cdev->handler)
cdev->handler (cdev, 0, irb);
return;
}
/* Add basic sense info to irb. */
ccw_device_accumulate_basic_sense(cdev, irb);
if (cdev->private->flags.dosense) {
/* Another basic sense is needed. */
ccw_device_do_sense(cdev, irb);
return;
}
cdev->private->state = DEV_STATE_ONLINE;
/* Call the handler. */
if (ccw_device_call_handler(cdev) && cdev->private->flags.doverify)
/* Start delayed path verification. */
ccw_device_online_verify(cdev, 0);
}
static void
ccw_device_clear_verify(struct ccw_device *cdev, enum dev_event dev_event)
{
struct irb *irb;
irb = (struct irb *) __LC_IRB;
/* Check for unsolicited interrupt. */
if (irb->scsw.stctl ==
(SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) {
if (cdev->handler)
cdev->handler (cdev, 0, irb);
return;
}
/* 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)
{
/* 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));
}
static void
ccw_device_killing_timeout(struct ccw_device *cdev, enum dev_event dev_event)
{
int ret;
ret = ccw_device_cancel_halt_clear(cdev);
if (ret == -EBUSY) {
ccw_device_set_timeout(cdev, 3*HZ);
return;
}
if (ret == -ENODEV) {
dev_fsm_event(cdev, DEV_EVENT_NOTOPER);
return;
}
//FIXME: Can we get here?
cdev->private->state = DEV_STATE_ONLINE;
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;
/* Check for unsolicited interrupt. */
if (irb->scsw.stctl ==
(SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) {
if (cdev->handler)
cdev->handler (cdev, 0, irb);
return;
}
/*
* 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. */
if (ccw_device_call_handler(cdev) && cdev->private->flags.doverify)
ccw_device_online_verify(cdev, 0);
}
static void
ccw_device_wait4io_timeout(struct ccw_device *cdev, enum dev_event dev_event)
{
int ret;
ccw_device_set_timeout(cdev, 0);
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)
dev_fsm_event(cdev, DEV_EVENT_NOTOPER);
else if (cdev->handler)
cdev->handler(cdev, cdev->private->intparm,
ERR_PTR(-ETIMEDOUT));
}
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;
}
static void
ccw_device_stlck_done(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))
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);
}
static void
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)
/* 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)
{
struct ccw_device *cdev;
unsigned long flags;
if (!sch->dev.driver_data)
return;
cdev = sch->dev.driver_data;
if (cdev->private->state != DEV_STATE_DISCONNECTED)
return;
spin_lock_irqsave(&sch->lock, flags);
/* 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;
ccw_device_start_id(cdev, 0);
spin_unlock_irqrestore(&sch->lock, flags);
}
static void
ccw_device_offline_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.
*/
cio_disable_subchannel(sch);
}
/*
* No operation action. This is used e.g. to ignore a timeout event in
* state offline.
*/
static void
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_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_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_VERIFY] ccw_device_nop,
},
[DEV_STATE_OFFLINE] {
[DEV_EVENT_NOTOPER] ccw_device_offline_notoper,
[DEV_EVENT_INTERRUPT] ccw_device_offline_irq,
[DEV_EVENT_TIMEOUT] ccw_device_nop,
[DEV_EVENT_VERIFY] ccw_device_nop,
},
[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_STATE_ONLINE] {
[DEV_EVENT_NOTOPER] ccw_device_online_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_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_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_TIMEOUT] ccw_device_nop,
[DEV_EVENT_VERIFY] ccw_device_nop,
},
[DEV_STATE_TIMEOUT_KILL] {
[DEV_EVENT_NOTOPER] ccw_device_online_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,
},
/* special states for devices gone not operational */
[DEV_STATE_DISCONNECTED] {
[DEV_EVENT_NOTOPER] ccw_device_disconnected_notoper,
[DEV_EVENT_INTERRUPT] ccw_device_start_id,
[DEV_EVENT_TIMEOUT] ccw_device_bug,
[DEV_EVENT_VERIFY] ccw_device_nop,
},
[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_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);
dev_fsm_event(cdev, DEV_EVENT_INTERRUPT);
}
EXPORT_SYMBOL_GPL(ccw_device_set_timeout);