Commit 6a562864 authored by Andrew Morton's avatar Andrew Morton Committed by Linus Torvalds

[PATCH] s390: common i/o layer

From: Martin Schwidefsky <schwidefsky@de.ibm.com>

Common i/o layer changes:
 - Avoid de-registering a ccwgroup device multiple times.
 - Remove check for channel path objects in get_subchannel_by_schid.
   Channel patch objects are never in the bus list.
 - Avoid NULL pointer deref. in qdio_unmark_q.
 - Fix reference counting on subchannel objects.
 - Add shutdown function to terminate i/o and disable subchannels at reipl.
 - Remove all ccwgroup devices if the ccwgroup driver is unregistered.
parent 74216ef5
/* /*
* drivers/s390/cio/ccwgroup.c * drivers/s390/cio/ccwgroup.c
* bus driver for ccwgroup * bus driver for ccwgroup
* $Revision: 1.25 $ * $Revision: 1.27 $
* *
* Copyright (C) 2002 IBM Deutschland Entwicklung GmbH, * Copyright (C) 2002 IBM Deutschland Entwicklung GmbH,
* IBM Corporation * IBM Corporation
...@@ -397,9 +397,35 @@ ccwgroup_driver_register (struct ccwgroup_driver *cdriver) ...@@ -397,9 +397,35 @@ ccwgroup_driver_register (struct ccwgroup_driver *cdriver)
return driver_register(&cdriver->driver); return driver_register(&cdriver->driver);
} }
static inline struct device *
__get_next_ccwgroup_device(struct device_driver *drv)
{
struct device *dev, *d;
down_read(&drv->bus->subsys.rwsem);
dev = NULL;
list_for_each_entry(d, &drv->devices, driver_list) {
dev = get_device(d);
if (dev)
break;
}
up_read(&drv->bus->subsys.rwsem);
return dev;
}
void void
ccwgroup_driver_unregister (struct ccwgroup_driver *cdriver) ccwgroup_driver_unregister (struct ccwgroup_driver *cdriver)
{ {
struct device *dev;
/* We don't want ccwgroup devices to live longer than their driver. */
get_driver(&cdriver->driver);
while ((dev = __get_next_ccwgroup_device(&cdriver->driver))) {
__ccwgroup_remove_symlinks(to_ccwgroupdev(dev));
device_unregister(dev);
put_device(dev);
};
put_driver(&cdriver->driver);
driver_unregister(&cdriver->driver); driver_unregister(&cdriver->driver);
} }
...@@ -416,8 +442,11 @@ __ccwgroup_get_gdev_by_cdev(struct ccw_device *cdev) ...@@ -416,8 +442,11 @@ __ccwgroup_get_gdev_by_cdev(struct ccw_device *cdev)
if (cdev->dev.driver_data) { if (cdev->dev.driver_data) {
gdev = (struct ccwgroup_device *)cdev->dev.driver_data; gdev = (struct ccwgroup_device *)cdev->dev.driver_data;
if (get_device(&gdev->dev)) if (get_device(&gdev->dev)) {
return gdev; if (!list_empty(&gdev->dev.node))
return gdev;
put_device(&gdev->dev);
}
return NULL; return NULL;
} }
return NULL; return NULL;
......
/* /*
* drivers/s390/cio/chsc.c * drivers/s390/cio/chsc.c
* S/390 common I/O routines -- channel subsystem call * S/390 common I/O routines -- channel subsystem call
* $Revision: 1.105 $ * $Revision: 1.107 $
* *
* Copyright (C) 1999-2002 IBM Deutschland Entwicklung GmbH, * Copyright (C) 1999-2002 IBM Deutschland Entwicklung GmbH,
* IBM Corporation * IBM Corporation
...@@ -819,8 +819,10 @@ s390_vary_chpid( __u8 chpid, int on) ...@@ -819,8 +819,10 @@ s390_vary_chpid( __u8 chpid, int on)
struct schib schib; struct schib schib;
sch = get_subchannel_by_schid(irq); sch = get_subchannel_by_schid(irq);
if (sch) if (sch) {
put_device(&sch->dev);
continue; continue;
}
if (stsch(irq, &schib)) if (stsch(irq, &schib))
/* We're through */ /* We're through */
break; break;
......
/* /*
* drivers/s390/cio/css.c * drivers/s390/cio/css.c
* driver for channel subsystem * driver for channel subsystem
* $Revision: 1.69 $ * $Revision: 1.72 $
* *
* Copyright (C) 2002 IBM Deutschland Entwicklung GmbH, * Copyright (C) 2002 IBM Deutschland Entwicklung GmbH,
* IBM Corporation * IBM Corporation
...@@ -163,11 +163,6 @@ get_subchannel_by_schid(int irq) ...@@ -163,11 +163,6 @@ get_subchannel_by_schid(int irq)
struct device, bus_list)); struct device, bus_list));
if (!dev) if (!dev)
continue; continue;
/* Skip channel paths. */
if (dev->release != &css_subchannel_release) {
put_device(dev);
continue;
}
sch = to_subchannel(dev); sch = to_subchannel(dev);
if (sch->irq == irq) if (sch->irq == irq)
break; break;
...@@ -206,10 +201,16 @@ css_evaluate_subchannel(int irq, int slow) ...@@ -206,10 +201,16 @@ css_evaluate_subchannel(int irq, int slow)
sch = get_subchannel_by_schid(irq); sch = get_subchannel_by_schid(irq);
disc = sch ? device_is_disconnected(sch) : 0; disc = sch ? device_is_disconnected(sch) : 0;
if (disc && slow) if (disc && slow) {
if (sch)
put_device(&sch->dev);
return 0; /* Already processed. */ return 0; /* Already processed. */
if (!disc && !slow) }
if (!disc && !slow) {
if (sch)
put_device(&sch->dev);
return -EAGAIN; /* Will be done on the slow path. */ return -EAGAIN; /* Will be done on the slow path. */
}
event = css_get_subchannel_status(sch, irq); event = css_get_subchannel_status(sch, irq);
switch (event) { switch (event) {
case CIO_GONE: case CIO_GONE:
......
/* /*
* drivers/s390/cio/device.c * drivers/s390/cio/device.c
* bus driver for ccw devices * bus driver for ccw devices
* $Revision: 1.110 $ * $Revision: 1.113 $
* *
* Copyright (C) 2002 IBM Deutschland Entwicklung GmbH, * Copyright (C) 2002 IBM Deutschland Entwicklung GmbH,
* IBM Corporation * IBM Corporation
...@@ -120,6 +120,7 @@ void io_subchannel_irq (struct device *); ...@@ -120,6 +120,7 @@ void io_subchannel_irq (struct device *);
static int io_subchannel_notify(struct device *, int); static int io_subchannel_notify(struct device *, int);
static void io_subchannel_verify(struct device *); static void io_subchannel_verify(struct device *);
static void io_subchannel_ioterm(struct device *); static void io_subchannel_ioterm(struct device *);
static void io_subchannel_shutdown(struct device *);
struct css_driver io_subchannel_driver = { struct css_driver io_subchannel_driver = {
.subchannel_type = SUBCHANNEL_TYPE_IO, .subchannel_type = SUBCHANNEL_TYPE_IO,
...@@ -128,6 +129,7 @@ struct css_driver io_subchannel_driver = { ...@@ -128,6 +129,7 @@ struct css_driver io_subchannel_driver = {
.bus = &css_bus_type, .bus = &css_bus_type,
.probe = &io_subchannel_probe, .probe = &io_subchannel_probe,
.remove = &io_subchannel_remove, .remove = &io_subchannel_remove,
.shutdown = &io_subchannel_shutdown,
}, },
.irq = io_subchannel_irq, .irq = io_subchannel_irq,
.notify = io_subchannel_notify, .notify = io_subchannel_notify,
...@@ -766,6 +768,37 @@ io_subchannel_ioterm(struct device *dev) ...@@ -766,6 +768,37 @@ io_subchannel_ioterm(struct device *dev)
ERR_PTR(-EIO)); ERR_PTR(-EIO));
} }
static void
io_subchannel_shutdown(struct device *dev)
{
struct subchannel *sch;
struct ccw_device *cdev;
int ret;
sch = to_subchannel(dev);
cdev = dev->driver_data;
if (cio_is_console(sch->irq))
return;
if (!sch->schib.pmcw.ena)
/* Nothing to do. */
return;
ret = cio_disable_subchannel(sch);
if (ret != -EBUSY)
/* Subchannel is disabled, we're done. */
return;
cdev->private->state = DEV_STATE_QUIESCE;
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));
}
cio_disable_subchannel(sch);
}
#ifdef CONFIG_CCW_CONSOLE #ifdef CONFIG_CCW_CONSOLE
static struct ccw_device console_cdev; static struct ccw_device console_cdev;
static struct ccw_device_private console_private; static struct ccw_device_private console_private;
......
...@@ -18,6 +18,7 @@ enum dev_state { ...@@ -18,6 +18,7 @@ enum dev_state {
DEV_STATE_CLEAR_VERIFY, DEV_STATE_CLEAR_VERIFY,
DEV_STATE_TIMEOUT_KILL, DEV_STATE_TIMEOUT_KILL,
DEV_STATE_WAIT4IO, DEV_STATE_WAIT4IO,
DEV_STATE_QUIESCE,
/* special states for devices gone not operational */ /* special states for devices gone not operational */
DEV_STATE_DISCONNECTED, DEV_STATE_DISCONNECTED,
DEV_STATE_DISCONNECTED_SENSE_ID, DEV_STATE_DISCONNECTED_SENSE_ID,
...@@ -68,6 +69,8 @@ extern struct workqueue_struct *ccw_device_work; ...@@ -68,6 +69,8 @@ extern struct workqueue_struct *ccw_device_work;
void io_subchannel_recog_done(struct ccw_device *cdev); void io_subchannel_recog_done(struct ccw_device *cdev);
int ccw_device_cancel_halt_clear(struct ccw_device *);
int ccw_device_register(struct ccw_device *); int ccw_device_register(struct ccw_device *);
void ccw_device_do_unreg_rereg(void *); void ccw_device_do_unreg_rereg(void *);
......
...@@ -101,7 +101,7 @@ ccw_device_set_timeout(struct ccw_device *cdev, int expires) ...@@ -101,7 +101,7 @@ ccw_device_set_timeout(struct ccw_device *cdev, int expires)
* -EBUSY if an interrupt is expected (either from halt/clear or from a * -EBUSY if an interrupt is expected (either from halt/clear or from a
* status pending). * status pending).
*/ */
static int int
ccw_device_cancel_halt_clear(struct ccw_device *cdev) ccw_device_cancel_halt_clear(struct ccw_device *cdev)
{ {
struct subchannel *sch; struct subchannel *sch;
...@@ -438,10 +438,13 @@ ccw_device_nopath_notify(void *data) ...@@ -438,10 +438,13 @@ ccw_device_nopath_notify(void *data)
ret = (sch->driver && sch->driver->notify) ? ret = (sch->driver && sch->driver->notify) ?
sch->driver->notify(&sch->dev, CIO_NO_PATH) : 0; sch->driver->notify(&sch->dev, CIO_NO_PATH) : 0;
if (!ret) { if (!ret) {
/* Driver doesn't want to keep device. */ if (get_device(&sch->dev)) {
device_unregister(&sch->dev); /* Driver doesn't want to keep device. */
sch->schib.pmcw.intparm = 0; device_unregister(&sch->dev);
cio_modify(sch); sch->schib.pmcw.intparm = 0;
cio_modify(sch);
put_device(&sch->dev);
}
} else { } else {
ccw_device_set_timeout(cdev, 0); ccw_device_set_timeout(cdev, 0);
cdev->private->state = DEV_STATE_DISCONNECTED; cdev->private->state = DEV_STATE_DISCONNECTED;
...@@ -710,9 +713,17 @@ ccw_device_online_timeout(struct ccw_device *cdev, enum dev_event dev_event) ...@@ -710,9 +713,17 @@ ccw_device_online_timeout(struct ccw_device *cdev, enum dev_event dev_event)
cdev->private->state = DEV_STATE_TIMEOUT_KILL; cdev->private->state = DEV_STATE_TIMEOUT_KILL;
return; return;
} }
if (ret == -ENODEV) if (ret == -ENODEV) {
dev_fsm_event(cdev, DEV_EVENT_NOTOPER); struct subchannel *sch;
else if (cdev->handler)
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_work, &cdev->private->kick_work);
} else
dev_fsm_event(cdev, DEV_EVENT_NOTOPER);
} else if (cdev->handler)
cdev->handler(cdev, cdev->private->intparm, cdev->handler(cdev, cdev->private->intparm,
ERR_PTR(-ETIMEDOUT)); ERR_PTR(-ETIMEDOUT));
} }
...@@ -808,8 +819,8 @@ ccw_device_killing_timeout(struct ccw_device *cdev, enum dev_event dev_event) ...@@ -808,8 +819,8 @@ ccw_device_killing_timeout(struct ccw_device *cdev, enum dev_event dev_event)
PREPARE_WORK(&cdev->private->kick_work, PREPARE_WORK(&cdev->private->kick_work,
ccw_device_nopath_notify, (void *)cdev); ccw_device_nopath_notify, (void *)cdev);
queue_work(ccw_device_work, &cdev->private->kick_work); queue_work(ccw_device_work, &cdev->private->kick_work);
} } else
dev_fsm_event(cdev, DEV_EVENT_NOTOPER); dev_fsm_event(cdev, DEV_EVENT_NOTOPER);
return; return;
} }
//FIXME: Can we get here? //FIXME: Can we get here?
...@@ -868,6 +879,7 @@ ccw_device_wait4io_timeout(struct ccw_device *cdev, enum dev_event dev_event) ...@@ -868,6 +879,7 @@ ccw_device_wait4io_timeout(struct ccw_device *cdev, enum dev_event dev_event)
int ret; int ret;
struct subchannel *sch; struct subchannel *sch;
sch = to_subchannel(cdev->dev.parent);
ccw_device_set_timeout(cdev, 0); ccw_device_set_timeout(cdev, 0);
ret = ccw_device_cancel_halt_clear(cdev); ret = ccw_device_cancel_halt_clear(cdev);
if (ret == -EBUSY) { if (ret == -EBUSY) {
...@@ -876,16 +888,17 @@ ccw_device_wait4io_timeout(struct ccw_device *cdev, enum dev_event dev_event) ...@@ -876,16 +888,17 @@ ccw_device_wait4io_timeout(struct ccw_device *cdev, enum dev_event dev_event)
return; return;
} }
if (ret == -ENODEV) { if (ret == -ENODEV) {
PREPARE_WORK(&cdev->private->kick_work, if (!sch->lpm) {
ccw_device_nopath_notify, (void *)cdev); PREPARE_WORK(&cdev->private->kick_work,
queue_work(ccw_device_work, &cdev->private->kick_work); ccw_device_nopath_notify, (void *)cdev);
dev_fsm_event(cdev, DEV_EVENT_NOTOPER); queue_work(ccw_device_work, &cdev->private->kick_work);
} else
dev_fsm_event(cdev, DEV_EVENT_NOTOPER);
return; return;
} }
if (cdev->handler) if (cdev->handler)
cdev->handler(cdev, cdev->private->intparm, cdev->handler(cdev, cdev->private->intparm,
ERR_PTR(-ETIMEDOUT)); ERR_PTR(-ETIMEDOUT));
sch = to_subchannel(cdev->dev.parent);
if (!sch->lpm) { if (!sch->lpm) {
PREPARE_WORK(&cdev->private->kick_work, PREPARE_WORK(&cdev->private->kick_work,
ccw_device_nopath_notify, (void *)cdev); ccw_device_nopath_notify, (void *)cdev);
...@@ -1005,6 +1018,37 @@ ccw_device_change_cmfstate(struct ccw_device *cdev, enum dev_event dev_event) ...@@ -1005,6 +1018,37 @@ ccw_device_change_cmfstate(struct ccw_device *cdev, enum dev_event 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;
wake_up(&cdev->private->wait_q);
}
static void
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:
cdev->private->state = DEV_STATE_NOT_OPER;
wake_up(&cdev->private->wait_q);
break;
default:
ccw_device_set_timeout(cdev, HZ/10);
}
}
/* /*
* No operation action. This is used e.g. to ignore a timeout event in * No operation action. This is used e.g. to ignore a timeout event in
* state offline. * state offline.
...@@ -1102,6 +1146,12 @@ fsm_func_t *dev_jumptable[NR_DEV_STATES][NR_DEV_EVENTS] = { ...@@ -1102,6 +1146,12 @@ fsm_func_t *dev_jumptable[NR_DEV_STATES][NR_DEV_EVENTS] = {
[DEV_EVENT_TIMEOUT] ccw_device_wait4io_timeout, [DEV_EVENT_TIMEOUT] ccw_device_wait4io_timeout,
[DEV_EVENT_VERIFY] ccw_device_wait4io_verify, [DEV_EVENT_VERIFY] ccw_device_wait4io_verify,
}, },
[DEV_STATE_QUIESCE] {
[DEV_EVENT_NOTOPER] ccw_device_quiesce_done,
[DEV_EVENT_INTERRUPT] ccw_device_quiesce_done,
[DEV_EVENT_TIMEOUT] ccw_device_quiesce_timeout,
[DEV_EVENT_VERIFY] ccw_device_nop,
},
/* special states for devices gone not operational */ /* special states for devices gone not operational */
[DEV_STATE_DISCONNECTED] { [DEV_STATE_DISCONNECTED] {
[DEV_EVENT_NOTOPER] ccw_device_nop, [DEV_EVENT_NOTOPER] ccw_device_nop,
......
...@@ -56,7 +56,7 @@ ...@@ -56,7 +56,7 @@
#include "ioasm.h" #include "ioasm.h"
#include "chsc.h" #include "chsc.h"
#define VERSION_QDIO_C "$Revision: 1.78 $" #define VERSION_QDIO_C "$Revision: 1.79 $"
/****************** MODULE PARAMETER VARIABLES ********************/ /****************** MODULE PARAMETER VARIABLES ********************/
MODULE_AUTHOR("Utz Bacher <utz.bacher@de.ibm.com>"); MODULE_AUTHOR("Utz Bacher <utz.bacher@de.ibm.com>");
...@@ -392,6 +392,11 @@ qdio_unmark_q(struct qdio_q *q) ...@@ -392,6 +392,11 @@ qdio_unmark_q(struct qdio_q *q)
if ((q->is_thinint_q)&&(q->is_input_q)) { if ((q->is_thinint_q)&&(q->is_input_q)) {
/* iQDIO */ /* iQDIO */
spin_lock_irqsave(&ttiq_list_lock,flags); spin_lock_irqsave(&ttiq_list_lock,flags);
/* in case cleanup has done this already and simultanously
* qdio_unmark_q is called from the interrupt handler, we've
* got to check this in this specific case again */
if ((!q->list_prev)||(!q->list_next))
goto out;
if (q->list_next==q) { if (q->list_next==q) {
/* q was the only interesting q */ /* q was the only interesting q */
tiq_list=NULL; tiq_list=NULL;
...@@ -404,6 +409,7 @@ qdio_unmark_q(struct qdio_q *q) ...@@ -404,6 +409,7 @@ qdio_unmark_q(struct qdio_q *q)
q->list_next=NULL; q->list_next=NULL;
q->list_prev=NULL; q->list_prev=NULL;
} }
out:
spin_unlock_irqrestore(&ttiq_list_lock,flags); spin_unlock_irqrestore(&ttiq_list_lock,flags);
} }
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment