X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=drivers%2Fs390%2Fcio%2Fdevice.c;h=8e3053c2a451b4f5dc84d5b7fb77df52119d124c;hb=43bc926fffd92024b46cafaf7350d669ba9ca884;hp=bf652fa346f37abd3d1289963329bc229cf2d5f1;hpb=c7b5ebbddf7bcd3651947760f423e3783bbe6573;p=linux-2.6.git diff --git a/drivers/s390/cio/device.c b/drivers/s390/cio/device.c index bf652fa34..8e3053c2a 100644 --- a/drivers/s390/cio/device.c +++ b/drivers/s390/cio/device.c @@ -1,12 +1,11 @@ /* * drivers/s390/cio/device.c * bus driver for ccw devices - * $Revision: 1.124 $ * * Copyright (C) 2002 IBM Deutschland Entwicklung GmbH, * IBM Corporation * Author(s): Arnd Bergmann (arndb@de.ibm.com) - * Cornelia Huck (cohuck@de.ibm.com) + * Cornelia Huck (cornelia.huck@de.ibm.com) * Martin Schwidefsky (schwidefsky@de.ibm.com) */ #include @@ -22,6 +21,7 @@ #include #include +#include /* HZ */ #include "cio.h" #include "css.h" @@ -58,7 +58,7 @@ ccw_bus_match (struct device * dev, struct device_driver * drv) * Heavily modeled on pci and usb hotplug. */ static int -ccw_hotplug (struct device *dev, char **envp, int num_envp, +ccw_uevent (struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size) { struct ccw_device *cdev = to_ccwdev(dev); @@ -68,9 +68,6 @@ ccw_hotplug (struct device *dev, char **envp, int num_envp, if (!cdev) return -ENODEV; - if (cdev->private->state == DEV_STATE_NOT_OPER) - return -ENODEV; - /* what we want to pass to /sbin/hotplug */ envp[i++] = buffer; @@ -109,33 +106,29 @@ ccw_hotplug (struct device *dev, char **envp, int num_envp, return 0; } -struct bus_type ccw_bus_type = { - .name = "ccw", - .match = &ccw_bus_match, - .hotplug = &ccw_hotplug, -}; +struct bus_type ccw_bus_type; -static int io_subchannel_probe (struct device *); -static int io_subchannel_remove (struct device *); +static int io_subchannel_probe (struct subchannel *); +static int io_subchannel_remove (struct subchannel *); 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 *); +static void io_subchannel_shutdown(struct subchannel *); struct css_driver io_subchannel_driver = { .subchannel_type = SUBCHANNEL_TYPE_IO, .drv = { .name = "io_subchannel", .bus = &css_bus_type, - .probe = &io_subchannel_probe, - .remove = &io_subchannel_remove, - .shutdown = &io_subchannel_shutdown, }, .irq = io_subchannel_irq, .notify = io_subchannel_notify, .verify = io_subchannel_verify, .termination = io_subchannel_ioterm, + .probe = io_subchannel_probe, + .remove = io_subchannel_remove, + .shutdown = io_subchannel_shutdown, }; struct workqueue_struct *ccw_device_work; @@ -207,7 +200,7 @@ module_exit(cleanup_ccw_bus_type); * TODO: Split chpids and pimpampom up? Where is "in use" in the tree? */ static ssize_t -chpids_show (struct device * dev, char * buf) +chpids_show (struct device * dev, struct device_attribute *attr, char * buf) { struct subchannel *sch = to_subchannel(dev); struct ssd_info *ssd = &sch->ssd_info; @@ -222,7 +215,7 @@ chpids_show (struct device * dev, char * buf) } static ssize_t -pimpampom_show (struct device * dev, char * buf) +pimpampom_show (struct device * dev, struct device_attribute *attr, char * buf) { struct subchannel *sch = to_subchannel(dev); struct pmcw *pmcw = &sch->schib.pmcw; @@ -232,7 +225,7 @@ pimpampom_show (struct device * dev, char * buf) } static ssize_t -devtype_show (struct device *dev, char *buf) +devtype_show (struct device *dev, struct device_attribute *attr, char *buf) { struct ccw_device *cdev = to_ccwdev(dev); struct ccw_device_id *id = &(cdev->id); @@ -245,7 +238,7 @@ devtype_show (struct device *dev, char *buf) } static ssize_t -cutype_show (struct device *dev, char *buf) +cutype_show (struct device *dev, struct device_attribute *attr, char *buf) { struct ccw_device *cdev = to_ccwdev(dev); struct ccw_device_id *id = &(cdev->id); @@ -255,7 +248,24 @@ cutype_show (struct device *dev, char *buf) } static ssize_t -online_show (struct device *dev, char *buf) +modalias_show (struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ccw_device *cdev = to_ccwdev(dev); + struct ccw_device_id *id = &(cdev->id); + int ret; + + ret = sprintf(buf, "ccw:t%04Xm%02X", + id->cu_type, id->cu_model); + if (id->dev_type != 0) + ret += sprintf(buf + ret, "dt%04Xdm%02X\n", + id->dev_type, id->dev_model); + else + ret += sprintf(buf + ret, "dtdm\n"); + return ret; +} + +static ssize_t +online_show (struct device *dev, struct device_attribute *attr, char *buf) { struct ccw_device *cdev = to_ccwdev(dev); @@ -296,6 +306,14 @@ ccw_device_set_offline(struct ccw_device *cdev) cdev->online = 0; spin_lock_irq(cdev->ccwlock); ret = ccw_device_offline(cdev); + if (ret == -ENODEV) { + if (cdev->private->state != DEV_STATE_NOT_OPER) { + cdev->private->state = DEV_STATE_OFFLINE; + dev_fsm_event(cdev, DEV_EVENT_NOTOPER); + } + spin_unlock_irq(cdev->ccwlock); + return ret; + } spin_unlock_irq(cdev->ccwlock); if (ret == 0) wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev)); @@ -341,17 +359,17 @@ ccw_device_set_online(struct ccw_device *cdev) else pr_debug("ccw_device_offline returned %d, device %s\n", ret, cdev->dev.bus_id); - return (ret = 0) ? -ENODEV : ret; + return (ret == 0) ? -ENODEV : ret; } static ssize_t -online_store (struct device *dev, const char *buf, size_t count) +online_store (struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct ccw_device *cdev = to_ccwdev(dev); int i, force, ret; char *tmp; - if (atomic_compare_and_swap(0, 1, &cdev->private->onoff)) + if (atomic_cmpxchg(&cdev->private->onoff, 0, 1) != 0) return -EAGAIN; if (cdev->drv && !try_module_get(cdev->drv->owner)) { @@ -417,7 +435,7 @@ online_store (struct device *dev, const char *buf, size_t count) } static ssize_t -available_show (struct device *dev, char *buf) +available_show (struct device *dev, struct device_attribute *attr, char *buf) { struct ccw_device *cdev = to_ccwdev(dev); struct subchannel *sch; @@ -443,6 +461,7 @@ static DEVICE_ATTR(chpids, 0444, chpids_show, NULL); static DEVICE_ATTR(pimpampom, 0444, pimpampom_show, NULL); static DEVICE_ATTR(devtype, 0444, devtype_show, NULL); static DEVICE_ATTR(cutype, 0444, cutype_show, NULL); +static DEVICE_ATTR(modalias, 0444, modalias_show, NULL); static DEVICE_ATTR(online, 0644, online_show, online_store); extern struct device_attribute dev_attr_cmb_enable; static DEVICE_ATTR(availability, 0444, available_show, NULL); @@ -466,6 +485,7 @@ subchannel_add_files (struct device *dev) static struct attribute * ccwdev_attrs[] = { &dev_attr_devtype.attr, &dev_attr_cutype.attr, + &dev_attr_modalias.attr, &dev_attr_online.attr, &dev_attr_cmb_enable.attr, &dev_attr_availability.attr, @@ -501,43 +521,69 @@ ccw_device_register(struct ccw_device *cdev) if ((ret = device_add(dev))) return ret; - if ((ret = device_add_files(dev))) - device_del(dev); - + set_bit(1, &cdev->private->registered); + if ((ret = device_add_files(dev))) { + if (test_and_clear_bit(1, &cdev->private->registered)) + device_del(dev); + } return ret; } +struct match_data { + unsigned int devno; + unsigned int ssid; + struct ccw_device * sibling; +}; + +static int +match_devno(struct device * dev, void * data) +{ + struct match_data * d = (struct match_data *)data; + struct ccw_device * cdev; + + cdev = to_ccwdev(dev); + if ((cdev->private->state == DEV_STATE_DISCONNECTED) && + (cdev->private->devno == d->devno) && + (cdev->private->ssid == d->ssid) && + (cdev != d->sibling)) { + cdev->private->state = DEV_STATE_NOT_OPER; + return 1; + } + return 0; +} + static struct ccw_device * -get_disc_ccwdev_by_devno(unsigned int devno, struct ccw_device *sibling) +get_disc_ccwdev_by_devno(unsigned int devno, unsigned int ssid, + struct ccw_device *sibling) { - struct ccw_device *cdev; - struct list_head *entry; struct device *dev; + struct match_data data = { + .devno = devno, + .ssid = ssid, + .sibling = sibling, + }; - 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); + dev = bus_find_device(&ccw_bus_type, NULL, &data, match_devno); + + return dev ? to_ccwdev(dev) : NULL; +} + +static void +ccw_device_add_changed(void *data) +{ + + struct ccw_device *cdev; - return cdev; + cdev = (struct ccw_device *)data; + if (device_add(&cdev->dev)) { + put_device(&cdev->dev); + return; + } + set_bit(1, &cdev->private->registered); + if (device_add_files(&cdev->dev)) { + if (test_and_clear_bit(1, &cdev->private->registered)) + device_unregister(&cdev->dev); + } } extern int css_get_ssd_info(struct subchannel *sch); @@ -569,13 +615,13 @@ ccw_device_do_unreg_rereg(void *data) need_rename = 1; other_cdev = get_disc_ccwdev_by_devno(sch->schib.pmcw.dev, - cdev); + sch->schid.ssid, 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); + stsch(other_sch->schid, &other_sch->schib); if (other_sch->schib.pmcw.dnv) { other_sch->schib.pmcw.intparm = 0; cio_modify(other_sch); @@ -589,16 +635,14 @@ ccw_device_do_unreg_rereg(void *data) } else need_rename = 0; device_remove_files(&cdev->dev); - device_del(&cdev->dev); + if (test_and_clear_bit(1, &cdev->private->registered)) + 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(&cdev->dev)) - device_unregister(&cdev->dev); + snprintf (cdev->dev.bus_id, BUS_ID_SIZE, "0.%x.%04x", + sch->schid.ssid, sch->schib.pmcw.dev); + PREPARE_WORK(&cdev->private->kick_work, + ccw_device_add_changed, (void *)cdev); + queue_work(ccw_device_work, &cdev->private->kick_work); } static void @@ -620,11 +664,12 @@ io_subchannel_register(void *data) struct ccw_device *cdev; struct subchannel *sch; int ret; + unsigned long flags; cdev = (struct ccw_device *) data; sch = to_subchannel(cdev->dev.parent); - if (!list_empty(&sch->dev.children)) { + if (klist_node_attached(&cdev->dev.knode_parent)) { bus_rescan_devices(&ccw_bus_type); goto out; } @@ -634,10 +679,14 @@ io_subchannel_register(void *data) printk (KERN_WARNING "%s: could not register %s\n", __func__, cdev->dev.bus_id); put_device(&cdev->dev); - sch->dev.driver_data = 0; + spin_lock_irqsave(&sch->lock, flags); + sch->dev.driver_data = NULL; + spin_unlock_irqrestore(&sch->lock, flags); kfree (cdev->private); kfree (cdev); put_device(&sch->dev); + if (atomic_dec_and_test(&ccw_device_init_count)) + wake_up(&ccw_device_init_wq); return; } @@ -650,6 +699,8 @@ out: cdev->private->flags.recog_done = 1; put_device(&sch->dev); wake_up(&cdev->private->wait_q); + if (atomic_dec_and_test(&ccw_device_init_count)) + wake_up(&ccw_device_init_wq); } void @@ -686,9 +737,11 @@ io_subchannel_recog_done(struct ccw_device *cdev) if (!get_device(&cdev->dev)) break; sch = to_subchannel(cdev->dev.parent); - INIT_WORK(&cdev->private->kick_work, - ccw_device_call_sch_unregister, (void *) cdev); + PREPARE_WORK(&cdev->private->kick_work, + ccw_device_call_sch_unregister, (void *) cdev); queue_work(slow_path_wq, &cdev->private->kick_work); + if (atomic_dec_and_test(&ccw_device_init_count)) + wake_up(&ccw_device_init_wq); break; case DEV_STATE_BOXED: /* Device did not respond in time. */ @@ -699,13 +752,11 @@ io_subchannel_recog_done(struct ccw_device *cdev) */ if (!get_device(&cdev->dev)) break; - INIT_WORK(&cdev->private->kick_work, - io_subchannel_register, (void *) cdev); - queue_work(ccw_device_work, &cdev->private->kick_work); + PREPARE_WORK(&cdev->private->kick_work, + io_subchannel_register, (void *) cdev); + queue_work(slow_path_wq, &cdev->private->kick_work); break; } - if (atomic_dec_and_test(&ccw_device_init_count)) - wake_up(&ccw_device_init_wq); } static int @@ -717,18 +768,20 @@ io_subchannel_recog(struct ccw_device *cdev, struct subchannel *sch) sch->dev.driver_data = cdev; sch->driver = &io_subchannel_driver; cdev->ccwlock = &sch->lock; + /* Init private data. */ priv = cdev->private; priv->devno = sch->schib.pmcw.dev; - priv->irq = sch->irq; + priv->ssid = sch->schid.ssid; + priv->sch_no = sch->schid.sch_no; priv->state = DEV_STATE_NOT_OPER; INIT_LIST_HEAD(&priv->cmb_list); init_waitqueue_head(&priv->wait_q); init_timer(&priv->timer); /* Set an initial name for the device. */ - snprintf (cdev->dev.bus_id, BUS_ID_SIZE, "0.0.%04x", - sch->schib.pmcw.dev); + snprintf (cdev->dev.bus_id, BUS_ID_SIZE, "0.%x.%04x", + sch->schid.ssid, sch->schib.pmcw.dev); /* Increase counter of devices currently in recognition. */ atomic_inc(&ccw_device_init_count); @@ -745,13 +798,12 @@ io_subchannel_recog(struct ccw_device *cdev, struct subchannel *sch) } static int -io_subchannel_probe (struct device *pdev) +io_subchannel_probe (struct subchannel *sch) { - struct subchannel *sch; struct ccw_device *cdev; int rc; + unsigned long flags; - sch = to_subchannel(pdev); if (sch->dev.driver_data) { /* * This subchannel already has an associated ccw_device. @@ -774,22 +826,21 @@ io_subchannel_probe (struct device *pdev) get_device(&cdev->dev); return 0; } - cdev = kmalloc (sizeof(*cdev), GFP_KERNEL); + cdev = kzalloc (sizeof(*cdev), GFP_KERNEL); if (!cdev) return -ENOMEM; - memset(cdev, 0, sizeof(struct ccw_device)); - cdev->private = kmalloc(sizeof(struct ccw_device_private), + cdev->private = kzalloc(sizeof(struct ccw_device_private), GFP_KERNEL | GFP_DMA); if (!cdev->private) { kfree(cdev); return -ENOMEM; } - memset(cdev->private, 0, sizeof(struct ccw_device_private)); atomic_set(&cdev->private->onoff, 0); cdev->dev = (struct device) { - .parent = pdev, + .parent = &sch->dev, .release = ccw_device_release, }; + INIT_LIST_HEAD(&cdev->private->kick_work.entry); /* Do first half of device_register. */ device_initialize(&cdev->dev); @@ -799,9 +850,11 @@ io_subchannel_probe (struct device *pdev) return -ENODEV; } - rc = io_subchannel_recog(cdev, to_subchannel(pdev)); + rc = io_subchannel_recog(cdev, sch); if (rc) { - sch->dev.driver_data = 0; + spin_lock_irqsave(&sch->lock, flags); + sch->dev.driver_data = NULL; + spin_unlock_irqrestore(&sch->lock, flags); if (cdev->dev.release) cdev->dev.release(&cdev->dev); } @@ -809,24 +862,40 @@ io_subchannel_probe (struct device *pdev) return rc; } +static void +ccw_device_unregister(void *data) +{ + struct ccw_device *cdev; + + cdev = (struct ccw_device *)data; + if (test_and_clear_bit(1, &cdev->private->registered)) + device_unregister(&cdev->dev); + put_device(&cdev->dev); +} + static int -io_subchannel_remove (struct device *dev) +io_subchannel_remove (struct subchannel *sch) { struct ccw_device *cdev; + unsigned long flags; - if (!dev->driver_data) + if (!sch->dev.driver_data) return 0; - cdev = dev->driver_data; + cdev = sch->dev.driver_data; /* Set ccw device to not operational and drop reference. */ + spin_lock_irqsave(cdev->ccwlock, flags); + sch->dev.driver_data = NULL; cdev->private->state = DEV_STATE_NOT_OPER; + spin_unlock_irqrestore(cdev->ccwlock, flags); /* - * Careful here. Our ccw device might be yet unregistered when - * de-registering its subchannel (machine check during device - * recognition). Better look if the subchannel has children. + * Put unregistration on workqueue to avoid livelocks on the css bus + * semaphore. */ - if (!list_empty(&dev->children)) - device_unregister(&cdev->dev); - dev->driver_data = NULL; + if (get_device(&cdev->dev)) { + PREPARE_WORK(&cdev->private->kick_work, + ccw_device_unregister, (void *) cdev); + queue_work(ccw_device_work, &cdev->private->kick_work); + } return 0; } @@ -870,16 +939,14 @@ io_subchannel_ioterm(struct device *dev) } static void -io_subchannel_shutdown(struct device *dev) +io_subchannel_shutdown(struct subchannel *sch) { - struct subchannel *sch; struct ccw_device *cdev; int ret; - sch = to_subchannel(dev); - cdev = dev->driver_data; + cdev = sch->dev.driver_data; - if (cio_is_console(sch->irq)) + if (cio_is_console(sch->schid)) return; if (!sch->schib.pmcw.ena) /* Nothing to do. */ @@ -914,10 +981,6 @@ ccw_device_console_enable (struct ccw_device *cdev, struct subchannel *sch) cdev->dev = (struct device) { .parent = &sch->dev, }; - /* Initialize the subchannel structure */ - sch->dev.parent = &css_bus_device; - sch->dev.bus = &css_bus_type; - rc = io_subchannel_recog(cdev, sch); if (rc) return rc; @@ -947,7 +1010,7 @@ ccw_device_probe_console(void) int ret; if (xchg(&console_cdev_in_use, 1) != 0) - return NULL; + return ERR_PTR(-EBUSY); sch = cio_probe_console(); if (IS_ERR(sch)) { console_cdev_in_use = 0; @@ -970,30 +1033,29 @@ ccw_device_probe_console(void) /* * get ccw_device matching the busid, but only if owned by cdrv */ +static int +__ccwdev_check_busid(struct device *dev, void *id) +{ + char *bus_id; + + bus_id = (char *)id; + + return (strncmp(bus_id, dev->bus_id, BUS_ID_SIZE) == 0); +} + + struct ccw_device * get_ccwdev_by_busid(struct ccw_driver *cdrv, const char *bus_id) { - struct device *d, *dev; + struct device *dev; struct device_driver *drv; drv = get_driver(&cdrv->driver); if (!drv) - return 0; - - down_read(&drv->bus->subsys.rwsem); - - dev = NULL; - list_for_each_entry(d, &drv->devices, driver_list) { - dev = get_device(d); + return NULL; - if (dev && !strncmp(bus_id, dev->bus_id, BUS_ID_SIZE)) - break; - else if (dev) { - put_device(dev); - dev = NULL; - } - } - up_read(&drv->bus->subsys.rwsem); + dev = driver_find_device(drv, NULL, (void *)bus_id, + __ccwdev_check_busid); put_driver(drv); return dev ? to_ccwdev(dev) : 0; @@ -1056,6 +1118,14 @@ ccw_device_remove (struct device *dev) return 0; } +struct bus_type ccw_bus_type = { + .name = "ccw", + .match = ccw_bus_match, + .uevent = ccw_uevent, + .probe = ccw_device_probe, + .remove = ccw_device_remove, +}; + int ccw_driver_register (struct ccw_driver *cdriver) { @@ -1063,8 +1133,6 @@ ccw_driver_register (struct ccw_driver *cdriver) drv->bus = &ccw_bus_type; drv->name = cdriver->name; - drv->probe = ccw_device_probe; - drv->remove = ccw_device_remove; return driver_register(drv); } @@ -1075,6 +1143,16 @@ ccw_driver_unregister (struct ccw_driver *cdriver) driver_unregister(&cdriver->driver); } +/* Helper func for qdio. */ +struct subchannel_id +ccw_device_get_subchannel_id(struct ccw_device *cdev) +{ + struct subchannel *sch; + + sch = to_subchannel(cdev->dev.parent); + return sch->schid; +} + MODULE_LICENSE("GPL"); EXPORT_SYMBOL(ccw_device_set_online); EXPORT_SYMBOL(ccw_device_set_offline); @@ -1084,3 +1162,4 @@ EXPORT_SYMBOL(get_ccwdev_by_busid); EXPORT_SYMBOL(ccw_bus_type); EXPORT_SYMBOL(ccw_device_work); EXPORT_SYMBOL(ccw_device_notify_work); +EXPORT_SYMBOL_GPL(ccw_device_get_subchannel_id);