*
* Copyright (C) 2002 IBM Deutschland Entwicklung GmbH,
* IBM Corporation
- * Author(s): Cornelia Huck(cohuck@de.ibm.com)
+ * 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 "cio.h"
#include "cio_debug.h"
#include "device.h"
#include "chsc.h"
#include "ioasm.h"
-#include "qdio.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);
+}
int
device_is_disconnected(struct subchannel *sch)
return;
cdev = sch->dev.driver_data;
ccw_device_set_timeout(cdev, 0);
+ cdev->private->flags.fake_irb = 0;
cdev->private->state = DEV_STATE_DISCONNECTED;
}
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
int ret;
sch = to_subchannel(cdev->dev.parent);
- ret = stsch(sch->irq, &sch->schib);
+ ret = stsch(sch->schid, &sch->schib);
if (ret || !sch->schib.pmcw.dnv)
return -ENODEV;
if (!sch->schib.pmcw.ena || sch->schib.scsw.actl == 0)
panic("Can't stop i/o on subchannel.\n");
}
-static void
+static int
ccw_device_handle_oper(struct ccw_device *cdev)
{
struct subchannel *sch;
PREPARE_WORK(&cdev->private->kick_work,
ccw_device_do_unreg_rereg, (void *)cdev);
queue_work(ccw_device_work, &cdev->private->kick_work);
- return;
+ return 0;
}
cdev->private->flags.donotify = 1;
+ return 1;
}
/*
ccw_device_recog_done(struct ccw_device *cdev, int state)
{
struct subchannel *sch;
- int notify, old_lpm;
+ int notify, old_lpm, same_dev;
sch = to_subchannel(cdev->dev.parent);
* through ssch() and the path information is up to date.
*/
old_lpm = sch->lpm;
- stsch(sch->irq, &sch->schib);
+ stsch(sch->schid, &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 (!sch->schib.pmcw.dnv)
+ state = DEV_STATE_NOT_OPER;
if (cdev->private->state == DEV_STATE_DISCONNECTED_SENSE_ID)
/* Force reprobe on all chpids. */
old_lpm = 0;
/* Boxed devices don't need extra treatment. */
}
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);
+ "SenseID : unknown device %04x on subchannel "
+ "0.%x.%04x\n", cdev->private->devno,
+ sch->schid.ssid, sch->schid.sch_no);
break;
case DEV_STATE_OFFLINE:
if (cdev->private->state == DEV_STATE_DISCONNECTED_SENSE_ID) {
- ccw_device_handle_oper(cdev);
+ same_dev = ccw_device_handle_oper(cdev);
notify = 1;
}
/* fill out sense information */
.dev_model = cdev->private->senseid.dev_model,
};
if (notify) {
- /* Get device online again. */
- ccw_device_online(cdev);
- wake_up(&cdev->private->wait_q);
+ cdev->private->state = DEV_STATE_OFFLINE;
+ if (same_dev) {
+ /* Get device online again. */
+ ccw_device_online(cdev);
+ wake_up(&cdev->private->wait_q);
+ }
return;
}
/* Issue device info message. */
- CIO_DEBUG(KERN_INFO, 2, "SenseID : device %04x reports: "
+ CIO_DEBUG(KERN_INFO, 2, "SenseID : device 0.%x.%04x reports: "
"CU Type/Mod = %04X/%02X, Dev Type/Mod = "
- "%04X/%02X\n", cdev->private->devno,
+ "%04X/%02X\n",
+ cdev->private->ssid, cdev->private->devno,
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 %04x on subchannel %04x\n",
- cdev->private->devno, sch->irq);
+ "SenseID : boxed device %04x on subchannel "
+ "0.%x.%04x\n", cdev->private->devno,
+ sch->schid.ssid, sch->schid.sch_no);
break;
}
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);
+ cdev->private->devno, sch->schid.sch_no);
if (cdev->private->flags.donotify) {
cdev->private->flags.donotify = 0;
(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);
}
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,
+ };
+ 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));
+ }
break;
case -ETIME:
ccw_device_done(cdev, DEV_STATE_BOXED);
struct subchannel *sch;
sch = to_subchannel(cdev->dev.parent);
+ if (stsch(sch->schid, &sch->schib) || !sch->schib.pmcw.dnv)
+ return -ENODEV;
if (cdev->private->state != DEV_STATE_ONLINE) {
if (sch->schib.scsw.actl != 0)
return -EBUSY;
cdev->private->state = DEV_STATE_NOT_OPER;
sch = to_subchannel(cdev->dev.parent);
- device_unregister(&sch->dev);
- sch->schib.pmcw.intparm = 0;
- cio_modify(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);
+ }
wake_up(&cdev->private->wait_q);
}
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;
// FIXME: not-oper indication to device driver ?
ccw_device_call_handler(cdev);
}
- device_unregister(&sch->dev);
- sch->schib.pmcw.intparm = 0;
- cio_modify(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);
+ }
wake_up(&cdev->private->wait_q);
}
return;
}
sch = to_subchannel(cdev->dev.parent);
+ /*
+ * Since we might not just be coming from an interrupt from the
+ * subchannel we have to update the schib.
+ */
+ stsch(sch->schid, &sch->schib);
+
if (sch->schib.scsw.actl != 0 ||
(cdev->private->irb.scsw.stctl & SCSW_STCTL_STATUS_PEND)) {
/*
/* Iff device is idle, reset timeout. */
sch = to_subchannel(cdev->dev.parent);
- if (!stsch(sch->irq, &sch->schib))
+ if (!stsch(sch->schid, &sch->schib))
if (sch->schib.scsw.actl == 0)
ccw_device_set_timeout(cdev, 0);
/* Call the handler. */
device_trigger_reprobe(struct subchannel *sch)
{
struct ccw_device *cdev;
- unsigned long flags;
if (!sch->dev.driver_data)
return;
cdev = sch->dev.driver_data;
- spin_lock_irqsave(&sch->lock, flags);
- if (cdev->private->state != DEV_STATE_DISCONNECTED) {
- spin_unlock_irqrestore(&sch->lock, flags);
+ if (cdev->private->state != DEV_STATE_DISCONNECTED)
return;
- }
+
/* Update some values. */
- if (stsch(sch->irq, &sch->schib)) {
- spin_unlock_irqrestore(&sch->lock, flags);
+ if (stsch(sch->schid, &sch->schib))
return;
- }
+
/*
* The pim, pam, pom values may not be accurate, but they are the best
* we have before performing device selection :/
if ((sch->lpm & (sch->lpm - 1)) != 0)
sch->schib.pmcw.mp = 1;
sch->schib.pmcw.intparm = (__u32)(unsigned long)sch;
+ /* We should also udate ssd info, but this has to wait. */
ccw_device_start_id(cdev, 0);
- spin_unlock_irqrestore(&sch->lock, flags);
}
static void
CIO_TRACE_EVENT (3, "IRQ");
CIO_TRACE_EVENT (3, pdev->bus_id);
-
- dev_fsm_event(cdev, DEV_EVENT_INTERRUPT);
+ if (cdev)
+ dev_fsm_event(cdev, DEV_EVENT_INTERRUPT);
}
EXPORT_SYMBOL_GPL(ccw_device_set_timeout);