From: Martin Schwidefsky 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. --- 25-akpm/drivers/s390/cio/ccwgroup.c | 35 +++++++++++++- 25-akpm/drivers/s390/cio/chsc.c | 6 +- 25-akpm/drivers/s390/cio/css.c | 17 +++---- 25-akpm/drivers/s390/cio/device.c | 35 ++++++++++++++ 25-akpm/drivers/s390/cio/device.h | 3 + 25-akpm/drivers/s390/cio/device_fsm.c | 80 +++++++++++++++++++++++++++------- 25-akpm/drivers/s390/cio/qdio.c | 8 ++- 7 files changed, 154 insertions(+), 30 deletions(-) diff -puN drivers/s390/cio/ccwgroup.c~s390-2-12-common-i-o-layer drivers/s390/cio/ccwgroup.c --- 25/drivers/s390/cio/ccwgroup.c~s390-2-12-common-i-o-layer 2004-04-08 13:54:59.648077672 -0700 +++ 25-akpm/drivers/s390/cio/ccwgroup.c 2004-04-08 13:54:59.661075696 -0700 @@ -1,7 +1,7 @@ /* * drivers/s390/cio/ccwgroup.c * bus driver for ccwgroup - * $Revision: 1.25 $ + * $Revision: 1.27 $ * * Copyright (C) 2002 IBM Deutschland Entwicklung GmbH, * IBM Corporation @@ -397,9 +397,35 @@ ccwgroup_driver_register (struct ccwgrou 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 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); } @@ -416,8 +442,11 @@ __ccwgroup_get_gdev_by_cdev(struct ccw_d if (cdev->dev.driver_data) { gdev = (struct ccwgroup_device *)cdev->dev.driver_data; - if (get_device(&gdev->dev)) - return gdev; + if (get_device(&gdev->dev)) { + if (!list_empty(&gdev->dev.node)) + return gdev; + put_device(&gdev->dev); + } return NULL; } return NULL; diff -puN drivers/s390/cio/chsc.c~s390-2-12-common-i-o-layer drivers/s390/cio/chsc.c --- 25/drivers/s390/cio/chsc.c~s390-2-12-common-i-o-layer 2004-04-08 13:54:59.650077368 -0700 +++ 25-akpm/drivers/s390/cio/chsc.c 2004-04-08 13:54:59.662075544 -0700 @@ -1,7 +1,7 @@ /* * drivers/s390/cio/chsc.c * S/390 common I/O routines -- channel subsystem call - * $Revision: 1.105 $ + * $Revision: 1.107 $ * * Copyright (C) 1999-2002 IBM Deutschland Entwicklung GmbH, * IBM Corporation @@ -819,8 +819,10 @@ s390_vary_chpid( __u8 chpid, int on) struct schib schib; sch = get_subchannel_by_schid(irq); - if (sch) + if (sch) { + put_device(&sch->dev); continue; + } if (stsch(irq, &schib)) /* We're through */ break; diff -puN drivers/s390/cio/css.c~s390-2-12-common-i-o-layer drivers/s390/cio/css.c --- 25/drivers/s390/cio/css.c~s390-2-12-common-i-o-layer 2004-04-08 13:54:59.652077064 -0700 +++ 25-akpm/drivers/s390/cio/css.c 2004-04-08 13:54:59.663075392 -0700 @@ -1,7 +1,7 @@ /* * drivers/s390/cio/css.c * driver for channel subsystem - * $Revision: 1.69 $ + * $Revision: 1.72 $ * * Copyright (C) 2002 IBM Deutschland Entwicklung GmbH, * IBM Corporation @@ -163,11 +163,6 @@ get_subchannel_by_schid(int irq) struct device, bus_list)); if (!dev) continue; - /* Skip channel paths. */ - if (dev->release != &css_subchannel_release) { - put_device(dev); - continue; - } sch = to_subchannel(dev); if (sch->irq == irq) break; @@ -206,10 +201,16 @@ css_evaluate_subchannel(int irq, int slo sch = get_subchannel_by_schid(irq); disc = sch ? device_is_disconnected(sch) : 0; - if (disc && slow) + if (disc && slow) { + if (sch) + put_device(&sch->dev); 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. */ + } event = css_get_subchannel_status(sch, irq); switch (event) { case CIO_GONE: diff -puN drivers/s390/cio/device.c~s390-2-12-common-i-o-layer drivers/s390/cio/device.c --- 25/drivers/s390/cio/device.c~s390-2-12-common-i-o-layer 2004-04-08 13:54:59.653076912 -0700 +++ 25-akpm/drivers/s390/cio/device.c 2004-04-08 13:54:59.664075240 -0700 @@ -1,7 +1,7 @@ /* * drivers/s390/cio/device.c * bus driver for ccw devices - * $Revision: 1.110 $ + * $Revision: 1.113 $ * * Copyright (C) 2002 IBM Deutschland Entwicklung GmbH, * IBM Corporation @@ -120,6 +120,7 @@ void io_subchannel_irq (struct device *) static int io_subchannel_notify(struct device *, int); static void io_subchannel_verify(struct device *); static void io_subchannel_ioterm(struct device *); +static void io_subchannel_shutdown(struct device *); struct css_driver io_subchannel_driver = { .subchannel_type = SUBCHANNEL_TYPE_IO, @@ -128,6 +129,7 @@ struct css_driver io_subchannel_driver = .bus = &css_bus_type, .probe = &io_subchannel_probe, .remove = &io_subchannel_remove, + .shutdown = &io_subchannel_shutdown, }, .irq = io_subchannel_irq, .notify = io_subchannel_notify, @@ -766,6 +768,37 @@ io_subchannel_ioterm(struct device *dev) 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 static struct ccw_device console_cdev; static struct ccw_device_private console_private; diff -puN drivers/s390/cio/device_fsm.c~s390-2-12-common-i-o-layer drivers/s390/cio/device_fsm.c --- 25/drivers/s390/cio/device_fsm.c~s390-2-12-common-i-o-layer 2004-04-08 13:54:59.655076608 -0700 +++ 25-akpm/drivers/s390/cio/device_fsm.c 2004-04-08 13:54:59.666074936 -0700 @@ -101,7 +101,7 @@ ccw_device_set_timeout(struct ccw_device * -EBUSY if an interrupt is expected (either from halt/clear or from a * status pending). */ -static int +int ccw_device_cancel_halt_clear(struct ccw_device *cdev) { struct subchannel *sch; @@ -438,10 +438,13 @@ ccw_device_nopath_notify(void *data) ret = (sch->driver && sch->driver->notify) ? sch->driver->notify(&sch->dev, CIO_NO_PATH) : 0; if (!ret) { - /* Driver doesn't want to keep device. */ - device_unregister(&sch->dev); - sch->schib.pmcw.intparm = 0; - cio_modify(sch); + if (get_device(&sch->dev)) { + /* Driver doesn't want to keep device. */ + device_unregister(&sch->dev); + sch->schib.pmcw.intparm = 0; + cio_modify(sch); + put_device(&sch->dev); + } } else { ccw_device_set_timeout(cdev, 0); cdev->private->state = DEV_STATE_DISCONNECTED; @@ -710,9 +713,17 @@ ccw_device_online_timeout(struct ccw_dev cdev->private->state = DEV_STATE_TIMEOUT_KILL; return; } - if (ret == -ENODEV) - dev_fsm_event(cdev, DEV_EVENT_NOTOPER); - else if (cdev->handler) + 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_work, &cdev->private->kick_work); + } else + dev_fsm_event(cdev, DEV_EVENT_NOTOPER); + } else if (cdev->handler) cdev->handler(cdev, cdev->private->intparm, ERR_PTR(-ETIMEDOUT)); } @@ -808,8 +819,8 @@ ccw_device_killing_timeout(struct ccw_de PREPARE_WORK(&cdev->private->kick_work, ccw_device_nopath_notify, (void *)cdev); queue_work(ccw_device_work, &cdev->private->kick_work); - } - dev_fsm_event(cdev, DEV_EVENT_NOTOPER); + } else + dev_fsm_event(cdev, DEV_EVENT_NOTOPER); return; } //FIXME: Can we get here? @@ -868,6 +879,7 @@ ccw_device_wait4io_timeout(struct ccw_de int ret; struct subchannel *sch; + sch = to_subchannel(cdev->dev.parent); ccw_device_set_timeout(cdev, 0); ret = ccw_device_cancel_halt_clear(cdev); if (ret == -EBUSY) { @@ -876,16 +888,17 @@ ccw_device_wait4io_timeout(struct ccw_de return; } if (ret == -ENODEV) { - PREPARE_WORK(&cdev->private->kick_work, - ccw_device_nopath_notify, (void *)cdev); - queue_work(ccw_device_work, &cdev->private->kick_work); - dev_fsm_event(cdev, DEV_EVENT_NOTOPER); + 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); return; } if (cdev->handler) cdev->handler(cdev, cdev->private->intparm, ERR_PTR(-ETIMEDOUT)); - sch = to_subchannel(cdev->dev.parent); if (!sch->lpm) { PREPARE_WORK(&cdev->private->kick_work, ccw_device_nopath_notify, (void *)cdev); @@ -1005,6 +1018,37 @@ ccw_device_change_cmfstate(struct ccw_de } +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 * state offline. @@ -1102,6 +1146,12 @@ fsm_func_t *dev_jumptable[NR_DEV_STATES] [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, + [DEV_EVENT_TIMEOUT] ccw_device_quiesce_timeout, + [DEV_EVENT_VERIFY] ccw_device_nop, + }, /* special states for devices gone not operational */ [DEV_STATE_DISCONNECTED] { [DEV_EVENT_NOTOPER] ccw_device_nop, diff -puN drivers/s390/cio/device.h~s390-2-12-common-i-o-layer drivers/s390/cio/device.h --- 25/drivers/s390/cio/device.h~s390-2-12-common-i-o-layer 2004-04-08 13:54:59.656076456 -0700 +++ 25-akpm/drivers/s390/cio/device.h 2004-04-08 13:54:59.664075240 -0700 @@ -18,6 +18,7 @@ enum dev_state { DEV_STATE_CLEAR_VERIFY, DEV_STATE_TIMEOUT_KILL, DEV_STATE_WAIT4IO, + DEV_STATE_QUIESCE, /* special states for devices gone not operational */ DEV_STATE_DISCONNECTED, DEV_STATE_DISCONNECTED_SENSE_ID, @@ -68,6 +69,8 @@ extern struct workqueue_struct *ccw_devi 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 *); void ccw_device_do_unreg_rereg(void *); diff -puN drivers/s390/cio/qdio.c~s390-2-12-common-i-o-layer drivers/s390/cio/qdio.c --- 25/drivers/s390/cio/qdio.c~s390-2-12-common-i-o-layer 2004-04-08 13:54:59.658076152 -0700 +++ 25-akpm/drivers/s390/cio/qdio.c 2004-04-08 13:54:59.668074632 -0700 @@ -56,7 +56,7 @@ #include "ioasm.h" #include "chsc.h" -#define VERSION_QDIO_C "$Revision: 1.78 $" +#define VERSION_QDIO_C "$Revision: 1.79 $" /****************** MODULE PARAMETER VARIABLES ********************/ MODULE_AUTHOR("Utz Bacher "); @@ -392,6 +392,11 @@ qdio_unmark_q(struct qdio_q *q) if ((q->is_thinint_q)&&(q->is_input_q)) { /* iQDIO */ 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) { /* q was the only interesting q */ tiq_list=NULL; @@ -404,6 +409,7 @@ qdio_unmark_q(struct qdio_q *q) q->list_next=NULL; q->list_prev=NULL; } +out: spin_unlock_irqrestore(&ttiq_list_lock,flags); } } _