diff options
Diffstat (limited to 'drivers/s390/cio')
-rw-r--r-- | drivers/s390/cio/ccwgroup.c | 333 | ||||
-rw-r--r-- | drivers/s390/cio/ccwreq.c | 23 | ||||
-rw-r--r-- | drivers/s390/cio/chp.c | 2 | ||||
-rw-r--r-- | drivers/s390/cio/chsc_sch.c | 5 | ||||
-rw-r--r-- | drivers/s390/cio/cio.c | 19 | ||||
-rw-r--r-- | drivers/s390/cio/css.h | 2 | ||||
-rw-r--r-- | drivers/s390/cio/device.c | 13 | ||||
-rw-r--r-- | drivers/s390/cio/device.h | 13 | ||||
-rw-r--r-- | drivers/s390/cio/io_sch.h | 2 | ||||
-rw-r--r-- | drivers/s390/cio/qdio.h | 40 | ||||
-rw-r--r-- | drivers/s390/cio/qdio_debug.c | 12 | ||||
-rw-r--r-- | drivers/s390/cio/qdio_main.c | 105 | ||||
-rw-r--r-- | drivers/s390/cio/qdio_setup.c | 2 | ||||
-rw-r--r-- | drivers/s390/cio/qdio_thinint.c | 52 |
14 files changed, 325 insertions, 298 deletions
diff --git a/drivers/s390/cio/ccwgroup.c b/drivers/s390/cio/ccwgroup.c index 5c567414c4b..4f1989d27b1 100644 --- a/drivers/s390/cio/ccwgroup.c +++ b/drivers/s390/cio/ccwgroup.c @@ -29,31 +29,20 @@ /* a device matches a driver if all its slave devices match the same * entry of the driver */ -static int -ccwgroup_bus_match (struct device * dev, struct device_driver * drv) +static int ccwgroup_bus_match(struct device *dev, struct device_driver * drv) { - struct ccwgroup_device *gdev; - struct ccwgroup_driver *gdrv; - - gdev = to_ccwgroupdev(dev); - gdrv = to_ccwgroupdrv(drv); + struct ccwgroup_device *gdev = to_ccwgroupdev(dev); + struct ccwgroup_driver *gdrv = to_ccwgroupdrv(drv); if (gdev->creator_id == gdrv->driver_id) return 1; return 0; } -static int -ccwgroup_uevent (struct device *dev, struct kobj_uevent_env *env) -{ - /* TODO */ - return 0; -} static struct bus_type ccwgroup_bus_type; -static void -__ccwgroup_remove_symlinks(struct ccwgroup_device *gdev) +static void __ccwgroup_remove_symlinks(struct ccwgroup_device *gdev) { int i; char str[8]; @@ -63,7 +52,6 @@ __ccwgroup_remove_symlinks(struct ccwgroup_device *gdev) sysfs_remove_link(&gdev->dev.kobj, str); sysfs_remove_link(&gdev->cdev[i]->dev.kobj, "group_device"); } - } /* @@ -87,6 +75,87 @@ static void __ccwgroup_remove_cdev_refs(struct ccwgroup_device *gdev) } } +static int ccwgroup_set_online(struct ccwgroup_device *gdev) +{ + struct ccwgroup_driver *gdrv = to_ccwgroupdrv(gdev->dev.driver); + int ret = 0; + + if (atomic_cmpxchg(&gdev->onoff, 0, 1) != 0) + return -EAGAIN; + if (gdev->state == CCWGROUP_ONLINE) + goto out; + if (gdrv->set_online) + ret = gdrv->set_online(gdev); + if (ret) + goto out; + + gdev->state = CCWGROUP_ONLINE; +out: + atomic_set(&gdev->onoff, 0); + return ret; +} + +static int ccwgroup_set_offline(struct ccwgroup_device *gdev) +{ + struct ccwgroup_driver *gdrv = to_ccwgroupdrv(gdev->dev.driver); + int ret = 0; + + if (atomic_cmpxchg(&gdev->onoff, 0, 1) != 0) + return -EAGAIN; + if (gdev->state == CCWGROUP_OFFLINE) + goto out; + if (gdrv->set_offline) + ret = gdrv->set_offline(gdev); + if (ret) + goto out; + + gdev->state = CCWGROUP_OFFLINE; +out: + atomic_set(&gdev->onoff, 0); + return ret; +} + +static ssize_t ccwgroup_online_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ccwgroup_device *gdev = to_ccwgroupdev(dev); + struct ccwgroup_driver *gdrv = to_ccwgroupdrv(dev->driver); + unsigned long value; + int ret; + + if (!dev->driver) + return -EINVAL; + if (!try_module_get(gdrv->driver.owner)) + return -EINVAL; + + ret = strict_strtoul(buf, 0, &value); + if (ret) + goto out; + + if (value == 1) + ret = ccwgroup_set_online(gdev); + else if (value == 0) + ret = ccwgroup_set_offline(gdev); + else + ret = -EINVAL; +out: + module_put(gdrv->driver.owner); + return (ret == 0) ? count : ret; +} + +static ssize_t ccwgroup_online_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ccwgroup_device *gdev = to_ccwgroupdev(dev); + int online; + + online = (gdev->state == CCWGROUP_ONLINE) ? 1 : 0; + + return scnprintf(buf, PAGE_SIZE, "%d\n", online); +} + /* * Provide an 'ungroup' attribute so the user can remove group devices no * longer needed or accidentially created. Saves memory :) @@ -104,14 +173,13 @@ static void ccwgroup_ungroup_callback(struct device *dev) mutex_unlock(&gdev->reg_mutex); } -static ssize_t -ccwgroup_ungroup_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +static ssize_t ccwgroup_ungroup_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) { - struct ccwgroup_device *gdev; + struct ccwgroup_device *gdev = to_ccwgroupdev(dev); int rc; - gdev = to_ccwgroupdev(dev); - /* Prevent concurrent online/offline processing and ungrouping. */ if (atomic_cmpxchg(&gdev->onoff, 0, 1) != 0) return -EAGAIN; @@ -132,24 +200,35 @@ out: } return count; } - static DEVICE_ATTR(ungroup, 0200, NULL, ccwgroup_ungroup_store); +static DEVICE_ATTR(online, 0644, ccwgroup_online_show, ccwgroup_online_store); -static void -ccwgroup_release (struct device *dev) +static struct attribute *ccwgroup_attrs[] = { + &dev_attr_online.attr, + &dev_attr_ungroup.attr, + NULL, +}; +static struct attribute_group ccwgroup_attr_group = { + .attrs = ccwgroup_attrs, +}; +static const struct attribute_group *ccwgroup_attr_groups[] = { + &ccwgroup_attr_group, + NULL, +}; + +static void ccwgroup_release(struct device *dev) { kfree(to_ccwgroupdev(dev)); } -static int -__ccwgroup_create_symlinks(struct ccwgroup_device *gdev) +static int __ccwgroup_create_symlinks(struct ccwgroup_device *gdev) { char str[8]; int i, rc; for (i = 0; i < gdev->count; i++) { - rc = sysfs_create_link(&gdev->cdev[i]->dev.kobj, &gdev->dev.kobj, - "group_device"); + rc = sysfs_create_link(&gdev->cdev[i]->dev.kobj, + &gdev->dev.kobj, "group_device"); if (rc) { for (--i; i >= 0; i--) sysfs_remove_link(&gdev->cdev[i]->dev.kobj, @@ -159,8 +238,8 @@ __ccwgroup_create_symlinks(struct ccwgroup_device *gdev) } for (i = 0; i < gdev->count; i++) { sprintf(str, "cdev%d", i); - rc = sysfs_create_link(&gdev->dev.kobj, &gdev->cdev[i]->dev.kobj, - str); + rc = sysfs_create_link(&gdev->dev.kobj, + &gdev->cdev[i]->dev.kobj, str); if (rc) { for (--i; i >= 0; i--) { sprintf(str, "cdev%d", i); @@ -293,26 +372,17 @@ int ccwgroup_create_from_string(struct device *root, unsigned int creator_id, } dev_set_name(&gdev->dev, "%s", dev_name(&gdev->cdev[0]->dev)); - + gdev->dev.groups = ccwgroup_attr_groups; rc = device_add(&gdev->dev); if (rc) goto error; - get_device(&gdev->dev); - rc = device_create_file(&gdev->dev, &dev_attr_ungroup); - + rc = __ccwgroup_create_symlinks(gdev); if (rc) { - device_unregister(&gdev->dev); + device_del(&gdev->dev); goto error; } - - rc = __ccwgroup_create_symlinks(gdev); - if (!rc) { - mutex_unlock(&gdev->reg_mutex); - put_device(&gdev->dev); - return 0; - } - device_remove_file(&gdev->dev, &dev_attr_ungroup); - device_unregister(&gdev->dev); + mutex_unlock(&gdev->reg_mutex); + return 0; error: for (i = 0; i < num_devices; i++) if (gdev->cdev[i]) { @@ -330,7 +400,15 @@ error: EXPORT_SYMBOL(ccwgroup_create_from_string); static int ccwgroup_notifier(struct notifier_block *nb, unsigned long action, - void *data); + void *data) +{ + struct device *dev = data; + + if (action == BUS_NOTIFY_UNBIND_DRIVER) + device_schedule_callback(dev, ccwgroup_ungroup_callback); + + return NOTIFY_OK; +} static struct notifier_block ccwgroup_nb = { .notifier_call = ccwgroup_notifier @@ -362,138 +440,21 @@ module_exit(cleanup_ccwgroup); /************************** driver stuff ******************************/ -static int -ccwgroup_set_online(struct ccwgroup_device *gdev) +static int ccwgroup_probe(struct device *dev) { - struct ccwgroup_driver *gdrv; - int ret; - - if (atomic_cmpxchg(&gdev->onoff, 0, 1) != 0) - return -EAGAIN; - if (gdev->state == CCWGROUP_ONLINE) { - ret = 0; - goto out; - } - if (!gdev->dev.driver) { - ret = -EINVAL; - goto out; - } - gdrv = to_ccwgroupdrv (gdev->dev.driver); - if ((ret = gdrv->set_online ? gdrv->set_online(gdev) : 0)) - goto out; - - gdev->state = CCWGROUP_ONLINE; - out: - atomic_set(&gdev->onoff, 0); - return ret; -} - -static int -ccwgroup_set_offline(struct ccwgroup_device *gdev) -{ - struct ccwgroup_driver *gdrv; - int ret; - - if (atomic_cmpxchg(&gdev->onoff, 0, 1) != 0) - return -EAGAIN; - if (gdev->state == CCWGROUP_OFFLINE) { - ret = 0; - goto out; - } - if (!gdev->dev.driver) { - ret = -EINVAL; - goto out; - } - gdrv = to_ccwgroupdrv (gdev->dev.driver); - if ((ret = gdrv->set_offline ? gdrv->set_offline(gdev) : 0)) - goto out; - - gdev->state = CCWGROUP_OFFLINE; - out: - atomic_set(&gdev->onoff, 0); - return ret; -} - -static ssize_t -ccwgroup_online_store (struct device *dev, struct device_attribute *attr, const char *buf, size_t count) -{ - struct ccwgroup_device *gdev; - struct ccwgroup_driver *gdrv; - unsigned long value; - int ret; - - if (!dev->driver) - return -ENODEV; - - gdev = to_ccwgroupdev(dev); - gdrv = to_ccwgroupdrv(dev->driver); - - if (!try_module_get(gdrv->driver.owner)) - return -EINVAL; - - ret = strict_strtoul(buf, 0, &value); - if (ret) - goto out; - - if (value == 1) - ret = ccwgroup_set_online(gdev); - else if (value == 0) - ret = ccwgroup_set_offline(gdev); - else - ret = -EINVAL; -out: - module_put(gdrv->driver.owner); - return (ret == 0) ? count : ret; -} - -static ssize_t -ccwgroup_online_show (struct device *dev, struct device_attribute *attr, char *buf) -{ - int online; - - online = (to_ccwgroupdev(dev)->state == CCWGROUP_ONLINE); - - return sprintf(buf, online ? "1\n" : "0\n"); -} - -static DEVICE_ATTR(online, 0644, ccwgroup_online_show, ccwgroup_online_store); - -static int -ccwgroup_probe (struct device *dev) -{ - struct ccwgroup_device *gdev; - struct ccwgroup_driver *gdrv; - - int ret; - - gdev = to_ccwgroupdev(dev); - gdrv = to_ccwgroupdrv(dev->driver); - - if ((ret = device_create_file(dev, &dev_attr_online))) - return ret; - - ret = gdrv->probe ? gdrv->probe(gdev) : -ENODEV; - if (ret) - device_remove_file(dev, &dev_attr_online); + struct ccwgroup_device *gdev = to_ccwgroupdev(dev); + struct ccwgroup_driver *gdrv = to_ccwgroupdrv(dev->driver); - return ret; + return gdrv->probe ? gdrv->probe(gdev) : -ENODEV; } -static int -ccwgroup_remove (struct device *dev) +static int ccwgroup_remove(struct device *dev) { - struct ccwgroup_device *gdev; - struct ccwgroup_driver *gdrv; - - device_remove_file(dev, &dev_attr_online); - device_remove_file(dev, &dev_attr_ungroup); + struct ccwgroup_device *gdev = to_ccwgroupdev(dev); + struct ccwgroup_driver *gdrv = to_ccwgroupdrv(dev->driver); if (!dev->driver) return 0; - - gdev = to_ccwgroupdev(dev); - gdrv = to_ccwgroupdrv(dev->driver); - if (gdrv->remove) gdrv->remove(gdev); @@ -502,15 +463,11 @@ ccwgroup_remove (struct device *dev) static void ccwgroup_shutdown(struct device *dev) { - struct ccwgroup_device *gdev; - struct ccwgroup_driver *gdrv; + struct ccwgroup_device *gdev = to_ccwgroupdev(dev); + struct ccwgroup_driver *gdrv = to_ccwgroupdrv(dev->driver); if (!dev->driver) return; - - gdev = to_ccwgroupdev(dev); - gdrv = to_ccwgroupdrv(dev->driver); - if (gdrv->shutdown) gdrv->shutdown(gdev); } @@ -586,26 +543,12 @@ static const struct dev_pm_ops ccwgroup_pm_ops = { static struct bus_type ccwgroup_bus_type = { .name = "ccwgroup", .match = ccwgroup_bus_match, - .uevent = ccwgroup_uevent, .probe = ccwgroup_probe, .remove = ccwgroup_remove, .shutdown = ccwgroup_shutdown, .pm = &ccwgroup_pm_ops, }; - -static int ccwgroup_notifier(struct notifier_block *nb, unsigned long action, - void *data) -{ - struct device *dev = data; - - if (action == BUS_NOTIFY_UNBIND_DRIVER) - device_schedule_callback(dev, ccwgroup_ungroup_callback); - - return NOTIFY_OK; -} - - /** * ccwgroup_driver_register() - register a ccw group driver * @cdriver: driver to be registered @@ -619,9 +562,9 @@ int ccwgroup_driver_register(struct ccwgroup_driver *cdriver) return driver_register(&cdriver->driver); } +EXPORT_SYMBOL(ccwgroup_driver_register); -static int -__ccwgroup_match_all(struct device *dev, void *data) +static int __ccwgroup_match_all(struct device *dev, void *data) { return 1; } @@ -652,6 +595,7 @@ void ccwgroup_driver_unregister(struct ccwgroup_driver *cdriver) put_driver(&cdriver->driver); driver_unregister(&cdriver->driver); } +EXPORT_SYMBOL(ccwgroup_driver_unregister); /** * ccwgroup_probe_ccwdev() - probe function for slave devices @@ -666,6 +610,7 @@ int ccwgroup_probe_ccwdev(struct ccw_device *cdev) { return 0; } +EXPORT_SYMBOL(ccwgroup_probe_ccwdev); /** * ccwgroup_remove_ccwdev() - remove function for slave devices @@ -702,9 +647,5 @@ void ccwgroup_remove_ccwdev(struct ccw_device *cdev) /* Release ccwgroup device reference for local processing. */ put_device(&gdev->dev); } - -MODULE_LICENSE("GPL"); -EXPORT_SYMBOL(ccwgroup_driver_register); -EXPORT_SYMBOL(ccwgroup_driver_unregister); -EXPORT_SYMBOL(ccwgroup_probe_ccwdev); EXPORT_SYMBOL(ccwgroup_remove_ccwdev); +MODULE_LICENSE("GPL"); diff --git a/drivers/s390/cio/ccwreq.c b/drivers/s390/cio/ccwreq.c index d15f8b4d78b..5156264d0c7 100644 --- a/drivers/s390/cio/ccwreq.c +++ b/drivers/s390/cio/ccwreq.c @@ -1,10 +1,13 @@ /* * Handling of internal CCW device requests. * - * Copyright IBM Corp. 2009 + * Copyright IBM Corp. 2009, 2011 * Author(s): Peter Oberparleiter <peter.oberparleiter@de.ibm.com> */ +#define KMSG_COMPONENT "cio" +#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt + #include <linux/types.h> #include <linux/err.h> #include <asm/ccwdev.h> @@ -323,7 +326,21 @@ void ccw_request_timeout(struct ccw_device *cdev) { struct subchannel *sch = to_subchannel(cdev->dev.parent); struct ccw_request *req = &cdev->private->req; - int rc; + int rc = -ENODEV, chp; + + if (cio_update_schib(sch)) + goto err; + + for (chp = 0; chp < 8; chp++) { + if ((0x80 >> chp) & sch->schib.pmcw.lpum) + pr_warning("%s: No interrupt was received within %lus " + "(CS=%02x, DS=%02x, CHPID=%x.%02x)\n", + dev_name(&cdev->dev), req->timeout / HZ, + scsw_cstat(&sch->schib.scsw), + scsw_dstat(&sch->schib.scsw), + sch->schid.cssid, + sch->schib.pmcw.chpid[chp]); + } if (!ccwreq_next_path(cdev)) { /* set the final return code for this request */ @@ -342,7 +359,7 @@ err: * ccw_request_notoper - notoper handler for I/O request procedure * @cdev: ccw device * - * Handle timeout during I/O request procedure. + * Handle notoper during I/O request procedure. */ void ccw_request_notoper(struct ccw_device *cdev) { diff --git a/drivers/s390/cio/chp.c b/drivers/s390/cio/chp.c index 2d32233943a..e792436c927 100644 --- a/drivers/s390/cio/chp.c +++ b/drivers/s390/cio/chp.c @@ -10,6 +10,8 @@ #include <linux/bug.h> #include <linux/workqueue.h> #include <linux/spinlock.h> +#include <linux/export.h> +#include <linux/sched.h> #include <linux/init.h> #include <linux/jiffies.h> #include <linux/wait.h> diff --git a/drivers/s390/cio/chsc_sch.c b/drivers/s390/cio/chsc_sch.c index e950f1ad4dd..0c87b0fc771 100644 --- a/drivers/s390/cio/chsc_sch.c +++ b/drivers/s390/cio/chsc_sch.c @@ -1,7 +1,7 @@ /* * Driver for s390 chsc subchannels * - * Copyright IBM Corp. 2008, 2009 + * Copyright IBM Corp. 2008, 2011 * * Author(s): Cornelia Huck <cornelia.huck@de.ibm.com> * @@ -12,6 +12,7 @@ #include <linux/module.h> #include <linux/uaccess.h> #include <linux/miscdevice.h> +#include <linux/kernel_stat.h> #include <asm/compat.h> #include <asm/cio.h> @@ -56,6 +57,8 @@ static void chsc_subchannel_irq(struct subchannel *sch) CHSC_LOG(4, "irb"); CHSC_LOG_HEX(4, irb, sizeof(*irb)); + kstat_cpu(smp_processor_id()).irqs[IOINT_CSC]++; + /* Copy irb to provided request and set done. */ if (!request) { CHSC_MSG(0, "Interrupt on sch 0.%x.%04x with no request\n", diff --git a/drivers/s390/cio/cio.c b/drivers/s390/cio/cio.c index eb3140ee821..dc67c397449 100644 --- a/drivers/s390/cio/cio.c +++ b/drivers/s390/cio/cio.c @@ -622,6 +622,7 @@ void __irq_entry do_IRQ(struct pt_regs *regs) sch = (struct subchannel *)(unsigned long)tpi_info->intparm; if (!sch) { /* Clear pending interrupt condition. */ + kstat_cpu(smp_processor_id()).irqs[IOINT_CIO]++; tsch(tpi_info->schid, irb); continue; } @@ -634,7 +635,10 @@ void __irq_entry do_IRQ(struct pt_regs *regs) /* Call interrupt handler if there is one. */ if (sch->driver && sch->driver->irq) sch->driver->irq(sch); - } + else + kstat_cpu(smp_processor_id()).irqs[IOINT_CIO]++; + } else + kstat_cpu(smp_processor_id()).irqs[IOINT_CIO]++; spin_unlock(sch->lock); /* * Are more interrupts pending? @@ -667,18 +671,23 @@ static int cio_tpi(void) tpi_info = (struct tpi_info *)&S390_lowcore.subchannel_id; if (tpi(NULL) != 1) return 0; + kstat_cpu(smp_processor_id()).irqs[IO_INTERRUPT]++; if (tpi_info->adapter_IO) { do_adapter_IO(tpi_info->isc); return 1; } irb = (struct irb *)&S390_lowcore.irb; /* Store interrupt response block to lowcore. */ - if (tsch(tpi_info->schid, irb) != 0) + if (tsch(tpi_info->schid, irb) != 0) { /* Not status pending or not operational. */ + kstat_cpu(smp_processor_id()).irqs[IOINT_CIO]++; return 1; + } sch = (struct subchannel *)(unsigned long)tpi_info->intparm; - if (!sch) + if (!sch) { + kstat_cpu(smp_processor_id()).irqs[IOINT_CIO]++; return 1; + } irq_context = in_interrupt(); if (!irq_context) local_bh_disable(); @@ -687,6 +696,8 @@ static int cio_tpi(void) memcpy(&sch->schib.scsw, &irb->scsw, sizeof(union scsw)); if (sch->driver && sch->driver->irq) sch->driver->irq(sch); + else + kstat_cpu(smp_processor_id()).irqs[IOINT_CIO]++; spin_unlock(sch->lock); irq_exit(); if (!irq_context) @@ -1058,7 +1069,7 @@ void reipl_ccw_dev(struct ccw_dev_id *devid) { struct subchannel_id schid; - s390_reset_system(); + s390_reset_system(NULL, NULL); if (reipl_find_schid(devid, &schid) != 0) panic("IPL Device not found\n"); do_reipl_asm(*((__u32*)&schid)); diff --git a/drivers/s390/cio/css.h b/drivers/s390/cio/css.h index 80ebdddf774..33bb4d891e1 100644 --- a/drivers/s390/cio/css.h +++ b/drivers/s390/cio/css.h @@ -133,6 +133,8 @@ struct channel_subsystem { extern struct channel_subsystem *channel_subsystems[]; +void channel_subsystem_reinit(void); + /* Helper functions to build lists for the slow path. */ void css_schedule_eval(struct subchannel_id schid); void css_schedule_eval_all(void); diff --git a/drivers/s390/cio/device.c b/drivers/s390/cio/device.c index 8e04c00cf0a..d734f4a0eca 100644 --- a/drivers/s390/cio/device.c +++ b/drivers/s390/cio/device.c @@ -21,6 +21,7 @@ #include <linux/device.h> #include <linux/workqueue.h> #include <linux/timer.h> +#include <linux/kernel_stat.h> #include <asm/ccwdev.h> #include <asm/cio.h> @@ -747,6 +748,7 @@ static int io_subchannel_initialize_dev(struct subchannel *sch, struct ccw_device *cdev) { cdev->private->cdev = cdev; + cdev->private->int_class = IOINT_CIO; atomic_set(&cdev->private->onoff, 0); cdev->dev.parent = &sch->dev; cdev->dev.release = ccw_device_release; @@ -1010,6 +1012,8 @@ static void io_subchannel_irq(struct subchannel *sch) CIO_TRACE_EVENT(6, dev_name(&sch->dev)); if (cdev) dev_fsm_event(cdev, DEV_EVENT_INTERRUPT); + else + kstat_cpu(smp_processor_id()).irqs[IOINT_CIO]++; } void io_subchannel_init_config(struct subchannel *sch) @@ -1621,6 +1625,7 @@ ccw_device_probe_console(void) memset(&console_private, 0, sizeof(struct ccw_device_private)); console_cdev.private = &console_private; console_private.cdev = &console_cdev; + console_private.int_class = IOINT_CIO; ret = ccw_device_console_enable(&console_cdev, sch); if (ret) { cio_release_console(); @@ -1702,11 +1707,18 @@ ccw_device_probe (struct device *dev) int ret; cdev->drv = cdrv; /* to let the driver call _set_online */ + /* Note: we interpret class 0 in this context as an uninitialized + * field since it translates to a non-I/O interrupt class. */ + if (cdrv->int_class != 0) + cdev->private->int_class = cdrv->int_class; + else + cdev->private->int_class = IOINT_CIO; ret = cdrv->probe ? cdrv->probe(cdev) : -ENODEV; if (ret) { cdev->drv = NULL; + cdev->private->int_class = IOINT_CIO; return ret; } @@ -1740,6 +1752,7 @@ ccw_device_remove (struct device *dev) } ccw_device_set_timeout(cdev, 0); cdev->drv = NULL; + cdev->private->int_class = IOINT_CIO; return 0; } diff --git a/drivers/s390/cio/device.h b/drivers/s390/cio/device.h index 0b7245c72d5..179824b3082 100644 --- a/drivers/s390/cio/device.h +++ b/drivers/s390/cio/device.h @@ -5,6 +5,7 @@ #include <linux/atomic.h> #include <linux/wait.h> #include <linux/notifier.h> +#include <linux/kernel_stat.h> #include "io_sch.h" /* @@ -56,7 +57,17 @@ extern fsm_func_t *dev_jumptable[NR_DEV_STATES][NR_DEV_EVENTS]; static inline void dev_fsm_event(struct ccw_device *cdev, enum dev_event dev_event) { - dev_jumptable[cdev->private->state][dev_event](cdev, dev_event); + int state = cdev->private->state; + + if (dev_event == DEV_EVENT_INTERRUPT) { + if (state == DEV_STATE_ONLINE) + kstat_cpu(smp_processor_id()). + irqs[cdev->private->int_class]++; + else if (state != DEV_STATE_CMFCHANGE && + state != DEV_STATE_CMFUPDATE) + kstat_cpu(smp_processor_id()).irqs[IOINT_CIO]++; + } + dev_jumptable[state][dev_event](cdev, dev_event); } /* diff --git a/drivers/s390/cio/io_sch.h b/drivers/s390/cio/io_sch.h index ba31ad88f4f..2ebb492a5c1 100644 --- a/drivers/s390/cio/io_sch.h +++ b/drivers/s390/cio/io_sch.h @@ -4,6 +4,7 @@ #include <linux/types.h> #include <asm/schid.h> #include <asm/ccwdev.h> +#include <asm/irq.h> #include "css.h" #include "orb.h" @@ -157,6 +158,7 @@ struct ccw_device_private { struct list_head cmb_list; /* list of measured devices */ u64 cmb_start_time; /* clock value of cmb reset */ void *cmb_wait; /* deferred cmb enable/disable */ + enum interruption_class int_class; }; static inline int rsch(struct subchannel_id schid) diff --git a/drivers/s390/cio/qdio.h b/drivers/s390/cio/qdio.h index 3dd86441da3..b962ffbc080 100644 --- a/drivers/s390/cio/qdio.h +++ b/drivers/s390/cio/qdio.h @@ -18,14 +18,6 @@ #define QDIO_BUSY_BIT_RETRIES 1000 /* = 10s retry time */ #define QDIO_INPUT_THRESHOLD (500 << 12) /* 500 microseconds */ -/* - * if an asynchronous HiperSockets queue runs full, the 10 seconds timer wait - * till next initiative to give transmitted skbs back to the stack is too long. - * Therefore polling is started in case of multicast queue is filled more - * than 50 percent. - */ -#define QDIO_IQDIO_POLL_LVL 65 /* HS multicast queue */ - enum qdio_irq_states { QDIO_IRQ_STATE_INACTIVE, QDIO_IRQ_STATE_ESTABLISHED, @@ -290,6 +282,9 @@ struct qdio_q { /* error condition during a data transfer */ unsigned int qdio_error; + /* last scan of the queue */ + u64 timestamp; + struct tasklet_struct tasklet; struct qdio_queue_perf_stat q_stats; @@ -423,31 +418,7 @@ static inline int multicast_outbound(struct qdio_q *q) #define queue_irqs_disabled(q) \ (test_bit(QDIO_QUEUE_IRQS_DISABLED, &q->u.in.queue_irq_state) != 0) -#define TIQDIO_SHARED_IND 63 - -/* device state change indicators */ -struct indicator_t { - u32 ind; /* u32 because of compare-and-swap performance */ - atomic_t count; /* use count, 0 or 1 for non-shared indicators */ -}; - -extern struct indicator_t *q_indicators; - -static inline int has_multiple_inq_on_dsci(struct qdio_irq *irq) -{ - return irq->nr_input_qs > 1; -} - -static inline int references_shared_dsci(struct qdio_irq *irq) -{ - return irq->dsci == &q_indicators[TIQDIO_SHARED_IND].ind; -} - -static inline int shared_ind(struct qdio_q *q) -{ - struct qdio_irq *i = q->irq_ptr; - return references_shared_dsci(i) || has_multiple_inq_on_dsci(i); -} +extern u64 last_ai_time; /* prototypes for thin interrupt */ void qdio_setup_thinint(struct qdio_irq *irq_ptr); @@ -460,7 +431,8 @@ int tiqdio_allocate_memory(void); void tiqdio_free_memory(void); int tiqdio_register_thinints(void); void tiqdio_unregister_thinints(void); - +void clear_nonshared_ind(struct qdio_irq *); +int test_nonshared_ind(struct qdio_irq *); /* prototypes for setup */ void qdio_inbound_processing(unsigned long data); diff --git a/drivers/s390/cio/qdio_debug.c b/drivers/s390/cio/qdio_debug.c index aaf7f935bfd..29021f4e96b 100644 --- a/drivers/s390/cio/qdio_debug.c +++ b/drivers/s390/cio/qdio_debug.c @@ -7,6 +7,8 @@ */ #include <linux/seq_file.h> #include <linux/debugfs.h> +#include <linux/uaccess.h> +#include <linux/export.h> #include <asm/debug.h> #include "qdio_debug.h" #include "qdio.h" @@ -54,15 +56,17 @@ static int qstat_show(struct seq_file *m, void *v) if (!q) return 0; - seq_printf(m, "DSCI: %d nr_used: %d\n", - *(u32 *)q->irq_ptr->dsci, atomic_read(&q->nr_buf_used)); - seq_printf(m, "ftc: %d last_move: %d\n", + seq_printf(m, "Timestamp: %Lx Last AI: %Lx\n", + q->timestamp, last_ai_time); + seq_printf(m, "nr_used: %d ftc: %d last_move: %d\n", + atomic_read(&q->nr_buf_used), q->first_to_check, q->last_move); if (q->is_input_q) { seq_printf(m, "polling: %d ack start: %d ack count: %d\n", q->u.in.polling, q->u.in.ack_start, q->u.in.ack_count); - seq_printf(m, "IRQs disabled: %u\n", + seq_printf(m, "DSCI: %d IRQs disabled: %u\n", + *(u32 *)q->irq_ptr->dsci, test_bit(QDIO_QUEUE_IRQS_DISABLED, &q->u.in.queue_irq_state)); } diff --git a/drivers/s390/cio/qdio_main.c b/drivers/s390/cio/qdio_main.c index 9a122280246..3ef8d071c64 100644 --- a/drivers/s390/cio/qdio_main.c +++ b/drivers/s390/cio/qdio_main.c @@ -15,7 +15,6 @@ #include <linux/delay.h> #include <linux/gfp.h> #include <linux/io.h> -#include <linux/kernel_stat.h> #include <linux/atomic.h> #include <asm/debug.h> #include <asm/qdio.h> @@ -105,9 +104,12 @@ static inline int qdio_check_ccq(struct qdio_q *q, unsigned int ccq) /* all done or next buffer state different */ if (ccq == 0 || ccq == 32) return 0; - /* not all buffers processed */ - if (ccq == 96 || ccq == 97) + /* no buffer processed */ + if (ccq == 97) return 1; + /* not all buffers processed */ + if (ccq == 96) + return 2; /* notify devices immediately */ DBF_ERROR("%4x ccq:%3d", SCH_NO(q), ccq); return -EIO; @@ -127,10 +129,8 @@ static inline int qdio_check_ccq(struct qdio_q *q, unsigned int ccq) static int qdio_do_eqbs(struct qdio_q *q, unsigned char *state, int start, int count, int auto_ack) { + int rc, tmp_count = count, tmp_start = start, nr = q->nr, retried = 0; unsigned int ccq = 0; - int tmp_count = count, tmp_start = start; - int nr = q->nr; - int rc; BUG_ON(!q->irq_ptr->sch_token); qperf_inc(q, eqbs); @@ -141,29 +141,34 @@ again: ccq = do_eqbs(q->irq_ptr->sch_token, state, nr, &tmp_start, &tmp_count, auto_ack); rc = qdio_check_ccq(q, ccq); - - /* At least one buffer was processed, return and extract the remaining - * buffers later. - */ - if ((ccq == 96) && (count != tmp_count)) { - qperf_inc(q, eqbs_partial); - return (count - tmp_count); - } + if (!rc) + return count - tmp_count; if (rc == 1) { DBF_DEV_EVENT(DBF_WARN, q->irq_ptr, "EQBS again:%2d", ccq); goto again; } - if (rc < 0) { - DBF_ERROR("%4x EQBS ERROR", SCH_NO(q)); - DBF_ERROR("%3d%3d%2d", count, tmp_count, nr); - q->handler(q->irq_ptr->cdev, - QDIO_ERROR_ACTIVATE_CHECK_CONDITION, - 0, -1, -1, q->irq_ptr->int_parm); - return 0; + if (rc == 2) { + BUG_ON(tmp_count == count); + qperf_inc(q, eqbs_partial); + DBF_DEV_EVENT(DBF_WARN, q->irq_ptr, "EQBS part:%02x", + tmp_count); + /* + * Retry once, if that fails bail out and process the + * extracted buffers before trying again. + */ + if (!retried++) + goto again; + else + return count - tmp_count; } - return count - tmp_count; + + DBF_ERROR("%4x EQBS ERROR", SCH_NO(q)); + DBF_ERROR("%3d%3d%2d", count, tmp_count, nr); + q->handler(q->irq_ptr->cdev, QDIO_ERROR_ACTIVATE_CHECK_CONDITION, + 0, -1, -1, q->irq_ptr->int_parm); + return 0; } /** @@ -196,21 +201,22 @@ static int qdio_do_sqbs(struct qdio_q *q, unsigned char state, int start, again: ccq = do_sqbs(q->irq_ptr->sch_token, state, nr, &tmp_start, &tmp_count); rc = qdio_check_ccq(q, ccq); - if (rc == 1) { + if (!rc) { + WARN_ON(tmp_count); + return count - tmp_count; + } + + if (rc == 1 || rc == 2) { DBF_DEV_EVENT(DBF_INFO, q->irq_ptr, "SQBS again:%2d", ccq); qperf_inc(q, sqbs_partial); goto again; } - if (rc < 0) { - DBF_ERROR("%4x SQBS ERROR", SCH_NO(q)); - DBF_ERROR("%3d%3d%2d", count, tmp_count, nr); - q->handler(q->irq_ptr->cdev, - QDIO_ERROR_ACTIVATE_CHECK_CONDITION, - 0, -1, -1, q->irq_ptr->int_parm); - return 0; - } - WARN_ON(tmp_count); - return count - tmp_count; + + DBF_ERROR("%4x SQBS ERROR", SCH_NO(q)); + DBF_ERROR("%3d%3d%2d", count, tmp_count, nr); + q->handler(q->irq_ptr->cdev, QDIO_ERROR_ACTIVATE_CHECK_CONDITION, + 0, -1, -1, q->irq_ptr->int_parm); + return 0; } /* returns number of examined buffers and their common state in *state */ @@ -275,7 +281,7 @@ static inline int set_buf_state(struct qdio_q *q, int bufnr, } /* set slsb states to initial state */ -void qdio_init_buf_states(struct qdio_irq *irq_ptr) +static void qdio_init_buf_states(struct qdio_irq *irq_ptr) { struct qdio_q *q; int i; @@ -444,7 +450,7 @@ static void process_buffer_error(struct qdio_q *q, int count) qperf_inc(q, target_full); DBF_DEV_EVENT(DBF_INFO, q->irq_ptr, "OUTFULL FTC:%02x", q->first_to_check); - return; + goto set; } DBF_ERROR("%4x BUF ERROR", SCH_NO(q)); @@ -454,6 +460,7 @@ static void process_buffer_error(struct qdio_q *q, int count) q->sbal[q->first_to_check]->element[14].sflags, q->sbal[q->first_to_check]->element[15].sflags); +set: /* * Interrupts may be avoided as long as the error is present * so change the buffer state immediately to avoid starvation. @@ -511,6 +518,8 @@ static int get_inbound_buffer_frontier(struct qdio_q *q) int count, stop; unsigned char state = 0; + q->timestamp = get_clock_fast(); + /* * Don't check 128 buffers, as otherwise qdio_inbound_q_moved * would return 0. @@ -780,6 +789,8 @@ static int get_outbound_buffer_frontier(struct qdio_q *q) int count, stop; unsigned char state = 0; + q->timestamp = get_clock_fast(); + if (need_siga_sync(q)) if (((queue_type(q) != QDIO_IQDIO_QFMT) && !pci_out_supported(q)) || @@ -910,21 +921,13 @@ static void __qdio_outbound_processing(struct qdio_q *q) if (!pci_out_supported(q) && !qdio_outbound_q_done(q)) goto sched; - /* bail out for HiperSockets unicast queues */ - if (queue_type(q) == QDIO_IQDIO_QFMT && !multicast_outbound(q)) - return; - - if ((queue_type(q) == QDIO_IQDIO_QFMT) && - (atomic_read(&q->nr_buf_used)) > QDIO_IQDIO_POLL_LVL) - goto sched; - if (q->u.out.pci_out_enabled) return; /* * Now we know that queue type is either qeth without pci enabled - * or HiperSockets multicast. Make sure buffer switch from PRIMED to - * EMPTY is noticed and outbound_handler is called after some time. + * or HiperSockets. Make sure buffer switch from PRIMED to EMPTY + * is noticed and outbound_handler is called after some time. */ if (qdio_outbound_q_done(q)) del_timer(&q->u.out.timer); @@ -1070,6 +1073,7 @@ static void qdio_handle_activate_check(struct ccw_device *cdev, { struct qdio_irq *irq_ptr = cdev->private->qdio_data; struct qdio_q *q; + int count; DBF_ERROR("%4x ACT CHECK", irq_ptr->schid.sch_no); DBF_ERROR("intp :%lx", intparm); @@ -1083,8 +1087,10 @@ static void qdio_handle_activate_check(struct ccw_device *cdev, dump_stack(); goto no_handler; } + + count = sub_buf(q->first_to_check, q->first_to_kick); q->handler(q->irq_ptr->cdev, QDIO_ERROR_ACTIVATE_CHECK_CONDITION, - 0, -1, -1, irq_ptr->int_parm); + q->nr, q->first_to_kick, count, irq_ptr->int_parm); no_handler: qdio_set_state(irq_ptr, QDIO_IRQ_STATE_STOPPED); } @@ -1123,7 +1129,6 @@ void qdio_int_handler(struct ccw_device *cdev, unsigned long intparm, return; } - kstat_cpu(smp_processor_id()).irqs[IOINT_QDI]++; if (irq_ptr->perf_stat_enabled) irq_ptr->perf_stat.qdio_int++; @@ -1714,9 +1719,7 @@ int qdio_start_irq(struct ccw_device *cdev, int nr) WARN_ON(queue_irqs_enabled(q)); - if (!shared_ind(q)) - xchg(q->irq_ptr->dsci, 0); - + clear_nonshared_ind(irq_ptr); qdio_stop_polling(q); clear_bit(QDIO_QUEUE_IRQS_DISABLED, &q->u.in.queue_irq_state); @@ -1724,7 +1727,7 @@ int qdio_start_irq(struct ccw_device *cdev, int nr) * We need to check again to not lose initiative after * resetting the ACK state. */ - if (!shared_ind(q) && *q->irq_ptr->dsci) + if (test_nonshared_ind(irq_ptr)) goto rescan; if (!qdio_inbound_q_done(q)) goto rescan; diff --git a/drivers/s390/cio/qdio_setup.c b/drivers/s390/cio/qdio_setup.c index dd8bd670a6b..2acc01f90a6 100644 --- a/drivers/s390/cio/qdio_setup.c +++ b/drivers/s390/cio/qdio_setup.c @@ -8,6 +8,7 @@ */ #include <linux/kernel.h> #include <linux/slab.h> +#include <linux/export.h> #include <asm/qdio.h> #include "cio.h" @@ -381,6 +382,7 @@ static void setup_qdr(struct qdio_irq *irq_ptr, int i; irq_ptr->qdr->qfmt = qdio_init->q_format; + irq_ptr->qdr->ac = qdio_init->qdr_ac; irq_ptr->qdr->iqdcnt = qdio_init->no_input_qs; irq_ptr->qdr->oqdcnt = qdio_init->no_output_qs; irq_ptr->qdr->iqdsz = sizeof(struct qdesfmt0) / 4; /* size in words */ diff --git a/drivers/s390/cio/qdio_thinint.c b/drivers/s390/cio/qdio_thinint.c index a3e3949d7b6..011eadea3ee 100644 --- a/drivers/s390/cio/qdio_thinint.c +++ b/drivers/s390/cio/qdio_thinint.c @@ -26,17 +26,24 @@ */ #define TIQDIO_NR_NONSHARED_IND 63 #define TIQDIO_NR_INDICATORS (TIQDIO_NR_NONSHARED_IND + 1) +#define TIQDIO_SHARED_IND 63 + +/* device state change indicators */ +struct indicator_t { + u32 ind; /* u32 because of compare-and-swap performance */ + atomic_t count; /* use count, 0 or 1 for non-shared indicators */ +}; /* list of thin interrupt input queues */ static LIST_HEAD(tiq_list); -DEFINE_MUTEX(tiq_list_lock); +static DEFINE_MUTEX(tiq_list_lock); /* adapter local summary indicator */ static u8 *tiqdio_alsi; -struct indicator_t *q_indicators; +static struct indicator_t *q_indicators; -static u64 last_ai_time; +u64 last_ai_time; /* returns addr for the device state change indicator */ static u32 *get_indicator(void) @@ -90,6 +97,43 @@ void tiqdio_remove_input_queues(struct qdio_irq *irq_ptr) synchronize_rcu(); } +static inline int has_multiple_inq_on_dsci(struct qdio_irq *irq_ptr) +{ + return irq_ptr->nr_input_qs > 1; +} + +static inline int references_shared_dsci(struct qdio_irq *irq_ptr) +{ + return irq_ptr->dsci == &q_indicators[TIQDIO_SHARED_IND].ind; +} + +static inline int shared_ind(struct qdio_irq *irq_ptr) +{ + return references_shared_dsci(irq_ptr) || + has_multiple_inq_on_dsci(irq_ptr); +} + +void clear_nonshared_ind(struct qdio_irq *irq_ptr) +{ + if (!is_thinint_irq(irq_ptr)) + return; + if (shared_ind(irq_ptr)) + return; + xchg(irq_ptr->dsci, 0); +} + +int test_nonshared_ind(struct qdio_irq *irq_ptr) +{ + if (!is_thinint_irq(irq_ptr)) + return 0; + if (shared_ind(irq_ptr)) + return 0; + if (*irq_ptr->dsci) + return 1; + else + return 0; +} + static inline u32 clear_shared_ind(void) { if (!atomic_read(&q_indicators[TIQDIO_SHARED_IND].count)) @@ -119,7 +163,7 @@ static inline void tiqdio_call_inq_handlers(struct qdio_irq *irq) q->u.in.queue_start_poll(q->irq_ptr->cdev, q->nr, q->irq_ptr->int_parm); } else { - if (!shared_ind(q)) + if (!shared_ind(q->irq_ptr)) xchg(q->irq_ptr->dsci, 0); /* |