X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=drivers%2Fs390%2Fcio%2Fdevice.c;h=a921b1fea5fdbd6aac2d7a932e63afc4f8a166c2;hb=9213980e6a70d8473e0ffd4b39ab5b6caaba9ff5;hp=31d561bea1c94b3bf4e83a1874c00f09dd307522;hpb=c449269f45c2cdf53af08c8d0af37472f66539d9;p=linux-2.6.git diff --git a/drivers/s390/cio/device.c b/drivers/s390/cio/device.c index 31d561bea..a921b1fea 100644 --- a/drivers/s390/cio/device.c +++ b/drivers/s390/cio/device.c @@ -1,7 +1,7 @@ /* * drivers/s390/cio/device.c * bus driver for ccw devices - * $Revision: 1.115 $ + * $Revision: 1.120 $ * * Copyright (C) 2002 IBM Deutschland Entwicklung GmbH, * IBM Corporation @@ -26,6 +26,7 @@ #include "cio.h" #include "css.h" #include "device.h" +#include "ioasm.h" /******************* bus type handling ***********************/ @@ -158,6 +159,11 @@ init_ccw_bus_type (void) ret = -ENOMEM; /* FIXME: better errno ? */ goto out_err; } + slow_path_wq = create_singlethread_workqueue("kslowcrw"); + if (!slow_path_wq) { + ret = -ENOMEM; /* FIXME: better errno ? */ + goto out_err; + } if ((ret = bus_register (&ccw_bus_type))) goto out_err; @@ -173,6 +179,8 @@ out_err: destroy_workqueue(ccw_device_work); if (ccw_device_notify_work) destroy_workqueue(ccw_device_notify_work); + if (slow_path_wq) + destroy_workqueue(slow_path_wq); return ret; } @@ -499,20 +507,94 @@ ccw_device_register(struct ccw_device *cdev) return ret; } -void -ccw_device_do_unreg_rereg(void *data) +static struct ccw_device * +get_disc_ccwdev_by_devno(unsigned int devno, struct ccw_device *sibling) { + struct ccw_device *cdev; + struct list_head *entry; struct device *dev; - dev = (struct device *)data; - device_remove_files(dev); - device_del(dev); - if (device_add(dev)) { + if (!get_bus(&ccw_bus_type)) + return NULL; + down_read(&ccw_bus_type.subsys.rwsem); + cdev = NULL; + list_for_each(entry, &ccw_bus_type.devices.list) { + dev = get_device(container_of(entry, + struct device, bus_list)); + if (!dev) + continue; + cdev = to_ccwdev(dev); + if ((cdev->private->state == DEV_STATE_DISCONNECTED) && + (cdev->private->devno == devno) && + (!strncmp(cdev->dev.bus_id, sibling->dev.bus_id, + BUS_ID_SIZE))) { + cdev->private->state = DEV_STATE_NOT_OPER; + break; + } put_device(dev); + cdev = NULL; + } + up_read(&ccw_bus_type.subsys.rwsem); + put_bus(&ccw_bus_type); + + return cdev; +} + +void +ccw_device_do_unreg_rereg(void *data) +{ + struct ccw_device *cdev; + struct subchannel *sch; + int need_rename; + + cdev = (struct ccw_device *)data; + sch = to_subchannel(cdev->dev.parent); + if (cdev->private->devno != sch->schib.pmcw.dev) { + /* + * The device number has changed. This is usually only when + * a device has been detached under VM and then re-appeared + * on another subchannel because of a different attachment + * order than before. Ideally, we should should just switch + * subchannels, but unfortunately, this is not possible with + * the current implementation. + * Instead, we search for the old subchannel for this device + * number and deregister so there are no collisions with the + * newly registered ccw_device. + * FIXME: Find another solution so the block layer doesn't + * get possibly sick... + */ + struct ccw_device *other_cdev; + + need_rename = 1; + other_cdev = get_disc_ccwdev_by_devno(sch->schib.pmcw.dev, + cdev); + if (other_cdev) { + struct subchannel *other_sch; + + other_sch = to_subchannel(other_cdev->dev.parent); + if (get_device(&other_sch->dev)) { + stsch(other_sch->irq, &other_sch->schib); + if (other_sch->schib.pmcw.dnv) { + other_sch->schib.pmcw.intparm = 0; + cio_modify(other_sch); + } + device_unregister(&other_sch->dev); + } + } + cdev->private->devno = sch->schib.pmcw.dev; + } else + need_rename = 0; + device_remove_files(&cdev->dev); + device_del(&cdev->dev); + if (need_rename) + snprintf (cdev->dev.bus_id, BUS_ID_SIZE, "0.0.%04x", + sch->schib.pmcw.dev); + if (device_add(&cdev->dev)) { + put_device(&cdev->dev); return; } - if (device_add_files(dev)) - device_unregister(dev); + if (device_add_files(&cdev->dev)) + device_unregister(&cdev->dev); } static void @@ -572,9 +654,7 @@ ccw_device_call_sch_unregister(void *data) struct subchannel *sch; sch = to_subchannel(cdev->dev.parent); - /* Check if device is registered. */ - if (!list_empty(&sch->dev.node)) - device_unregister(&sch->dev); + device_unregister(&sch->dev); /* Reset intparm to zeroes. */ sch->schib.pmcw.intparm = 0; cio_modify(sch); @@ -603,7 +683,7 @@ io_subchannel_recog_done(struct ccw_device *cdev) sch = to_subchannel(cdev->dev.parent); INIT_WORK(&cdev->private->kick_work, ccw_device_call_sch_unregister, (void *) cdev); - queue_work(ccw_device_work, &cdev->private->kick_work); + queue_work(slow_path_wq, &cdev->private->kick_work); break; case DEV_STATE_BOXED: /* Device did not respond in time. */