diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/s390/cio |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/s390/cio')
-rw-r--r-- | drivers/s390/cio/Makefile | 10 | ||||
-rw-r--r-- | drivers/s390/cio/airq.c | 87 | ||||
-rw-r--r-- | drivers/s390/cio/airq.h | 10 | ||||
-rw-r--r-- | drivers/s390/cio/blacklist.c | 351 | ||||
-rw-r--r-- | drivers/s390/cio/blacklist.h | 6 | ||||
-rw-r--r-- | drivers/s390/cio/ccwgroup.c | 482 | ||||
-rw-r--r-- | drivers/s390/cio/chsc.c | 1114 | ||||
-rw-r--r-- | drivers/s390/cio/chsc.h | 66 | ||||
-rw-r--r-- | drivers/s390/cio/cio.c | 860 | ||||
-rw-r--r-- | drivers/s390/cio/cio.h | 143 | ||||
-rw-r--r-- | drivers/s390/cio/cio_debug.h | 32 | ||||
-rw-r--r-- | drivers/s390/cio/cmf.c | 1042 | ||||
-rw-r--r-- | drivers/s390/cio/css.c | 575 | ||||
-rw-r--r-- | drivers/s390/cio/css.h | 155 | ||||
-rw-r--r-- | drivers/s390/cio/device.c | 1135 | ||||
-rw-r--r-- | drivers/s390/cio/device.h | 115 | ||||
-rw-r--r-- | drivers/s390/cio/device_fsm.c | 1250 | ||||
-rw-r--r-- | drivers/s390/cio/device_id.c | 355 | ||||
-rw-r--r-- | drivers/s390/cio/device_ops.c | 603 | ||||
-rw-r--r-- | drivers/s390/cio/device_pgid.c | 448 | ||||
-rw-r--r-- | drivers/s390/cio/device_status.c | 385 | ||||
-rw-r--r-- | drivers/s390/cio/ioasm.h | 228 | ||||
-rw-r--r-- | drivers/s390/cio/qdio.c | 3468 | ||||
-rw-r--r-- | drivers/s390/cio/qdio.h | 648 |
24 files changed, 13568 insertions, 0 deletions
diff --git a/drivers/s390/cio/Makefile b/drivers/s390/cio/Makefile new file mode 100644 index 000000000000..c490c2a1c2fc --- /dev/null +++ b/drivers/s390/cio/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for the S/390 common i/o drivers +# + +obj-y += airq.o blacklist.o chsc.o cio.o css.o +ccw_device-objs += device.o device_fsm.o device_ops.o +ccw_device-objs += device_id.o device_pgid.o device_status.o +obj-y += ccw_device.o cmf.o +obj-$(CONFIG_CCWGROUP) += ccwgroup.o +obj-$(CONFIG_QDIO) += qdio.o diff --git a/drivers/s390/cio/airq.c b/drivers/s390/cio/airq.c new file mode 100644 index 000000000000..3720e77b465f --- /dev/null +++ b/drivers/s390/cio/airq.c @@ -0,0 +1,87 @@ +/* + * drivers/s390/cio/airq.c + * S/390 common I/O routines -- support for adapter interruptions + * + * $Revision: 1.12 $ + * + * Copyright (C) 1999-2002 IBM Deutschland Entwicklung GmbH, + * IBM Corporation + * Author(s): Ingo Adlung (adlung@de.ibm.com) + * Cornelia Huck (cohuck@de.ibm.com) + * Arnd Bergmann (arndb@de.ibm.com) + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/rcupdate.h> + +#include "cio_debug.h" +#include "airq.h" + +static adapter_int_handler_t adapter_handler; + +/* + * register for adapter interrupts + * + * With HiperSockets the zSeries architecture provides for + * means of adapter interrups, pseudo I/O interrupts that are + * not tied to an I/O subchannel, but to an adapter. However, + * it doesn't disclose the info how to enable/disable them, but + * to recognize them only. Perhaps we should consider them + * being shared interrupts, and thus build a linked list + * of adapter handlers ... to be evaluated ... + */ +int +s390_register_adapter_interrupt (adapter_int_handler_t handler) +{ + int ret; + char dbf_txt[15]; + + CIO_TRACE_EVENT (4, "rgaint"); + + if (handler == NULL) + ret = -EINVAL; + else + ret = (cmpxchg(&adapter_handler, NULL, handler) ? -EBUSY : 0); + if (!ret) + synchronize_kernel(); + + sprintf (dbf_txt, "ret:%d", ret); + CIO_TRACE_EVENT (4, dbf_txt); + + return ret; +} + +int +s390_unregister_adapter_interrupt (adapter_int_handler_t handler) +{ + int ret; + char dbf_txt[15]; + + CIO_TRACE_EVENT (4, "urgaint"); + + if (handler == NULL) + ret = -EINVAL; + else { + adapter_handler = NULL; + synchronize_kernel(); + ret = 0; + } + sprintf (dbf_txt, "ret:%d", ret); + CIO_TRACE_EVENT (4, dbf_txt); + + return ret; +} + +void +do_adapter_IO (void) +{ + CIO_TRACE_EVENT (6, "doaio"); + + if (adapter_handler) + (*adapter_handler) (); +} + +EXPORT_SYMBOL (s390_register_adapter_interrupt); +EXPORT_SYMBOL (s390_unregister_adapter_interrupt); diff --git a/drivers/s390/cio/airq.h b/drivers/s390/cio/airq.h new file mode 100644 index 000000000000..7d6be3fdcd66 --- /dev/null +++ b/drivers/s390/cio/airq.h @@ -0,0 +1,10 @@ +#ifndef S390_AINTERRUPT_H +#define S390_AINTERRUPT_H + +typedef int (*adapter_int_handler_t)(void); + +extern int s390_register_adapter_interrupt(adapter_int_handler_t handler); +extern int s390_unregister_adapter_interrupt(adapter_int_handler_t handler); +extern void do_adapter_IO (void); + +#endif diff --git a/drivers/s390/cio/blacklist.c b/drivers/s390/cio/blacklist.c new file mode 100644 index 000000000000..4a06c7d0e5e4 --- /dev/null +++ b/drivers/s390/cio/blacklist.c @@ -0,0 +1,351 @@ +/* + * drivers/s390/cio/blacklist.c + * S/390 common I/O routines -- blacklisting of specific devices + * $Revision: 1.33 $ + * + * Copyright (C) 1999-2002 IBM Deutschland Entwicklung GmbH, + * IBM Corporation + * Author(s): Ingo Adlung (adlung@de.ibm.com) + * Cornelia Huck (cohuck@de.ibm.com) + * Arnd Bergmann (arndb@de.ibm.com) + */ + +#include <linux/config.h> +#include <linux/init.h> +#include <linux/vmalloc.h> +#include <linux/slab.h> +#include <linux/proc_fs.h> +#include <linux/ctype.h> +#include <linux/device.h> + +#include <asm/cio.h> +#include <asm/uaccess.h> + +#include "blacklist.h" +#include "cio.h" +#include "cio_debug.h" +#include "css.h" + +/* + * "Blacklisting" of certain devices: + * Device numbers given in the commandline as cio_ignore=... won't be known + * to Linux. + * + * These can be single devices or ranges of devices + */ + +/* 65536 bits to indicate if a devno is blacklisted or not */ +#define __BL_DEV_WORDS (__MAX_SUBCHANNELS + (8*sizeof(long) - 1) / \ + (8*sizeof(long))) +static unsigned long bl_dev[__BL_DEV_WORDS]; +typedef enum {add, free} range_action; + +/* + * Function: blacklist_range + * (Un-)blacklist the devices from-to + */ +static inline void +blacklist_range (range_action action, unsigned int from, unsigned int to) +{ + if (!to) + to = from; + + if (from > to || to > __MAX_SUBCHANNELS) { + printk (KERN_WARNING "Invalid blacklist range " + "0x%04x to 0x%04x, skipping\n", from, to); + return; + } + for (; from <= to; from++) { + if (action == add) + set_bit (from, bl_dev); + else + clear_bit (from, bl_dev); + } +} + +/* + * Function: blacklist_busid + * Get devno/busid from given string. + * Shamelessly grabbed from dasd_devmap.c. + */ +static inline int +blacklist_busid(char **str, int *id0, int *id1, int *devno) +{ + int val, old_style; + char *sav; + + sav = *str; + + /* check for leading '0x' */ + old_style = 0; + if ((*str)[0] == '0' && (*str)[1] == 'x') { + *str += 2; + old_style = 1; + } + if (!isxdigit((*str)[0])) /* We require at least one hex digit */ + goto confused; + val = simple_strtoul(*str, str, 16); + if (old_style || (*str)[0] != '.') { + *id0 = *id1 = 0; + if (val < 0 || val > 0xffff) + goto confused; + *devno = val; + if ((*str)[0] != ',' && (*str)[0] != '-' && + (*str)[0] != '\n' && (*str)[0] != '\0') + goto confused; + return 0; + } + /* New style x.y.z busid */ + if (val < 0 || val > 0xff) + goto confused; + *id0 = val; + (*str)++; + if (!isxdigit((*str)[0])) /* We require at least one hex digit */ + goto confused; + val = simple_strtoul(*str, str, 16); + if (val < 0 || val > 0xff || (*str)++[0] != '.') + goto confused; + *id1 = val; + if (!isxdigit((*str)[0])) /* We require at least one hex digit */ + goto confused; + val = simple_strtoul(*str, str, 16); + if (val < 0 || val > 0xffff) + goto confused; + *devno = val; + if ((*str)[0] != ',' && (*str)[0] != '-' && + (*str)[0] != '\n' && (*str)[0] != '\0') + goto confused; + return 0; +confused: + strsep(str, ",\n"); + printk(KERN_WARNING "Invalid cio_ignore parameter '%s'\n", sav); + return 1; +} + +static inline int +blacklist_parse_parameters (char *str, range_action action) +{ + unsigned int from, to, from_id0, to_id0, from_id1, to_id1; + + while (*str != 0 && *str != '\n') { + range_action ra = action; + while(*str == ',') + str++; + if (*str == '!') { + ra = !action; + ++str; + } + + /* + * Since we have to parse the proc commands and the + * kernel arguments we have to check four cases + */ + if (strncmp(str,"all,",4) == 0 || strcmp(str,"all") == 0 || + strncmp(str,"all\n",4) == 0 || strncmp(str,"all ",4) == 0) { + from = 0; + to = __MAX_SUBCHANNELS; + str += 3; + } else { + int rc; + + rc = blacklist_busid(&str, &from_id0, + &from_id1, &from); + if (rc) + continue; + to = from; + to_id0 = from_id0; + to_id1 = from_id1; + if (*str == '-') { + str++; + rc = blacklist_busid(&str, &to_id0, + &to_id1, &to); + if (rc) + continue; + } + if (*str == '-') { + printk(KERN_WARNING "invalid cio_ignore " + "parameter '%s'\n", + strsep(&str, ",\n")); + continue; + } + if ((from_id0 != to_id0) || (from_id1 != to_id1)) { + printk(KERN_WARNING "invalid cio_ignore range " + "%x.%x.%04x-%x.%x.%04x\n", + from_id0, from_id1, from, + to_id0, to_id1, to); + continue; + } + } + /* FIXME: ignoring id0 and id1 here. */ + pr_debug("blacklist_setup: adding range " + "from 0.0.%04x to 0.0.%04x\n", from, to); + blacklist_range (ra, from, to); + } + return 1; +} + +/* Parsing the commandline for blacklist parameters, e.g. to blacklist + * bus ids 0.0.1234, 0.0.1235 and 0.0.1236, you could use any of: + * - cio_ignore=1234-1236 + * - cio_ignore=0x1234-0x1235,1236 + * - cio_ignore=0x1234,1235-1236 + * - cio_ignore=1236 cio_ignore=1234-0x1236 + * - cio_ignore=1234 cio_ignore=1236 cio_ignore=0x1235 + * - cio_ignore=0.0.1234-0.0.1236 + * - cio_ignore=0.0.1234,0x1235,1236 + * - ... + */ +static int __init +blacklist_setup (char *str) +{ + CIO_MSG_EVENT(6, "Reading blacklist parameters\n"); + return blacklist_parse_parameters (str, add); +} + +__setup ("cio_ignore=", blacklist_setup); + +/* Checking if devices are blacklisted */ + +/* + * Function: is_blacklisted + * Returns 1 if the given devicenumber can be found in the blacklist, + * otherwise 0. + * Used by validate_subchannel() + */ +int +is_blacklisted (int devno) +{ + return test_bit (devno, bl_dev); +} + +#ifdef CONFIG_PROC_FS +/* + * Function: s390_redo_validation + * Look for no longer blacklisted devices + * FIXME: there must be a better way to do this */ +static inline void +s390_redo_validation (void) +{ + unsigned int irq; + + CIO_TRACE_EVENT (0, "redoval"); + for (irq = 0; irq < __MAX_SUBCHANNELS; irq++) { + int ret; + struct subchannel *sch; + + sch = get_subchannel_by_schid(irq); + if (sch) { + /* Already known. */ + put_device(&sch->dev); + continue; + } + ret = css_probe_device(irq); + if (ret == -ENXIO) + break; /* We're through. */ + if (ret == -ENOMEM) + /* + * Stop validation for now. Bad, but no need for a + * panic. + */ + break; + } +} + +/* + * Function: blacklist_parse_proc_parameters + * parse the stuff which is piped to /proc/cio_ignore + */ +static inline void +blacklist_parse_proc_parameters (char *buf) +{ + if (strncmp (buf, "free ", 5) == 0) { + blacklist_parse_parameters (buf + 5, free); + } else if (strncmp (buf, "add ", 4) == 0) { + /* + * We don't need to check for known devices since + * css_probe_device will handle this correctly. + */ + blacklist_parse_parameters (buf + 4, add); + } else { + printk (KERN_WARNING "cio_ignore: Parse error; \n" + KERN_WARNING "try using 'free all|<devno-range>," + "<devno-range>,...'\n" + KERN_WARNING "or 'add <devno-range>," + "<devno-range>,...'\n"); + return; + } + + s390_redo_validation (); +} + +/* FIXME: These should be real bus ids and not home-grown ones! */ +static int cio_ignore_read (char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + const unsigned int entry_size = 18; /* "0.0.ABCD-0.0.EFGH\n" */ + long devno; + int len; + + len = 0; + for (devno = off; /* abuse the page variable + * as counter, see fs/proc/generic.c */ + devno <= __MAX_SUBCHANNELS && len + entry_size < count; devno++) { + if (!test_bit(devno, bl_dev)) + continue; + len += sprintf(page + len, "0.0.%04lx", devno); + if (test_bit(devno + 1, bl_dev)) { /* print range */ + while (++devno < __MAX_SUBCHANNELS) + if (!test_bit(devno, bl_dev)) + break; + len += sprintf(page + len, "-0.0.%04lx", --devno); + } + len += sprintf(page + len, "\n"); + } + + if (devno <= __MAX_SUBCHANNELS) + *eof = 1; + *start = (char *) (devno - off); /* number of checked entries */ + return len; +} + +static int cio_ignore_write(struct file *file, const char __user *user_buf, + unsigned long user_len, void *data) +{ + char *buf; + + if (user_len > 65536) + user_len = 65536; + buf = vmalloc (user_len + 1); /* maybe better use the stack? */ + if (buf == NULL) + return -ENOMEM; + if (strncpy_from_user (buf, user_buf, user_len) < 0) { + vfree (buf); + return -EFAULT; + } + buf[user_len] = '\0'; + + blacklist_parse_proc_parameters (buf); + + vfree (buf); + return user_len; +} + +static int +cio_ignore_proc_init (void) +{ + struct proc_dir_entry *entry; + + entry = create_proc_entry ("cio_ignore", S_IFREG | S_IRUGO | S_IWUSR, + &proc_root); + if (!entry) + return 0; + + entry->read_proc = cio_ignore_read; + entry->write_proc = cio_ignore_write; + + return 1; +} + +__initcall (cio_ignore_proc_init); + +#endif /* CONFIG_PROC_FS */ diff --git a/drivers/s390/cio/blacklist.h b/drivers/s390/cio/blacklist.h new file mode 100644 index 000000000000..fb42cafbe57c --- /dev/null +++ b/drivers/s390/cio/blacklist.h @@ -0,0 +1,6 @@ +#ifndef S390_BLACKLIST_H +#define S390_BLACKLIST_H + +extern int is_blacklisted (int devno); + +#endif diff --git a/drivers/s390/cio/ccwgroup.c b/drivers/s390/cio/ccwgroup.c new file mode 100644 index 000000000000..21a75ee28b80 --- /dev/null +++ b/drivers/s390/cio/ccwgroup.c @@ -0,0 +1,482 @@ +/* + * drivers/s390/cio/ccwgroup.c + * bus driver for ccwgroup + * $Revision: 1.29 $ + * + * Copyright (C) 2002 IBM Deutschland Entwicklung GmbH, + * IBM Corporation + * Author(s): Arnd Bergmann (arndb@de.ibm.com) + * Cornelia Huck (cohuck@de.ibm.com) + */ +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/device.h> +#include <linux/init.h> +#include <linux/ctype.h> +#include <linux/dcache.h> + +#include <asm/semaphore.h> +#include <asm/ccwdev.h> +#include <asm/ccwgroup.h> + +/* In Linux 2.4, we had a channel device layer called "chandev" + * that did all sorts of obscure stuff for networking devices. + * This is another driver that serves as a replacement for just + * one of its functions, namely the translation of single subchannels + * to devices that use multiple subchannels. + */ + +/* 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) +{ + struct ccwgroup_device *gdev; + struct ccwgroup_driver *gdrv; + + gdev = container_of(dev, struct ccwgroup_device, dev); + gdrv = container_of(drv, struct ccwgroup_driver, driver); + + if (gdev->creator_id == gdrv->driver_id) + return 1; + + return 0; +} +static int +ccwgroup_hotplug (struct device *dev, char **envp, int num_envp, char *buffer, + int buffer_size) +{ + /* TODO */ + return 0; +} + +static struct bus_type ccwgroup_bus_type = { + .name = "ccwgroup", + .match = ccwgroup_bus_match, + .hotplug = ccwgroup_hotplug, +}; + +static inline void +__ccwgroup_remove_symlinks(struct ccwgroup_device *gdev) +{ + int i; + char str[8]; + + for (i = 0; i < gdev->count; i++) { + sprintf(str, "cdev%d", i); + sysfs_remove_link(&gdev->dev.kobj, str); + sysfs_remove_link(&gdev->cdev[i]->dev.kobj, "group_device"); + } + +} + +/* + * Provide an 'ungroup' attribute so the user can remove group devices no + * longer needed or accidentially created. Saves memory :) + */ +static ssize_t +ccwgroup_ungroup_store(struct device *dev, const char *buf, size_t count) +{ + struct ccwgroup_device *gdev; + + gdev = to_ccwgroupdev(dev); + + if (gdev->state != CCWGROUP_OFFLINE) + return -EINVAL; + + __ccwgroup_remove_symlinks(gdev); + device_unregister(dev); + + return count; +} + +static DEVICE_ATTR(ungroup, 0200, NULL, ccwgroup_ungroup_store); + +static void +ccwgroup_release (struct device *dev) +{ + struct ccwgroup_device *gdev; + int i; + + gdev = to_ccwgroupdev(dev); + + for (i = 0; i < gdev->count; i++) { + gdev->cdev[i]->dev.driver_data = NULL; + put_device(&gdev->cdev[i]->dev); + } + kfree(gdev); +} + +static inline 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"); + if (rc) { + for (--i; i >= 0; i--) + sysfs_remove_link(&gdev->cdev[i]->dev.kobj, + "group_device"); + return rc; + } + } + 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); + if (rc) { + for (--i; i >= 0; i--) { + sprintf(str, "cdev%d", i); + sysfs_remove_link(&gdev->dev.kobj, str); + } + for (i = 0; i < gdev->count; i++) + sysfs_remove_link(&gdev->cdev[i]->dev.kobj, + "group_device"); + return rc; + } + } + return 0; +} + +/* + * try to add a new ccwgroup device for one driver + * argc and argv[] are a list of bus_id's of devices + * belonging to the driver. + */ +int +ccwgroup_create(struct device *root, + unsigned int creator_id, + struct ccw_driver *cdrv, + int argc, char *argv[]) +{ + struct ccwgroup_device *gdev; + int i; + int rc; + int del_drvdata; + + if (argc > 256) /* disallow dumb users */ + return -EINVAL; + + gdev = kmalloc(sizeof(*gdev) + argc*sizeof(gdev->cdev[0]), GFP_KERNEL); + if (!gdev) + return -ENOMEM; + + memset(gdev, 0, sizeof(*gdev) + argc*sizeof(gdev->cdev[0])); + atomic_set(&gdev->onoff, 0); + + del_drvdata = 0; + for (i = 0; i < argc; i++) { + gdev->cdev[i] = get_ccwdev_by_busid(cdrv, argv[i]); + + /* all devices have to be of the same type in + * order to be grouped */ + if (!gdev->cdev[i] + || gdev->cdev[i]->id.driver_info != + gdev->cdev[0]->id.driver_info) { + rc = -EINVAL; + goto free_dev; + } + /* Don't allow a device to belong to more than one group. */ + if (gdev->cdev[i]->dev.driver_data) { + rc = -EINVAL; + goto free_dev; + } + } + for (i = 0; i < argc; i++) + gdev->cdev[i]->dev.driver_data = gdev; + del_drvdata = 1; + + gdev->creator_id = creator_id; + gdev->count = argc; + gdev->dev = (struct device ) { + .bus = &ccwgroup_bus_type, + .parent = root, + .release = ccwgroup_release, + }; + + snprintf (gdev->dev.bus_id, BUS_ID_SIZE, "%s", + gdev->cdev[0]->dev.bus_id); + + rc = device_register(&gdev->dev); + + if (rc) + goto free_dev; + get_device(&gdev->dev); + rc = device_create_file(&gdev->dev, &dev_attr_ungroup); + + if (rc) { + device_unregister(&gdev->dev); + goto error; + } + + rc = __ccwgroup_create_symlinks(gdev); + if (!rc) { + put_device(&gdev->dev); + return 0; + } + device_remove_file(&gdev->dev, &dev_attr_ungroup); + device_unregister(&gdev->dev); +error: + for (i = 0; i < argc; i++) + if (gdev->cdev[i]) { + put_device(&gdev->cdev[i]->dev); + gdev->cdev[i]->dev.driver_data = NULL; + } + put_device(&gdev->dev); + return rc; +free_dev: + for (i = 0; i < argc; i++) + if (gdev->cdev[i]) { + put_device(&gdev->cdev[i]->dev); + if (del_drvdata) + gdev->cdev[i]->dev.driver_data = NULL; + } + kfree(gdev); + return rc; +} + +static int __init +init_ccwgroup (void) +{ + return bus_register (&ccwgroup_bus_type); +} + +static void __exit +cleanup_ccwgroup (void) +{ + bus_unregister (&ccwgroup_bus_type); +} + +module_init(init_ccwgroup); +module_exit(cleanup_ccwgroup); + +/************************** driver stuff ******************************/ + +static int +ccwgroup_set_online(struct ccwgroup_device *gdev) +{ + struct ccwgroup_driver *gdrv; + int ret; + + if (atomic_compare_and_swap(0, 1, &gdev->onoff)) + 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(gdev))) + 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_compare_and_swap(0, 1, &gdev->onoff)) + 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(gdev))) + goto out; + + gdev->state = CCWGROUP_OFFLINE; + out: + atomic_set(&gdev->onoff, 0); + return ret; +} + +static ssize_t +ccwgroup_online_store (struct device *dev, const char *buf, size_t count) +{ + struct ccwgroup_device *gdev; + struct ccwgroup_driver *gdrv; + unsigned int value; + int ret; + + gdev = to_ccwgroupdev(dev); + if (!dev->driver) + return count; + + gdrv = to_ccwgroupdrv (gdev->dev.driver); + if (!try_module_get(gdrv->owner)) + return -EINVAL; + + value = simple_strtoul(buf, 0, 0); + ret = count; + if (value == 1) + ccwgroup_set_online(gdev); + else if (value == 0) + ccwgroup_set_offline(gdev); + else + ret = -EINVAL; + module_put(gdrv->owner); + return ret; +} + +static ssize_t +ccwgroup_online_show (struct device *dev, 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; + + pr_debug("%s: device %s\n", __func__, gdev->dev.bus_id); + ret = gdrv->probe ? gdrv->probe(gdev) : -ENODEV; + if (ret) + device_remove_file(dev, &dev_attr_online); + + return ret; +} + +static int +ccwgroup_remove (struct device *dev) +{ + struct ccwgroup_device *gdev; + struct ccwgroup_driver *gdrv; + + gdev = to_ccwgroupdev(dev); + gdrv = to_ccwgroupdrv(dev->driver); + + pr_debug("%s: device %s\n", __func__, gdev->dev.bus_id); + + device_remove_file(dev, &dev_attr_online); + + if (gdrv && gdrv->remove) + gdrv->remove(gdev); + return 0; +} + +int +ccwgroup_driver_register (struct ccwgroup_driver *cdriver) +{ + /* register our new driver with the core */ + cdriver->driver = (struct device_driver) { + .bus = &ccwgroup_bus_type, + .name = cdriver->name, + .probe = ccwgroup_probe, + .remove = ccwgroup_remove, + }; + + 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); +} + +int +ccwgroup_probe_ccwdev(struct ccw_device *cdev) +{ + return 0; +} + +static inline struct ccwgroup_device * +__ccwgroup_get_gdev_by_cdev(struct ccw_device *cdev) +{ + struct ccwgroup_device *gdev; + + if (cdev->dev.driver_data) { + gdev = (struct ccwgroup_device *)cdev->dev.driver_data; + if (get_device(&gdev->dev)) { + if (!list_empty(&gdev->dev.node)) + return gdev; + put_device(&gdev->dev); + } + return NULL; + } + return NULL; +} + +void +ccwgroup_remove_ccwdev(struct ccw_device *cdev) +{ + struct ccwgroup_device *gdev; + + /* Ignore offlining errors, device is gone anyway. */ + ccw_device_set_offline(cdev); + /* If one of its devices is gone, the whole group is done for. */ + gdev = __ccwgroup_get_gdev_by_cdev(cdev); + if (gdev) { + __ccwgroup_remove_symlinks(gdev); + device_unregister(&gdev->dev); + put_device(&gdev->dev); + } +} + +MODULE_LICENSE("GPL"); +EXPORT_SYMBOL(ccwgroup_driver_register); +EXPORT_SYMBOL(ccwgroup_driver_unregister); +EXPORT_SYMBOL(ccwgroup_create); +EXPORT_SYMBOL(ccwgroup_probe_ccwdev); +EXPORT_SYMBOL(ccwgroup_remove_ccwdev); diff --git a/drivers/s390/cio/chsc.c b/drivers/s390/cio/chsc.c new file mode 100644 index 000000000000..b35fe12e6bfc --- /dev/null +++ b/drivers/s390/cio/chsc.c @@ -0,0 +1,1114 @@ +/* + * drivers/s390/cio/chsc.c + * S/390 common I/O routines -- channel subsystem call + * $Revision: 1.119 $ + * + * Copyright (C) 1999-2002 IBM Deutschland Entwicklung GmbH, + * IBM Corporation + * Author(s): Ingo Adlung (adlung@de.ibm.com) + * Cornelia Huck (cohuck@de.ibm.com) + * Arnd Bergmann (arndb@de.ibm.com) + */ + +#include <linux/module.h> +#include <linux/config.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/device.h> + +#include <asm/cio.h> + +#include "css.h" +#include "cio.h" +#include "cio_debug.h" +#include "ioasm.h" +#include "chsc.h" + +static struct channel_path *chps[NR_CHPIDS]; + +static void *sei_page; + +static int new_channel_path(int chpid); + +static inline void +set_chp_logically_online(int chp, int onoff) +{ + chps[chp]->state = onoff; +} + +static int +get_chp_status(int chp) +{ + return (chps[chp] ? chps[chp]->state : -ENODEV); +} + +void +chsc_validate_chpids(struct subchannel *sch) +{ + int mask, chp; + + for (chp = 0; chp <= 7; chp++) { + mask = 0x80 >> chp; + if (!get_chp_status(sch->schib.pmcw.chpid[chp])) + /* disable using this path */ + sch->opm &= ~mask; + } +} + +void +chpid_is_actually_online(int chp) +{ + int state; + + state = get_chp_status(chp); + if (state < 0) { + need_rescan = 1; + queue_work(slow_path_wq, &slow_path_work); + } else + WARN_ON(!state); +} + +/* FIXME: this is _always_ called for every subchannel. shouldn't we + * process more than one at a time? */ +static int +chsc_get_sch_desc_irq(struct subchannel *sch, void *page) +{ + int ccode, j; + + struct { + struct chsc_header request; + u16 reserved1; + u16 f_sch; /* first subchannel */ + u16 reserved2; + u16 l_sch; /* last subchannel */ + u32 reserved3; + struct chsc_header response; + u32 reserved4; + u8 sch_valid : 1; + u8 dev_valid : 1; + u8 st : 3; /* subchannel type */ + u8 zeroes : 3; + u8 unit_addr; /* unit address */ + u16 devno; /* device number */ + u8 path_mask; + u8 fla_valid_mask; + u16 sch; /* subchannel */ + u8 chpid[8]; /* chpids 0-7 */ + u16 fla[8]; /* full link addresses 0-7 */ + } *ssd_area; + + ssd_area = page; + + ssd_area->request = (struct chsc_header) { + .length = 0x0010, + .code = 0x0004, + }; + + ssd_area->f_sch = sch->irq; + ssd_area->l_sch = sch->irq; + + ccode = chsc(ssd_area); + if (ccode > 0) { + pr_debug("chsc returned with ccode = %d\n", ccode); + return (ccode == 3) ? -ENODEV : -EBUSY; + } + + switch (ssd_area->response.code) { + case 0x0001: /* everything ok */ + break; + case 0x0002: + CIO_CRW_EVENT(2, "Invalid command!\n"); + return -EINVAL; + case 0x0003: + CIO_CRW_EVENT(2, "Error in chsc request block!\n"); + return -EINVAL; + case 0x0004: + CIO_CRW_EVENT(2, "Model does not provide ssd\n"); + return -EOPNOTSUPP; + default: + CIO_CRW_EVENT(2, "Unknown CHSC response %d\n", + ssd_area->response.code); + return -EIO; + } + + /* + * ssd_area->st stores the type of the detected + * subchannel, with the following definitions: + * + * 0: I/O subchannel: All fields have meaning + * 1: CHSC subchannel: Only sch_val, st and sch + * have meaning + * 2: Message subchannel: All fields except unit_addr + * have meaning + * 3: ADM subchannel: Only sch_val, st and sch + * have meaning + * + * Other types are currently undefined. + */ + if (ssd_area->st > 3) { /* uhm, that looks strange... */ + CIO_CRW_EVENT(0, "Strange subchannel type %d" + " for sch %04x\n", ssd_area->st, sch->irq); + /* + * There may have been a new subchannel type defined in the + * time since this code was written; since we don't know which + * fields have meaning and what to do with it we just jump out + */ + return 0; + } else { + const char *type[4] = {"I/O", "chsc", "message", "ADM"}; + CIO_CRW_EVENT(6, "ssd: sch %04x is %s subchannel\n", + sch->irq, type[ssd_area->st]); + + sch->ssd_info.valid = 1; + sch->ssd_info.type = ssd_area->st; + } + + if (ssd_area->st == 0 || ssd_area->st == 2) { + for (j = 0; j < 8; j++) { + if (!((0x80 >> j) & ssd_area->path_mask & + ssd_area->fla_valid_mask)) + continue; + sch->ssd_info.chpid[j] = ssd_area->chpid[j]; + sch->ssd_info.fla[j] = ssd_area->fla[j]; + } + } + return 0; +} + +int +css_get_ssd_info(struct subchannel *sch) +{ + int ret; + void *page; + + page = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); + if (!page) + return -ENOMEM; + spin_lock_irq(&sch->lock); + ret = chsc_get_sch_desc_irq(sch, page); + if (ret) { + static int cio_chsc_err_msg; + + if (!cio_chsc_err_msg) { + printk(KERN_ERR + "chsc_get_sch_descriptions:" + " Error %d while doing chsc; " + "processing some machine checks may " + "not work\n", ret); + cio_chsc_err_msg = 1; + } + } + spin_unlock_irq(&sch->lock); + free_page((unsigned long)page); + if (!ret) { + int j, chpid; + /* Allocate channel path structures, if needed. */ + for (j = 0; j < 8; j++) { + chpid = sch->ssd_info.chpid[j]; + if (chpid && (get_chp_status(chpid) < 0)) + new_channel_path(chpid); + } + } + return ret; +} + +static int +s390_subchannel_remove_chpid(struct device *dev, void *data) +{ + int j; + int mask; + struct subchannel *sch; + __u8 *chpid; + struct schib schib; + + sch = to_subchannel(dev); + chpid = data; + for (j = 0; j < 8; j++) + if (sch->schib.pmcw.chpid[j] == *chpid) + break; + if (j >= 8) + return 0; + + mask = 0x80 >> j; + spin_lock(&sch->lock); + + stsch(sch->irq, &schib); + if (!schib.pmcw.dnv) + goto out_unreg; + memcpy(&sch->schib, &schib, sizeof(struct schib)); + /* Check for single path devices. */ + if (sch->schib.pmcw.pim == 0x80) + goto out_unreg; + if (sch->vpm == mask) + goto out_unreg; + + if ((sch->schib.scsw.actl & (SCSW_ACTL_CLEAR_PEND | + SCSW_ACTL_HALT_PEND | + SCSW_ACTL_START_PEND | + SCSW_ACTL_RESUME_PEND)) && + (sch->schib.pmcw.lpum == mask)) { + int cc = cio_cancel(sch); + + if (cc == -ENODEV) + goto out_unreg; + + if (cc == -EINVAL) { + cc = cio_clear(sch); + if (cc == -ENODEV) + goto out_unreg; + /* Call handler. */ + if (sch->driver && sch->driver->termination) + sch->driver->termination(&sch->dev); + goto out_unlock; + } + } else if ((sch->schib.scsw.actl & SCSW_ACTL_DEVACT) && + (sch->schib.scsw.actl & SCSW_ACTL_SCHACT) && + (sch->schib.pmcw.lpum == mask)) { + int cc; + + cc = cio_clear(sch); + if (cc == -ENODEV) + goto out_unreg; + /* Call handler. */ + if (sch->driver && sch->driver->termination) + sch->driver->termination(&sch->dev); + goto out_unlock; + } + + /* trigger path verification. */ + if (sch->driver && sch->driver->verify) + sch->driver->verify(&sch->dev); +out_unlock: + spin_unlock(&sch->lock); + return 0; +out_unreg: + spin_unlock(&sch->lock); + sch->lpm = 0; + if (css_enqueue_subchannel_slow(sch->irq)) { + css_clear_subchannel_slow_list(); + need_rescan = 1; + } + return 0; +} + +static inline void +s390_set_chpid_offline( __u8 chpid) +{ + char dbf_txt[15]; + + sprintf(dbf_txt, "chpr%x", chpid); + CIO_TRACE_EVENT(2, dbf_txt); + + if (get_chp_status(chpid) <= 0) + return; + + bus_for_each_dev(&css_bus_type, NULL, &chpid, + s390_subchannel_remove_chpid); + + if (need_rescan || css_slow_subchannels_exist()) + queue_work(slow_path_wq, &slow_path_work); +} + +static int +s390_process_res_acc_sch(u8 chpid, __u16 fla, u32 fla_mask, + struct subchannel *sch) +{ + int found; + int chp; + int ccode; + + found = 0; + for (chp = 0; chp <= 7; chp++) + /* + * check if chpid is in information updated by ssd + */ + if (sch->ssd_info.valid && + sch->ssd_info.chpid[chp] == chpid && + (sch->ssd_info.fla[chp] & fla_mask) == fla) { + found = 1; + break; + } + + if (found == 0) + return 0; + + /* + * Do a stsch to update our subchannel structure with the + * new path information and eventually check for logically + * offline chpids. + */ + ccode = stsch(sch->irq, &sch->schib); + if (ccode > 0) + return 0; + + return 0x80 >> chp; +} + +static int +s390_process_res_acc (u8 chpid, __u16 fla, u32 fla_mask) +{ + struct subchannel *sch; + int irq, rc; + char dbf_txt[15]; + + sprintf(dbf_txt, "accpr%x", chpid); + CIO_TRACE_EVENT( 2, dbf_txt); + if (fla != 0) { + sprintf(dbf_txt, "fla%x", fla); + CIO_TRACE_EVENT( 2, dbf_txt); + } + + /* + * I/O resources may have become accessible. + * Scan through all subchannels that may be concerned and + * do a validation on those. + * The more information we have (info), the less scanning + * will we have to do. + */ + + if (!get_chp_status(chpid)) + return 0; /* no need to do the rest */ + + rc = 0; + for (irq = 0; irq < __MAX_SUBCHANNELS; irq++) { + int chp_mask, old_lpm; + + sch = get_subchannel_by_schid(irq); + if (!sch) { + struct schib schib; + int ret; + /* + * We don't know the device yet, but since a path + * may be available now to the device we'll have + * to do recognition again. + * Since we don't have any idea about which chpid + * that beast may be on we'll have to do a stsch + * on all devices, grr... + */ + if (stsch(irq, &schib)) { + /* We're through */ + if (need_rescan) + rc = -EAGAIN; + break; + } + if (need_rescan) { + rc = -EAGAIN; + continue; + } + /* Put it on the slow path. */ + ret = css_enqueue_subchannel_slow(irq); + if (ret) { + css_clear_subchannel_slow_list(); + need_rescan = 1; + } + rc = -EAGAIN; + continue; + } + + spin_lock_irq(&sch->lock); + + chp_mask = s390_process_res_acc_sch(chpid, fla, fla_mask, sch); + + if (chp_mask == 0) { + + spin_unlock_irq(&sch->lock); + + if (fla_mask != 0) + break; + else + continue; + } + old_lpm = sch->lpm; + sch->lpm = ((sch->schib.pmcw.pim & + sch->schib.pmcw.pam & + sch->schib.pmcw.pom) + | chp_mask) & sch->opm; + if (!old_lpm && sch->lpm) + device_trigger_reprobe(sch); + else if (sch->driver && sch->driver->verify) + sch->driver->verify(&sch->dev); + + spin_unlock_irq(&sch->lock); + put_device(&sch->dev); + if (fla_mask != 0) + break; + } + return rc; +} + +static int +__get_chpid_from_lir(void *data) +{ + struct lir { + u8 iq; + u8 ic; + u16 sci; + /* incident-node descriptor */ + u32 indesc[28]; + /* attached-node descriptor */ + u32 andesc[28]; + /* incident-specific information */ + u32 isinfo[28]; + } *lir; + + lir = (struct lir*) data; + if (!(lir->iq&0x80)) + /* NULL link incident record */ + return -EINVAL; + if (!(lir->indesc[0]&0xc0000000)) + /* node descriptor not valid */ + return -EINVAL; + if (!(lir->indesc[0]&0x10000000)) + /* don't handle device-type nodes - FIXME */ + return -EINVAL; + /* Byte 3 contains the chpid. Could also be CTCA, but we don't care */ + + return (u16) (lir->indesc[0]&0x000000ff); +} + +int +chsc_process_crw(void) +{ + int chpid, ret; + struct { + struct chsc_header request; + u32 reserved1; + u32 reserved2; + u32 reserved3; + struct chsc_header response; + u32 reserved4; + u8 flags; + u8 vf; /* validity flags */ + u8 rs; /* reporting source */ + u8 cc; /* content code */ + u16 fla; /* full link address */ + u16 rsid; /* reporting source id */ + u32 reserved5; + u32 reserved6; + u32 ccdf[96]; /* content-code dependent field */ + /* ccdf has to be big enough for a link-incident record */ + } *sei_area; + + if (!sei_page) + return 0; + /* + * build the chsc request block for store event information + * and do the call + * This function is only called by the machine check handler thread, + * so we don't need locking for the sei_page. + */ + sei_area = sei_page; + + CIO_TRACE_EVENT( 2, "prcss"); + ret = 0; + do { + int ccode, status; + memset(sei_area, 0, sizeof(*sei_area)); + + sei_area->request = (struct chsc_header) { + .length = 0x0010, + .code = 0x000e, + }; + + ccode = chsc(sei_area); + if (ccode > 0) + return 0; + + switch (sei_area->response.code) { + /* for debug purposes, check for problems */ + case 0x0001: + CIO_CRW_EVENT(4, "chsc_process_crw: event information " + "successfully stored\n"); + break; /* everything ok */ + case 0x0002: + CIO_CRW_EVENT(2, + "chsc_process_crw: invalid command!\n"); + return 0; + case 0x0003: + CIO_CRW_EVENT(2, "chsc_process_crw: error in chsc " + "request block!\n"); + return 0; + case 0x0005: + CIO_CRW_EVENT(2, "chsc_process_crw: no event " + "information stored\n"); + return 0; + default: + CIO_CRW_EVENT(2, "chsc_process_crw: chsc response %d\n", + sei_area->response.code); + return 0; + } + + /* Check if we might have lost some information. */ + if (sei_area->flags & 0x40) + CIO_CRW_EVENT(2, "chsc_process_crw: Event information " + "has been lost due to overflow!\n"); + + if (sei_area->rs != 4) { + CIO_CRW_EVENT(2, "chsc_process_crw: reporting source " + "(%04X) isn't a chpid!\n", + sei_area->rsid); + continue; + } + + /* which kind of information was stored? */ + switch (sei_area->cc) { + case 1: /* link incident*/ + CIO_CRW_EVENT(4, "chsc_process_crw: " + "channel subsystem reports link incident," + " reporting source is chpid %x\n", + sei_area->rsid); + chpid = __get_chpid_from_lir(sei_area->ccdf); + if (chpid < 0) + CIO_CRW_EVENT(4, "%s: Invalid LIR, skipping\n", + __FUNCTION__); + else + s390_set_chpid_offline(chpid); + break; + + case 2: /* i/o resource accessibiliy */ + CIO_CRW_EVENT(4, "chsc_process_crw: " + "channel subsystem reports some I/O " + "devices may have become accessible\n"); + pr_debug("Data received after sei: \n"); + pr_debug("Validity flags: %x\n", sei_area->vf); + + /* allocate a new channel path structure, if needed */ + status = get_chp_status(sei_area->rsid); + if (status < 0) + new_channel_path(sei_area->rsid); + else if (!status) + return 0; + if ((sei_area->vf & 0x80) == 0) { + pr_debug("chpid: %x\n", sei_area->rsid); + ret = s390_process_res_acc(sei_area->rsid, + 0, 0); + } else if ((sei_area->vf & 0xc0) == 0x80) { + pr_debug("chpid: %x link addr: %x\n", + sei_area->rsid, sei_area->fla); + ret = s390_process_res_acc(sei_area->rsid, + sei_area->fla, + 0xff00); + } else if ((sei_area->vf & 0xc0) == 0xc0) { + pr_debug("chpid: %x full link addr: %x\n", + sei_area->rsid, sei_area->fla); + ret = s390_process_res_acc(sei_area->rsid, + sei_area->fla, + 0xffff); + } + pr_debug("\n"); + + break; + + default: /* other stuff */ + CIO_CRW_EVENT(4, "chsc_process_crw: event %d\n", + sei_area->cc); + break; + } + } while (sei_area->flags & 0x80); + return ret; +} + +static int +chp_add(int chpid) +{ + struct subchannel *sch; + int irq, ret, rc; + char dbf_txt[15]; + + if (!get_chp_status(chpid)) + return 0; /* no need to do the rest */ + + sprintf(dbf_txt, "cadd%x", chpid); + CIO_TRACE_EVENT(2, dbf_txt); + + rc = 0; + for (irq = 0; irq < __MAX_SUBCHANNELS; irq++) { + int i; + + sch = get_subchannel_by_schid(irq); + if (!sch) { + struct schib schib; + + if (stsch(irq, &schib)) { + /* We're through */ + if (need_rescan) + rc = -EAGAIN; + break; + } + if (need_rescan) { + rc = -EAGAIN; + continue; + } + /* Put it on the slow path. */ + ret = css_enqueue_subchannel_slow(irq); + if (ret) { + css_clear_subchannel_slow_list(); + need_rescan = 1; + } + rc = -EAGAIN; + continue; + } + + spin_lock(&sch->lock); + for (i=0; i<8; i++) + if (sch->schib.pmcw.chpid[i] == chpid) { + if (stsch(sch->irq, &sch->schib) != 0) { + /* Endgame. */ + spin_unlock(&sch->lock); + return rc; + } + break; + } + if (i==8) { + spin_unlock(&sch->lock); + return rc; + } + sch->lpm = ((sch->schib.pmcw.pim & + sch->schib.pmcw.pam & + sch->schib.pmcw.pom) + | 0x80 >> i) & sch->opm; + + if (sch->driver && sch->driver->verify) + sch->driver->verify(&sch->dev); + + spin_unlock(&sch->lock); + put_device(&sch->dev); + } + return rc; +} + +/* + * Handling of crw machine checks with channel path source. + */ +int +chp_process_crw(int chpid, int on) +{ + if (on == 0) { + /* Path has gone. We use the link incident routine.*/ + s390_set_chpid_offline(chpid); + return 0; /* De-register is async anyway. */ + } + /* + * Path has come. Allocate a new channel path structure, + * if needed. + */ + if (get_chp_status(chpid) < 0) + new_channel_path(chpid); + /* Avoid the extra overhead in process_rec_acc. */ + return chp_add(chpid); +} + +static inline int +__check_for_io_and_kill(struct subchannel *sch, int index) +{ + int cc; + + if (!device_is_online(sch)) + /* cio could be doing I/O. */ + return 0; + cc = stsch(sch->irq, &sch->schib); + if (cc) + return 0; + if (sch->schib.scsw.actl && sch->schib.pmcw.lpum == (0x80 >> index)) { + device_set_waiting(sch); + return 1; + } + return 0; +} + +static inline void +__s390_subchannel_vary_chpid(struct subchannel *sch, __u8 chpid, int on) +{ + int chp, old_lpm; + unsigned long flags; + + if (!sch->ssd_info.valid) + return; + + spin_lock_irqsave(&sch->lock, flags); + old_lpm = sch->lpm; + for (chp = 0; chp < 8; chp++) { + if (sch->ssd_info.chpid[chp] != chpid) + continue; + + if (on) { + sch->opm |= (0x80 >> chp); + sch->lpm |= (0x80 >> chp); + if (!old_lpm) + device_trigger_reprobe(sch); + else if (sch->driver && sch->driver->verify) + sch->driver->verify(&sch->dev); + } else { + sch->opm &= ~(0x80 >> chp); + sch->lpm &= ~(0x80 >> chp); + /* + * Give running I/O a grace period in which it + * can successfully terminate, even using the + * just varied off path. Then kill it. + */ + if (!__check_for_io_and_kill(sch, chp) && !sch->lpm) { + if (css_enqueue_subchannel_slow(sch->irq)) { + css_clear_subchannel_slow_list(); + need_rescan = 1; + } + } else if (sch->driver && sch->driver->verify) + sch->driver->verify(&sch->dev); + } + break; + } + spin_unlock_irqrestore(&sch->lock, flags); +} + +static int +s390_subchannel_vary_chpid_off(struct device *dev, void *data) +{ + struct subchannel *sch; + __u8 *chpid; + + sch = to_subchannel(dev); + chpid = data; + + __s390_subchannel_vary_chpid(sch, *chpid, 0); + return 0; +} + +static int +s390_subchannel_vary_chpid_on(struct device *dev, void *data) +{ + struct subchannel *sch; + __u8 *chpid; + + sch = to_subchannel(dev); + chpid = data; + + __s390_subchannel_vary_chpid(sch, *chpid, 1); + return 0; +} + +/* + * Function: s390_vary_chpid + * Varies the specified chpid online or offline + */ +static int +s390_vary_chpid( __u8 chpid, int on) +{ + char dbf_text[15]; + int status, irq, ret; + struct subchannel *sch; + + sprintf(dbf_text, on?"varyon%x":"varyoff%x", chpid); + CIO_TRACE_EVENT( 2, dbf_text); + + status = get_chp_status(chpid); + if (status < 0) { + printk(KERN_ERR "Can't vary unknown chpid %02X\n", chpid); + return -EINVAL; + } + + if (!on && !status) { + printk(KERN_ERR "chpid %x is already offline\n", chpid); + return -EINVAL; + } + + set_chp_logically_online(chpid, on); + + /* + * Redo PathVerification on the devices the chpid connects to + */ + + bus_for_each_dev(&css_bus_type, NULL, &chpid, on ? + s390_subchannel_vary_chpid_on : + s390_subchannel_vary_chpid_off); + if (!on) + goto out; + /* Scan for new devices on varied on path. */ + for (irq = 0; irq < __MAX_SUBCHANNELS; irq++) { + struct schib schib; + + if (need_rescan) + break; + sch = get_subchannel_by_schid(irq); + if (sch) { + put_device(&sch->dev); + continue; + } + if (stsch(irq, &schib)) + /* We're through */ + break; + /* Put it on the slow path. */ + ret = css_enqueue_subchannel_slow(irq); + if (ret) { + css_clear_subchannel_slow_list(); + need_rescan = 1; + } + } +out: + if (need_rescan || css_slow_subchannels_exist()) + queue_work(slow_path_wq, &slow_path_work); + return 0; +} + +/* + * Files for the channel path entries. + */ +static ssize_t +chp_status_show(struct device *dev, char *buf) +{ + struct channel_path *chp = container_of(dev, struct channel_path, dev); + + if (!chp) + return 0; + return (get_chp_status(chp->id) ? sprintf(buf, "online\n") : + sprintf(buf, "offline\n")); +} + +static ssize_t +chp_status_write(struct device *dev, const char *buf, size_t count) +{ + struct channel_path *cp = container_of(dev, struct channel_path, dev); + char cmd[10]; + int num_args; + int error; + + num_args = sscanf(buf, "%5s", cmd); + if (!num_args) + return count; + + if (!strnicmp(cmd, "on", 2)) + error = s390_vary_chpid(cp->id, 1); + else if (!strnicmp(cmd, "off", 3)) + error = s390_vary_chpid(cp->id, 0); + else + error = -EINVAL; + + return error < 0 ? error : count; + +} + +static DEVICE_ATTR(status, 0644, chp_status_show, chp_status_write); + +static ssize_t +chp_type_show(struct device *dev, char *buf) +{ + struct channel_path *chp = container_of(dev, struct channel_path, dev); + + if (!chp) + return 0; + return sprintf(buf, "%x\n", chp->desc.desc); +} + +static DEVICE_ATTR(type, 0444, chp_type_show, NULL); + +static struct attribute * chp_attrs[] = { + &dev_attr_status.attr, + &dev_attr_type.attr, + NULL, +}; + +static struct attribute_group chp_attr_group = { + .attrs = chp_attrs, +}; + +static void +chp_release(struct device *dev) +{ + struct channel_path *cp; + + cp = container_of(dev, struct channel_path, dev); + kfree(cp); +} + +static int +chsc_determine_channel_path_description(int chpid, + struct channel_path_desc *desc) +{ + int ccode, ret; + + struct { + struct chsc_header request; + u32 : 24; + u32 first_chpid : 8; + u32 : 24; + u32 last_chpid : 8; + u32 zeroes1; + struct chsc_header response; + u32 zeroes2; + struct channel_path_desc desc; + } *scpd_area; + + scpd_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); + if (!scpd_area) + return -ENOMEM; + + scpd_area->request = (struct chsc_header) { + .length = 0x0010, + .code = 0x0002, + }; + + scpd_area->first_chpid = chpid; + scpd_area->last_chpid = chpid; + + ccode = chsc(scpd_area); + if (ccode > 0) { + ret = (ccode == 3) ? -ENODEV : -EBUSY; + goto out; + } + + switch (scpd_area->response.code) { + case 0x0001: /* Success. */ + memcpy(desc, &scpd_area->desc, + sizeof(struct channel_path_desc)); + ret = 0; + break; + case 0x0003: /* Invalid block. */ + case 0x0007: /* Invalid format. */ + case 0x0008: /* Other invalid block. */ + CIO_CRW_EVENT(2, "Error in chsc request block!\n"); + ret = -EINVAL; + break; + case 0x0004: /* Command not provided in model. */ + CIO_CRW_EVENT(2, "Model does not provide scpd\n"); + ret = -EOPNOTSUPP; + break; + default: + CIO_CRW_EVENT(2, "Unknown CHSC response %d\n", + scpd_area->response.code); + ret = -EIO; + } +out: + free_page((unsigned long)scpd_area); + return ret; +} + +/* + * Entries for chpids on the system bus. + * This replaces /proc/chpids. + */ +static int +new_channel_path(int chpid) +{ + struct channel_path *chp; + int ret; + + chp = kmalloc(sizeof(struct channel_path), GFP_KERNEL); + if (!chp) + return -ENOMEM; + memset(chp, 0, sizeof(struct channel_path)); + + /* fill in status, etc. */ + chp->id = chpid; + chp->state = 1; + chp->dev = (struct device) { + .parent = &css_bus_device, + .release = chp_release, + }; + snprintf(chp->dev.bus_id, BUS_ID_SIZE, "chp0.%x", chpid); + + /* Obtain channel path description and fill it in. */ + ret = chsc_determine_channel_path_description(chpid, &chp->desc); + if (ret) + goto out_free; + + /* make it known to the system */ + ret = device_register(&chp->dev); + if (ret) { + printk(KERN_WARNING "%s: could not register %02x\n", + __func__, chpid); + goto out_free; + } + ret = sysfs_create_group(&chp->dev.kobj, &chp_attr_group); + if (ret) { + device_unregister(&chp->dev); + goto out_free; + } else + chps[chpid] = chp; + return ret; +out_free: + kfree(chp); + return ret; +} + +void * +chsc_get_chp_desc(struct subchannel *sch, int chp_no) +{ + struct channel_path *chp; + struct channel_path_desc *desc; + + chp = chps[sch->schib.pmcw.chpid[chp_no]]; + if (!chp) + return NULL; + desc = kmalloc(sizeof(struct channel_path_desc), GFP_KERNEL); + if (!desc) + return NULL; + memcpy(desc, &chp->desc, sizeof(struct channel_path_desc)); + return desc; +} + + +static int __init +chsc_alloc_sei_area(void) +{ + sei_page = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); + if (!sei_page) + printk(KERN_WARNING"Can't allocate page for processing of " \ + "chsc machine checks!\n"); + return (sei_page ? 0 : -ENOMEM); +} + +subsys_initcall(chsc_alloc_sei_area); + +struct css_general_char css_general_characteristics; +struct css_chsc_char css_chsc_characteristics; + +int __init +chsc_determine_css_characteristics(void) +{ + int result; + struct { + struct chsc_header request; + u32 reserved1; + u32 reserved2; + u32 reserved3; + struct chsc_header response; + u32 reserved4; + u32 general_char[510]; + u32 chsc_char[518]; + } *scsc_area; + + scsc_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); + if (!scsc_area) { + printk(KERN_WARNING"cio: Was not able to determine available" \ + "CHSCs due to no memory.\n"); + return -ENOMEM; + } + + scsc_area->request = (struct chsc_header) { + .length = 0x0010, + .code = 0x0010, + }; + + result = chsc(scsc_area); + if (result) { + printk(KERN_WARNING"cio: Was not able to determine " \ + "available CHSCs, cc=%i.\n", result); + result = -EIO; + goto exit; + } + + if (scsc_area->response.code != 1) { + printk(KERN_WARNING"cio: Was not able to determine " \ + "available CHSCs.\n"); + result = -EIO; + goto exit; + } + memcpy(&css_general_characteristics, scsc_area->general_char, + sizeof(css_general_characteristics)); + memcpy(&css_chsc_characteristics, scsc_area->chsc_char, + sizeof(css_chsc_characteristics)); +exit: + free_page ((unsigned long) scsc_area); + return result; +} + +EXPORT_SYMBOL_GPL(css_general_characteristics); +EXPORT_SYMBOL_GPL(css_chsc_characteristics); diff --git a/drivers/s390/cio/chsc.h b/drivers/s390/cio/chsc.h new file mode 100644 index 000000000000..be20da49d147 --- /dev/null +++ b/drivers/s390/cio/chsc.h @@ -0,0 +1,66 @@ +#ifndef S390_CHSC_H +#define S390_CHSC_H + +#define NR_CHPIDS 256 + +#define CHSC_SEI_ACC_CHPID 1 +#define CHSC_SEI_ACC_LINKADDR 2 +#define CHSC_SEI_ACC_FULLLINKADDR 3 + +struct chsc_header { + u16 length; + u16 code; +}; + +struct channel_path_desc { + u8 flags; + u8 lsn; + u8 desc; + u8 chpid; + u8 swla; + u8 zeroes; + u8 chla; + u8 chpp; +}; + +struct channel_path { + int id; + int state; + struct channel_path_desc desc; + struct device dev; +}; + +extern void s390_process_css( void ); +extern void chsc_validate_chpids(struct subchannel *); +extern void chpid_is_actually_online(int); + +struct css_general_char { + u64 : 41; + u32 aif : 1; /* bit 41 */ + u32 : 3; + u32 mcss : 1; /* bit 45 */ + u32 : 2; + u32 ext_mb : 1; /* bit 48 */ + u32 : 7; + u32 aif_tdd : 1; /* bit 56 */ + u32 : 10; + u32 aif_osa : 1; /* bit 67 */ + u32 : 28; +}__attribute__((packed)); + +struct css_chsc_char { + u64 res; + u64 : 43; + u32 scssc : 1; /* bit 107 */ + u32 scsscf : 1; /* bit 108 */ + u32 : 19; +}__attribute__((packed)); + +extern struct css_general_char css_general_characteristics; +extern struct css_chsc_char css_chsc_characteristics; + +extern int chsc_determine_css_characteristics(void); +extern int css_characteristics_avail; + +extern void *chsc_get_chp_desc(struct subchannel*, int); +#endif diff --git a/drivers/s390/cio/cio.c b/drivers/s390/cio/cio.c new file mode 100644 index 000000000000..99ce5a567982 --- /dev/null +++ b/drivers/s390/cio/cio.c @@ -0,0 +1,860 @@ +/* + * drivers/s390/cio/cio.c + * S/390 common I/O routines -- low level i/o calls + * $Revision: 1.131 $ + * + * Copyright (C) 1999-2002 IBM Deutschland Entwicklung GmbH, + * IBM Corporation + * Author(s): Ingo Adlung (adlung@de.ibm.com) + * Cornelia Huck (cohuck@de.ibm.com) + * Arnd Bergmann (arndb@de.ibm.com) + * Martin Schwidefsky (schwidefsky@de.ibm.com) + */ + +#include <linux/module.h> +#include <linux/config.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/kernel_stat.h> +#include <linux/interrupt.h> + +#include <asm/cio.h> +#include <asm/delay.h> +#include <asm/irq.h> + +#include "airq.h" +#include "cio.h" +#include "css.h" +#include "chsc.h" +#include "ioasm.h" +#include "blacklist.h" +#include "cio_debug.h" + +debug_info_t *cio_debug_msg_id; +debug_info_t *cio_debug_trace_id; +debug_info_t *cio_debug_crw_id; + +int cio_show_msg; + +static int __init +cio_setup (char *parm) +{ + if (!strcmp (parm, "yes")) + cio_show_msg = 1; + else if (!strcmp (parm, "no")) + cio_show_msg = 0; + else + printk (KERN_ERR "cio_setup : invalid cio_msg parameter '%s'", + parm); + return 1; +} + +__setup ("cio_msg=", cio_setup); + +/* + * Function: cio_debug_init + * Initializes three debug logs (under /proc/s390dbf) for common I/O: + * - cio_msg logs the messages which are printk'ed when CONFIG_DEBUG_IO is on + * - cio_trace logs the calling of different functions + * - cio_crw logs the messages which are printk'ed when CONFIG_DEBUG_CRW is on + * debug levels depend on CONFIG_DEBUG_IO resp. CONFIG_DEBUG_CRW + */ +static int __init +cio_debug_init (void) +{ + cio_debug_msg_id = debug_register ("cio_msg", 4, 4, 16*sizeof (long)); + if (!cio_debug_msg_id) + goto out_unregister; + debug_register_view (cio_debug_msg_id, &debug_sprintf_view); + debug_set_level (cio_debug_msg_id, 2); + cio_debug_trace_id = debug_register ("cio_trace", 4, 4, 8); + if (!cio_debug_trace_id) + goto out_unregister; + debug_register_view (cio_debug_trace_id, &debug_hex_ascii_view); + debug_set_level (cio_debug_trace_id, 2); + cio_debug_crw_id = debug_register ("cio_crw", 2, 4, 16*sizeof (long)); + if (!cio_debug_crw_id) + goto out_unregister; + debug_register_view (cio_debug_crw_id, &debug_sprintf_view); + debug_set_level (cio_debug_crw_id, 2); + pr_debug("debugging initialized\n"); + return 0; + +out_unregister: + if (cio_debug_msg_id) + debug_unregister (cio_debug_msg_id); + if (cio_debug_trace_id) + debug_unregister (cio_debug_trace_id); + if (cio_debug_crw_id) + debug_unregister (cio_debug_crw_id); + pr_debug("could not initialize debugging\n"); + return -1; +} + +arch_initcall (cio_debug_init); + +int +cio_set_options (struct subchannel *sch, int flags) +{ + sch->options.suspend = (flags & DOIO_ALLOW_SUSPEND) != 0; + sch->options.prefetch = (flags & DOIO_DENY_PREFETCH) != 0; + sch->options.inter = (flags & DOIO_SUPPRESS_INTER) != 0; + return 0; +} + +/* FIXME: who wants to use this? */ +int +cio_get_options (struct subchannel *sch) +{ + int flags; + + flags = 0; + if (sch->options.suspend) + flags |= DOIO_ALLOW_SUSPEND; + if (sch->options.prefetch) + flags |= DOIO_DENY_PREFETCH; + if (sch->options.inter) + flags |= DOIO_SUPPRESS_INTER; + return flags; +} + +/* + * Use tpi to get a pending interrupt, call the interrupt handler and + * return a pointer to the subchannel structure. + */ +static inline int +cio_tpi(void) +{ + struct tpi_info *tpi_info; + struct subchannel *sch; + struct irb *irb; + + tpi_info = (struct tpi_info *) __LC_SUBCHANNEL_ID; + if (tpi (NULL) != 1) + return 0; + irb = (struct irb *) __LC_IRB; + /* Store interrupt response block to lowcore. */ + if (tsch (tpi_info->irq, irb) != 0) + /* Not status pending or not operational. */ + return 1; + sch = (struct subchannel *)(unsigned long)tpi_info->intparm; + if (!sch) + return 1; + local_bh_disable(); + irq_enter (); + spin_lock(&sch->lock); + memcpy (&sch->schib.scsw, &irb->scsw, sizeof (struct scsw)); + if (sch->driver && sch->driver->irq) + sch->driver->irq(&sch->dev); + spin_unlock(&sch->lock); + irq_exit (); + __local_bh_enable(); + return 1; +} + +static inline int +cio_start_handle_notoper(struct subchannel *sch, __u8 lpm) +{ + char dbf_text[15]; + + if (lpm != 0) + sch->lpm &= ~lpm; + else + sch->lpm = 0; + + stsch (sch->irq, &sch->schib); + + CIO_MSG_EVENT(0, "cio_start: 'not oper' status for " + "subchannel %04x!\n", sch->irq); + sprintf(dbf_text, "no%s", sch->dev.bus_id); + CIO_TRACE_EVENT(0, dbf_text); + CIO_HEX_EVENT(0, &sch->schib, sizeof (struct schib)); + + return (sch->lpm ? -EACCES : -ENODEV); +} + +int +cio_start_key (struct subchannel *sch, /* subchannel structure */ + struct ccw1 * cpa, /* logical channel prog addr */ + __u8 lpm, /* logical path mask */ + __u8 key) /* storage key */ +{ + char dbf_txt[15]; + int ccode; + + CIO_TRACE_EVENT (4, "stIO"); + CIO_TRACE_EVENT (4, sch->dev.bus_id); + + /* sch is always under 2G. */ + sch->orb.intparm = (__u32)(unsigned long)sch; + sch->orb.fmt = 1; + + sch->orb.pfch = sch->options.prefetch == 0; + sch->orb.spnd = sch->options.suspend; + sch->orb.ssic = sch->options.suspend && sch->options.inter; + sch->orb.lpm = (lpm != 0) ? (lpm & sch->opm) : sch->lpm; +#ifdef CONFIG_ARCH_S390X + /* + * for 64 bit we always support 64 bit IDAWs with 4k page size only + */ + sch->orb.c64 = 1; + sch->orb.i2k = 0; +#endif + sch->orb.key = key >> 4; + /* issue "Start Subchannel" */ + sch->orb.cpa = (__u32) __pa (cpa); + ccode = ssch (sch->irq, &sch->orb); + + /* process condition code */ + sprintf (dbf_txt, "ccode:%d", ccode); + CIO_TRACE_EVENT (4, dbf_txt); + + switch (ccode) { + case 0: + /* + * initialize device status information + */ + sch->schib.scsw.actl |= SCSW_ACTL_START_PEND; + return 0; + case 1: /* status pending */ + case 2: /* busy */ + return -EBUSY; + default: /* device/path not operational */ + return cio_start_handle_notoper(sch, lpm); + } +} + +int +cio_start (struct subchannel *sch, struct ccw1 *cpa, __u8 lpm) +{ + return cio_start_key(sch, cpa, lpm, default_storage_key); +} + +/* + * resume suspended I/O operation + */ +int +cio_resume (struct subchannel *sch) +{ + char dbf_txt[15]; + int ccode; + + CIO_TRACE_EVENT (4, "resIO"); + CIO_TRACE_EVENT (4, sch->dev.bus_id); + + ccode = rsch (sch->irq); + + sprintf (dbf_txt, "ccode:%d", ccode); + CIO_TRACE_EVENT (4, dbf_txt); + + switch (ccode) { + case 0: + sch->schib.scsw.actl |= SCSW_ACTL_RESUME_PEND; + return 0; + case 1: + return -EBUSY; + case 2: + return -EINVAL; + default: + /* + * useless to wait for request completion + * as device is no longer operational ! + */ + return -ENODEV; + } +} + +/* + * halt I/O operation + */ +int +cio_halt(struct subchannel *sch) +{ + char dbf_txt[15]; + int ccode; + + if (!sch) + return -ENODEV; + + CIO_TRACE_EVENT (2, "haltIO"); + CIO_TRACE_EVENT (2, sch->dev.bus_id); + + /* + * Issue "Halt subchannel" and process condition code + */ + ccode = hsch (sch->irq); + + sprintf (dbf_txt, "ccode:%d", ccode); + CIO_TRACE_EVENT (2, dbf_txt); + + switch (ccode) { + case 0: + sch->schib.scsw.actl |= SCSW_ACTL_HALT_PEND; + return 0; + case 1: /* status pending */ + case 2: /* busy */ + return -EBUSY; + default: /* device not operational */ + return -ENODEV; + } +} + +/* + * Clear I/O operation + */ +int +cio_clear(struct subchannel *sch) +{ + char dbf_txt[15]; + int ccode; + + if (!sch) + return -ENODEV; + + CIO_TRACE_EVENT (2, "clearIO"); + CIO_TRACE_EVENT (2, sch->dev.bus_id); + + /* + * Issue "Clear subchannel" and process condition code + */ + ccode = csch (sch->irq); + + sprintf (dbf_txt, "ccode:%d", ccode); + CIO_TRACE_EVENT (2, dbf_txt); + + switch (ccode) { + case 0: + sch->schib.scsw.actl |= SCSW_ACTL_CLEAR_PEND; + return 0; + default: /* device not operational */ + return -ENODEV; + } +} + +/* + * Function: cio_cancel + * Issues a "Cancel Subchannel" on the specified subchannel + * Note: We don't need any fancy intparms and flags here + * since xsch is executed synchronously. + * Only for common I/O internal use as for now. + */ +int +cio_cancel (struct subchannel *sch) +{ + char dbf_txt[15]; + int ccode; + + if (!sch) + return -ENODEV; + + CIO_TRACE_EVENT (2, "cancelIO"); + CIO_TRACE_EVENT (2, sch->dev.bus_id); + + ccode = xsch (sch->irq); + + sprintf (dbf_txt, "ccode:%d", ccode); + CIO_TRACE_EVENT (2, dbf_txt); + + switch (ccode) { + case 0: /* success */ + /* Update information in scsw. */ + stsch (sch->irq, &sch->schib); + return 0; + case 1: /* status pending */ + return -EBUSY; + case 2: /* not applicable */ + return -EINVAL; + default: /* not oper */ + return -ENODEV; + } +} + +/* + * Function: cio_modify + * Issues a "Modify Subchannel" on the specified subchannel + */ +int +cio_modify (struct subchannel *sch) +{ + int ccode, retry, ret; + + ret = 0; + for (retry = 0; retry < 5; retry++) { + ccode = msch_err (sch->irq, &sch->schib); + if (ccode < 0) /* -EIO if msch gets a program check. */ + return ccode; + switch (ccode) { + case 0: /* successfull */ + return 0; + case 1: /* status pending */ + return -EBUSY; + case 2: /* busy */ + udelay (100); /* allow for recovery */ + ret = -EBUSY; + break; + case 3: /* not operational */ + return -ENODEV; + } + } + return ret; +} + +/* + * Enable subchannel. + */ +int +cio_enable_subchannel (struct subchannel *sch, unsigned int isc) +{ + char dbf_txt[15]; + int ccode; + int retry; + int ret; + + CIO_TRACE_EVENT (2, "ensch"); + CIO_TRACE_EVENT (2, sch->dev.bus_id); + + ccode = stsch (sch->irq, &sch->schib); + if (ccode) + return -ENODEV; + + for (retry = 5, ret = 0; retry > 0; retry--) { + sch->schib.pmcw.ena = 1; + sch->schib.pmcw.isc = isc; + sch->schib.pmcw.intparm = (__u32)(unsigned long)sch; + ret = cio_modify(sch); + if (ret == -ENODEV) + break; + if (ret == -EIO) + /* + * Got a program check in cio_modify. Try without + * the concurrent sense bit the next time. + */ + sch->schib.pmcw.csense = 0; + if (ret == 0) { + stsch (sch->irq, &sch->schib); + if (sch->schib.pmcw.ena) + break; + } + if (ret == -EBUSY) { + struct irb irb; + if (tsch(sch->irq, &irb) != 0) + break; + } + } + sprintf (dbf_txt, "ret:%d", ret); + CIO_TRACE_EVENT (2, dbf_txt); + return ret; +} + +/* + * Disable subchannel. + */ +int +cio_disable_subchannel (struct subchannel *sch) +{ + char dbf_txt[15]; + int ccode; + int retry; + int ret; + + CIO_TRACE_EVENT (2, "dissch"); + CIO_TRACE_EVENT (2, sch->dev.bus_id); + + ccode = stsch (sch->irq, &sch->schib); + if (ccode == 3) /* Not operational. */ + return -ENODEV; + + if (sch->schib.scsw.actl != 0) + /* + * the disable function must not be called while there are + * requests pending for completion ! + */ + return -EBUSY; + + for (retry = 5, ret = 0; retry > 0; retry--) { + sch->schib.pmcw.ena = 0; + ret = cio_modify(sch); + if (ret == -ENODEV) + break; + if (ret == -EBUSY) + /* + * The subchannel is busy or status pending. + * We'll disable when the next interrupt was delivered + * via the state machine. + */ + break; + if (ret == 0) { + stsch (sch->irq, &sch->schib); + if (!sch->schib.pmcw.ena) + break; + } + } + sprintf (dbf_txt, "ret:%d", ret); + CIO_TRACE_EVENT (2, dbf_txt); + return ret; +} + +/* + * cio_validate_subchannel() + * + * Find out subchannel type and initialize struct subchannel. + * Return codes: + * SUBCHANNEL_TYPE_IO for a normal io subchannel + * SUBCHANNEL_TYPE_CHSC for a chsc subchannel + * SUBCHANNEL_TYPE_MESSAGE for a messaging subchannel + * SUBCHANNEL_TYPE_ADM for a adm(?) subchannel + * -ENXIO for non-defined subchannels + * -ENODEV for subchannels with invalid device number or blacklisted devices + */ +int +cio_validate_subchannel (struct subchannel *sch, unsigned int irq) +{ + char dbf_txt[15]; + int ccode; + + sprintf (dbf_txt, "valsch%x", irq); + CIO_TRACE_EVENT (4, dbf_txt); + + /* Nuke all fields. */ + memset(sch, 0, sizeof(struct subchannel)); + + spin_lock_init(&sch->lock); + + /* Set a name for the subchannel */ + snprintf (sch->dev.bus_id, BUS_ID_SIZE, "0.0.%04x", irq); + + /* + * The first subchannel that is not-operational (ccode==3) + * indicates that there aren't any more devices available. + */ + sch->irq = irq; + ccode = stsch (irq, &sch->schib); + if (ccode) + return -ENXIO; + + /* Copy subchannel type from path management control word. */ + sch->st = sch->schib.pmcw.st; + + /* + * ... just being curious we check for non I/O subchannels + */ + if (sch->st != 0) { + CIO_DEBUG(KERN_INFO, 0, + "Subchannel %04X reports " + "non-I/O subchannel type %04X\n", + sch->irq, sch->st); + /* We stop here for non-io subchannels. */ + return sch->st; + } + + /* Initialization for io subchannels. */ + if (!sch->schib.pmcw.dnv) + /* io subchannel but device number is invalid. */ + return -ENODEV; + + /* Devno is valid. */ + if (is_blacklisted (sch->schib.pmcw.dev)) { + /* + * This device must not be known to Linux. So we simply + * say that there is no device and return ENODEV. + */ + CIO_MSG_EVENT(0, "Blacklisted device detected " + "at devno %04X\n", sch->schib.pmcw.dev); + return -ENODEV; + } + sch->opm = 0xff; + chsc_validate_chpids(sch); + sch->lpm = sch->schib.pmcw.pim & + sch->schib.pmcw.pam & + sch->schib.pmcw.pom & + sch->opm; + + CIO_DEBUG(KERN_INFO, 0, + "Detected device %04X on subchannel %04X" + " - PIM = %02X, PAM = %02X, POM = %02X\n", + sch->schib.pmcw.dev, sch->irq, sch->schib.pmcw.pim, + sch->schib.pmcw.pam, sch->schib.pmcw.pom); + + /* + * We now have to initially ... + * ... set "interruption subclass" + * ... enable "concurrent sense" + * ... enable "multipath mode" if more than one + * CHPID is available. This is done regardless + * whether multiple paths are available for us. + */ + sch->schib.pmcw.isc = 3; /* could be smth. else */ + sch->schib.pmcw.csense = 1; /* concurrent sense */ + sch->schib.pmcw.ena = 0; + if ((sch->lpm & (sch->lpm - 1)) != 0) + sch->schib.pmcw.mp = 1; /* multipath mode */ + return 0; +} + +/* + * do_IRQ() handles all normal I/O device IRQ's (the special + * SMP cross-CPU interrupts have their own specific + * handlers). + * + */ +void +do_IRQ (struct pt_regs *regs) +{ + struct tpi_info *tpi_info; + struct subchannel *sch; + struct irb *irb; + + irq_enter (); + asm volatile ("mc 0,0"); + if (S390_lowcore.int_clock >= S390_lowcore.jiffy_timer) + /** + * Make sure that the i/o interrupt did not "overtake" + * the last HZ timer interrupt. + */ + account_ticks(regs); + /* + * Get interrupt information from lowcore + */ + tpi_info = (struct tpi_info *) __LC_SUBCHANNEL_ID; + irb = (struct irb *) __LC_IRB; + do { + kstat_cpu(smp_processor_id()).irqs[IO_INTERRUPT]++; + /* + * Non I/O-subchannel thin interrupts are processed differently + */ + if (tpi_info->adapter_IO == 1 && + tpi_info->int_type == IO_INTERRUPT_TYPE) { + do_adapter_IO(); + continue; + } + sch = (struct subchannel *)(unsigned long)tpi_info->intparm; + if (sch) + spin_lock(&sch->lock); + /* Store interrupt response block to lowcore. */ + if (tsch (tpi_info->irq, irb) == 0 && sch) { + /* Keep subchannel information word up to date. */ + memcpy (&sch->schib.scsw, &irb->scsw, + sizeof (irb->scsw)); + /* Call interrupt handler if there is one. */ + if (sch->driver && sch->driver->irq) + sch->driver->irq(&sch->dev); + } + if (sch) + spin_unlock(&sch->lock); + /* + * Are more interrupts pending? + * If so, the tpi instruction will update the lowcore + * to hold the info for the next interrupt. + * We don't do this for VM because a tpi drops the cpu + * out of the sie which costs more cycles than it saves. + */ + } while (!MACHINE_IS_VM && tpi (NULL) != 0); + irq_exit (); +} + +#ifdef CONFIG_CCW_CONSOLE +static struct subchannel console_subchannel; +static int console_subchannel_in_use; + +/* + * busy wait for the next interrupt on the console + */ +void +wait_cons_dev (void) +{ + unsigned long cr6 __attribute__ ((aligned (8))); + unsigned long save_cr6 __attribute__ ((aligned (8))); + + /* + * before entering the spinlock we may already have + * processed the interrupt on a different CPU... + */ + if (!console_subchannel_in_use) + return; + + /* disable all but isc 7 (console device) */ + __ctl_store (save_cr6, 6, 6); + cr6 = 0x01000000; + __ctl_load (cr6, 6, 6); + + do { + spin_unlock(&console_subchannel.lock); + if (!cio_tpi()) + cpu_relax(); + spin_lock(&console_subchannel.lock); + } while (console_subchannel.schib.scsw.actl != 0); + /* + * restore previous isc value + */ + __ctl_load (save_cr6, 6, 6); +} + +static int +cio_console_irq(void) +{ + int irq; + + if (console_irq != -1) { + /* VM provided us with the irq number of the console. */ + if (stsch(console_irq, &console_subchannel.schib) != 0 || + !console_subchannel.schib.pmcw.dnv) + return -1; + console_devno = console_subchannel.schib.pmcw.dev; + } else if (console_devno != -1) { + /* At least the console device number is known. */ + for (irq = 0; irq < __MAX_SUBCHANNELS; irq++) { + if (stsch(irq, &console_subchannel.schib) != 0) + break; + if (console_subchannel.schib.pmcw.dnv && + console_subchannel.schib.pmcw.dev == + console_devno) { + console_irq = irq; + break; + } + } + if (console_irq == -1) + return -1; + } else { + /* unlike in 2.4, we cannot autoprobe here, since + * the channel subsystem is not fully initialized. + * With some luck, the HWC console can take over */ + printk(KERN_WARNING "No ccw console found!\n"); + return -1; + } + return console_irq; +} + +struct subchannel * +cio_probe_console(void) +{ + int irq, ret; + + if (xchg(&console_subchannel_in_use, 1) != 0) + return ERR_PTR(-EBUSY); + irq = cio_console_irq(); + if (irq == -1) { + console_subchannel_in_use = 0; + return ERR_PTR(-ENODEV); + } + memset(&console_subchannel, 0, sizeof(struct subchannel)); + ret = cio_validate_subchannel(&console_subchannel, irq); + if (ret) { + console_subchannel_in_use = 0; + return ERR_PTR(-ENODEV); + } + + /* + * enable console I/O-interrupt subclass 7 + */ + ctl_set_bit(6, 24); + console_subchannel.schib.pmcw.isc = 7; + console_subchannel.schib.pmcw.intparm = + (__u32)(unsigned long)&console_subchannel; + ret = cio_modify(&console_subchannel); + if (ret) { + console_subchannel_in_use = 0; + return ERR_PTR(ret); + } + return &console_subchannel; +} + +void +cio_release_console(void) +{ + console_subchannel.schib.pmcw.intparm = 0; + cio_modify(&console_subchannel); + ctl_clear_bit(6, 24); + console_subchannel_in_use = 0; +} + +/* Bah... hack to catch console special sausages. */ +int +cio_is_console(int irq) +{ + if (!console_subchannel_in_use) + return 0; + return (irq == console_subchannel.irq); +} + +struct subchannel * +cio_get_console_subchannel(void) +{ + if (!console_subchannel_in_use) + return 0; + return &console_subchannel; +} + +#endif +static inline int +__disable_subchannel_easy(unsigned int schid, struct schib *schib) +{ + int retry, cc; + + cc = 0; + for (retry=0;retry<3;retry++) { + schib->pmcw.ena = 0; + cc = msch(schid, schib); + if (cc) + return (cc==3?-ENODEV:-EBUSY); + stsch(schid, schib); + if (!schib->pmcw.ena) + return 0; + } + return -EBUSY; /* uhm... */ +} + +static inline int +__clear_subchannel_easy(unsigned int schid) +{ + int retry; + + if (csch(schid)) + return -ENODEV; + for (retry=0;retry<20;retry++) { + struct tpi_info ti; + + if (tpi(&ti)) { + tsch(schid, (struct irb *)__LC_IRB); + return 0; + } + udelay(100); + } + return -EBUSY; +} + +extern void do_reipl(unsigned long devno); + +/* Clear all subchannels. */ +void +clear_all_subchannels(void) +{ + unsigned int schid; + + local_irq_disable(); + for (schid=0;schid<=highest_subchannel;schid++) { + struct schib schib; + if (stsch(schid, &schib)) + break; /* break out of the loop */ + if (!schib.pmcw.ena) + continue; + switch(__disable_subchannel_easy(schid, &schib)) { + case 0: + case -ENODEV: + break; + default: /* -EBUSY */ + if (__clear_subchannel_easy(schid)) + break; /* give up... jump out of switch */ + stsch(schid, &schib); + __disable_subchannel_easy(schid, &schib); + } + } +} + +/* Make sure all subchannels are quiet before we re-ipl an lpar. */ +void +reipl(unsigned long devno) +{ + clear_all_subchannels(); + do_reipl(devno); +} diff --git a/drivers/s390/cio/cio.h b/drivers/s390/cio/cio.h new file mode 100644 index 000000000000..c50a9da420a9 --- /dev/null +++ b/drivers/s390/cio/cio.h @@ -0,0 +1,143 @@ +#ifndef S390_CIO_H +#define S390_CIO_H + +/* + * where we put the ssd info + */ +struct ssd_info { + __u8 valid:1; + __u8 type:7; /* subchannel type */ + __u8 chpid[8]; /* chpids */ + __u16 fla[8]; /* full link addresses */ +} __attribute__ ((packed)); + +/* + * path management control word + */ +struct pmcw { + __u32 intparm; /* interruption parameter */ + __u32 qf : 1; /* qdio facility */ + __u32 res0 : 1; /* reserved zeros */ + __u32 isc : 3; /* interruption sublass */ + __u32 res5 : 3; /* reserved zeros */ + __u32 ena : 1; /* enabled */ + __u32 lm : 2; /* limit mode */ + __u32 mme : 2; /* measurement-mode enable */ + __u32 mp : 1; /* multipath mode */ + __u32 tf : 1; /* timing facility */ + __u32 dnv : 1; /* device number valid */ + __u32 dev : 16; /* device number */ + __u8 lpm; /* logical path mask */ + __u8 pnom; /* path not operational mask */ + __u8 lpum; /* last path used mask */ + __u8 pim; /* path installed mask */ + __u16 mbi; /* measurement-block index */ + __u8 pom; /* path operational mask */ + __u8 pam; /* path available mask */ + __u8 chpid[8]; /* CHPID 0-7 (if available) */ + __u32 unused1 : 8; /* reserved zeros */ + __u32 st : 3; /* subchannel type */ + __u32 unused2 : 18; /* reserved zeros */ + __u32 mbfc : 1; /* measurement block format control */ + __u32 xmwme : 1; /* extended measurement word mode enable */ + __u32 csense : 1; /* concurrent sense; can be enabled ...*/ + /* ... per MSCH, however, if facility */ + /* ... is not installed, this results */ + /* ... in an operand exception. */ +} __attribute__ ((packed)); + +/* + * subchannel information block + */ +struct schib { + struct pmcw pmcw; /* path management control word */ + struct scsw scsw; /* subchannel status word */ + __u64 mba; /* measurement block address */ + __u8 mda[4]; /* model dependent area */ +} __attribute__ ((packed,aligned(4))); + +/* + * operation request block + */ +struct orb { + __u32 intparm; /* interruption parameter */ + __u32 key : 4; /* flags, like key, suspend control, etc. */ + __u32 spnd : 1; /* suspend control */ + __u32 res1 : 1; /* reserved */ + __u32 mod : 1; /* modification control */ + __u32 sync : 1; /* synchronize control */ + __u32 fmt : 1; /* format control */ + __u32 pfch : 1; /* prefetch control */ + __u32 isic : 1; /* initial-status-interruption control */ + __u32 alcc : 1; /* address-limit-checking control */ + __u32 ssic : 1; /* suppress-suspended-interr. control */ + __u32 res2 : 1; /* reserved */ + __u32 c64 : 1; /* IDAW/QDIO 64 bit control */ + __u32 i2k : 1; /* IDAW 2/4kB block size control */ + __u32 lpm : 8; /* logical path mask */ + __u32 ils : 1; /* incorrect length */ + __u32 zero : 6; /* reserved zeros */ + __u32 orbx : 1; /* ORB extension control */ + __u32 cpa; /* channel program address */ +} __attribute__ ((packed,aligned(4))); + +/* subchannel data structure used by I/O subroutines */ +struct subchannel { + unsigned int irq; /* aka. subchannel number */ + spinlock_t lock; /* subchannel lock */ + + enum { + SUBCHANNEL_TYPE_IO = 0, + SUBCHANNEL_TYPE_CHSC = 1, + SUBCHANNEL_TYPE_MESSAGE = 2, + SUBCHANNEL_TYPE_ADM = 3, + } st; /* subchannel type */ + + struct { + unsigned int suspend:1; /* allow suspend */ + unsigned int prefetch:1;/* deny prefetch */ + unsigned int inter:1; /* suppress intermediate interrupts */ + } __attribute__ ((packed)) options; + + __u8 vpm; /* verified path mask */ + __u8 lpm; /* logical path mask */ + __u8 opm; /* operational path mask */ + struct schib schib; /* subchannel information block */ + struct orb orb; /* operation request block */ + struct ccw1 sense_ccw; /* static ccw for sense command */ + struct ssd_info ssd_info; /* subchannel description */ + struct device dev; /* entry in device tree */ + struct css_driver *driver; +} __attribute__ ((aligned(8))); + +#define IO_INTERRUPT_TYPE 0 /* I/O interrupt type */ + +#define to_subchannel(n) container_of(n, struct subchannel, dev) + +extern int cio_validate_subchannel (struct subchannel *, unsigned int); +extern int cio_enable_subchannel (struct subchannel *, unsigned int); +extern int cio_disable_subchannel (struct subchannel *); +extern int cio_cancel (struct subchannel *); +extern int cio_clear (struct subchannel *); +extern int cio_resume (struct subchannel *); +extern int cio_halt (struct subchannel *); +extern int cio_start (struct subchannel *, struct ccw1 *, __u8); +extern int cio_start_key (struct subchannel *, struct ccw1 *, __u8, __u8); +extern int cio_cancel (struct subchannel *); +extern int cio_set_options (struct subchannel *, int); +extern int cio_get_options (struct subchannel *); +extern int cio_modify (struct subchannel *); +/* Use with care. */ +#ifdef CONFIG_CCW_CONSOLE +extern struct subchannel *cio_probe_console(void); +extern void cio_release_console(void); +extern int cio_is_console(int irq); +extern struct subchannel *cio_get_console_subchannel(void); +#else +#define cio_is_console(irq) 0 +#define cio_get_console_subchannel() NULL +#endif + +extern int cio_show_msg; + +#endif diff --git a/drivers/s390/cio/cio_debug.h b/drivers/s390/cio/cio_debug.h new file mode 100644 index 000000000000..6af8b27d366b --- /dev/null +++ b/drivers/s390/cio/cio_debug.h @@ -0,0 +1,32 @@ +#ifndef CIO_DEBUG_H +#define CIO_DEBUG_H + +#include <asm/debug.h> + +#define CIO_TRACE_EVENT(imp, txt) do { \ + debug_text_event(cio_debug_trace_id, imp, txt); \ + } while (0) + +#define CIO_MSG_EVENT(imp, args...) do { \ + debug_sprintf_event(cio_debug_msg_id, imp , ##args); \ + } while (0) + +#define CIO_CRW_EVENT(imp, args...) do { \ + debug_sprintf_event(cio_debug_crw_id, imp , ##args); \ + } while (0) + +#define CIO_HEX_EVENT(imp, args...) do { \ + debug_event(cio_debug_trace_id, imp, ##args); \ + } while (0) + +#define CIO_DEBUG(printk_level,event_level,msg...) ({ \ + if (cio_show_msg) printk(printk_level msg); \ + CIO_MSG_EVENT (event_level, msg); \ +}) + +/* for use of debug feature */ +extern debug_info_t *cio_debug_msg_id; +extern debug_info_t *cio_debug_trace_id; +extern debug_info_t *cio_debug_crw_id; + +#endif diff --git a/drivers/s390/cio/cmf.c b/drivers/s390/cio/cmf.c new file mode 100644 index 000000000000..49def26ba383 --- /dev/null +++ b/drivers/s390/cio/cmf.c @@ -0,0 +1,1042 @@ +/* + * linux/drivers/s390/cio/cmf.c ($Revision: 1.16 $) + * + * Linux on zSeries Channel Measurement Facility support + * + * Copyright 2000,2003 IBM Corporation + * + * Author: Arnd Bergmann <arndb@de.ibm.com> + * + * original idea from Natarajan Krishnaswami <nkrishna@us.ibm.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/bootmem.h> +#include <linux/device.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/moduleparam.h> + +#include <asm/ccwdev.h> +#include <asm/cio.h> +#include <asm/cmb.h> + +#include "cio.h" +#include "css.h" +#include "device.h" +#include "ioasm.h" +#include "chsc.h" + +/* parameter to enable cmf during boot, possible uses are: + * "s390cmf" -- enable cmf and allocate 2 MB of ram so measuring can be + * used on any subchannel + * "s390cmf=<num>" -- enable cmf and allocate enough memory to measure + * <num> subchannel, where <num> is an integer + * between 1 and 65535, default is 1024 + */ +#define ARGSTRING "s390cmf" + +/* indices for READCMB */ +enum cmb_index { + /* basic and exended format: */ + cmb_ssch_rsch_count, + cmb_sample_count, + cmb_device_connect_time, + cmb_function_pending_time, + cmb_device_disconnect_time, + cmb_control_unit_queuing_time, + cmb_device_active_only_time, + /* extended format only: */ + cmb_device_busy_time, + cmb_initial_command_response_time, +}; + +/** + * enum cmb_format - types of supported measurement block formats + * + * @CMF_BASIC: traditional channel measurement blocks supported + * by all machines that we run on + * @CMF_EXTENDED: improved format that was introduced with the z990 + * machine + * @CMF_AUTODETECT: default: use extended format when running on a z990 + * or later machine, otherwise fall back to basic format + **/ +enum cmb_format { + CMF_BASIC, + CMF_EXTENDED, + CMF_AUTODETECT = -1, +}; +/** + * format - actual format for all measurement blocks + * + * The format module parameter can be set to a value of 0 (zero) + * or 1, indicating basic or extended format as described for + * enum cmb_format. + */ +static int format = CMF_AUTODETECT; +module_param(format, bool, 0444); + +/** + * struct cmb_operations - functions to use depending on cmb_format + * + * all these functions operate on a struct cmf_device. There is only + * one instance of struct cmb_operations because all cmf_device + * objects are guaranteed to be of the same type. + * + * @alloc: allocate memory for a channel measurement block, + * either with the help of a special pool or with kmalloc + * @free: free memory allocated with @alloc + * @set: enable or disable measurement + * @readall: read a measurement block in a common format + * @reset: clear the data in the associated measurement block and + * reset its time stamp + */ +struct cmb_operations { + int (*alloc) (struct ccw_device*); + void(*free) (struct ccw_device*); + int (*set) (struct ccw_device*, u32); + u64 (*read) (struct ccw_device*, int); + int (*readall)(struct ccw_device*, struct cmbdata *); + void (*reset) (struct ccw_device*); + + struct attribute_group *attr_group; +}; +static struct cmb_operations *cmbops; + +/* our user interface is designed in terms of nanoseconds, + * while the hardware measures total times in its own + * unit.*/ +static inline u64 time_to_nsec(u32 value) +{ + return ((u64)value) * 128000ull; +} + +/* + * Users are usually interested in average times, + * not accumulated time. + * This also helps us with atomicity problems + * when reading sinlge values. + */ +static inline u64 time_to_avg_nsec(u32 value, u32 count) +{ + u64 ret; + + /* no samples yet, avoid division by 0 */ + if (count == 0) + return 0; + + /* value comes in units of 128 µsec */ + ret = time_to_nsec(value); + do_div(ret, count); + + return ret; +} + +/* activate or deactivate the channel monitor. When area is NULL, + * the monitor is deactivated. The channel monitor needs to + * be active in order to measure subchannels, which also need + * to be enabled. */ +static inline void +cmf_activate(void *area, unsigned int onoff) +{ + register void * __gpr2 asm("2"); + register long __gpr1 asm("1"); + + __gpr2 = area; + __gpr1 = onoff ? 2 : 0; + /* activate channel measurement */ + asm("schm" : : "d" (__gpr2), "d" (__gpr1) ); +} + +static int +set_schib(struct ccw_device *cdev, u32 mme, int mbfc, unsigned long address) +{ + int ret; + int retry; + struct subchannel *sch; + struct schib *schib; + + sch = to_subchannel(cdev->dev.parent); + schib = &sch->schib; + /* msch can silently fail, so do it again if necessary */ + for (retry = 0; retry < 3; retry++) { + /* prepare schib */ + stsch(sch->irq, schib); + schib->pmcw.mme = mme; + schib->pmcw.mbfc = mbfc; + /* address can be either a block address or a block index */ + if (mbfc) + schib->mba = address; + else + schib->pmcw.mbi = address; + + /* try to submit it */ + switch(ret = msch_err(sch->irq, schib)) { + case 0: + break; + case 1: + case 2: /* in I/O or status pending */ + ret = -EBUSY; + break; + case 3: /* subchannel is no longer valid */ + ret = -ENODEV; + break; + default: /* msch caught an exception */ + ret = -EINVAL; + break; + } + stsch(sch->irq, schib); /* restore the schib */ + + if (ret) + break; + + /* check if it worked */ + if (schib->pmcw.mme == mme && + schib->pmcw.mbfc == mbfc && + (mbfc ? (schib->mba == address) + : (schib->pmcw.mbi == address))) + return 0; + + ret = -EINVAL; + } + + return ret; +} + +struct set_schib_struct { + u32 mme; + int mbfc; + unsigned long address; + wait_queue_head_t wait; + int ret; +}; + +static int set_schib_wait(struct ccw_device *cdev, u32 mme, + int mbfc, unsigned long address) +{ + struct set_schib_struct s = { + .mme = mme, + .mbfc = mbfc, + .address = address, + .wait = __WAIT_QUEUE_HEAD_INITIALIZER(s.wait), + }; + + spin_lock_irq(cdev->ccwlock); + s.ret = set_schib(cdev, mme, mbfc, address); + if (s.ret != -EBUSY) { + goto out_nowait; + } + + if (cdev->private->state != DEV_STATE_ONLINE) { + s.ret = -EBUSY; + /* if the device is not online, don't even try again */ + goto out_nowait; + } + cdev->private->state = DEV_STATE_CMFCHANGE; + cdev->private->cmb_wait = &s; + s.ret = 1; + + spin_unlock_irq(cdev->ccwlock); + if (wait_event_interruptible(s.wait, s.ret != 1)) { + spin_lock_irq(cdev->ccwlock); + if (s.ret == 1) { + s.ret = -ERESTARTSYS; + cdev->private->cmb_wait = 0; + if (cdev->private->state == DEV_STATE_CMFCHANGE) + cdev->private->state = DEV_STATE_ONLINE; + } + spin_unlock_irq(cdev->ccwlock); + } + return s.ret; + +out_nowait: + spin_unlock_irq(cdev->ccwlock); + return s.ret; +} + +void retry_set_schib(struct ccw_device *cdev) +{ + struct set_schib_struct *s; + + s = cdev->private->cmb_wait; + cdev->private->cmb_wait = 0; + if (!s) { + WARN_ON(1); + return; + } + s->ret = set_schib(cdev, s->mme, s->mbfc, s->address); + wake_up(&s->wait); +} + +/** + * struct cmb_area - container for global cmb data + * + * @mem: pointer to CMBs (only in basic measurement mode) + * @list: contains a linked list of all subchannels + * @lock: protect concurrent access to @mem and @list + */ +struct cmb_area { + struct cmb *mem; + struct list_head list; + int num_channels; + spinlock_t lock; +}; + +static struct cmb_area cmb_area = { + .lock = SPIN_LOCK_UNLOCKED, + .list = LIST_HEAD_INIT(cmb_area.list), + .num_channels = 1024, +}; + + +/* ****** old style CMB handling ********/ + +/** int maxchannels + * + * Basic channel measurement blocks are allocated in one contiguous + * block of memory, which can not be moved as long as any channel + * is active. Therefore, a maximum number of subchannels needs to + * be defined somewhere. This is a module parameter, defaulting to + * a resonable value of 1024, or 32 kb of memory. + * Current kernels don't allow kmalloc with more than 128kb, so the + * maximum is 4096 + */ + +module_param_named(maxchannels, cmb_area.num_channels, uint, 0444); + +/** + * struct cmb - basic channel measurement block + * + * cmb as used by the hardware the fields are described in z/Architecture + * Principles of Operation, chapter 17. + * The area to be a contiguous array and may not be reallocated or freed. + * Only one cmb area can be present in the system. + */ +struct cmb { + u16 ssch_rsch_count; + u16 sample_count; + u32 device_connect_time; + u32 function_pending_time; + u32 device_disconnect_time; + u32 control_unit_queuing_time; + u32 device_active_only_time; + u32 reserved[2]; +}; + +/* insert a single device into the cmb_area list + * called with cmb_area.lock held from alloc_cmb + */ +static inline int +alloc_cmb_single (struct ccw_device *cdev) +{ + struct cmb *cmb; + struct ccw_device_private *node; + int ret; + + spin_lock_irq(cdev->ccwlock); + if (!list_empty(&cdev->private->cmb_list)) { + ret = -EBUSY; + goto out; + } + + /* find first unused cmb in cmb_area.mem. + * this is a little tricky: cmb_area.list + * remains sorted by ->cmb pointers */ + cmb = cmb_area.mem; + list_for_each_entry(node, &cmb_area.list, cmb_list) { + if ((struct cmb*)node->cmb > cmb) + break; + cmb++; + } + if (cmb - cmb_area.mem >= cmb_area.num_channels) { + ret = -ENOMEM; + goto out; + } + + /* insert new cmb */ + list_add_tail(&cdev->private->cmb_list, &node->cmb_list); + cdev->private->cmb = cmb; + ret = 0; +out: + spin_unlock_irq(cdev->ccwlock); + return ret; +} + +static int +alloc_cmb (struct ccw_device *cdev) +{ + int ret; + struct cmb *mem; + ssize_t size; + + spin_lock(&cmb_area.lock); + + if (!cmb_area.mem) { + /* there is no user yet, so we need a new area */ + size = sizeof(struct cmb) * cmb_area.num_channels; + WARN_ON(!list_empty(&cmb_area.list)); + + spin_unlock(&cmb_area.lock); + mem = (void*)__get_free_pages(GFP_KERNEL | GFP_DMA, + get_order(size)); + spin_lock(&cmb_area.lock); + + if (cmb_area.mem) { + /* ok, another thread was faster */ + free_pages((unsigned long)mem, get_order(size)); + } else if (!mem) { + /* no luck */ + ret = -ENOMEM; + goto out; + } else { + /* everything ok */ + memset(mem, 0, size); + cmb_area.mem = mem; + cmf_activate(cmb_area.mem, 1); + } + } + + /* do the actual allocation */ + ret = alloc_cmb_single(cdev); +out: + spin_unlock(&cmb_area.lock); + + return ret; +} + +static void +free_cmb(struct ccw_device *cdev) +{ + struct ccw_device_private *priv; + + priv = cdev->private; + + spin_lock(&cmb_area.lock); + spin_lock_irq(cdev->ccwlock); + + if (list_empty(&priv->cmb_list)) { + /* already freed */ + goto out; + } + + priv->cmb = NULL; + list_del_init(&priv->cmb_list); + + if (list_empty(&cmb_area.list)) { + ssize_t size; + size = sizeof(struct cmb) * cmb_area.num_channels; + cmf_activate(NULL, 0); + free_pages((unsigned long)cmb_area.mem, get_order(size)); + cmb_area.mem = NULL; + } +out: + spin_unlock_irq(cdev->ccwlock); + spin_unlock(&cmb_area.lock); +} + +static int +set_cmb(struct ccw_device *cdev, u32 mme) +{ + u16 offset; + + if (!cdev->private->cmb) + return -EINVAL; + + offset = mme ? (struct cmb *)cdev->private->cmb - cmb_area.mem : 0; + + return set_schib_wait(cdev, mme, 0, offset); +} + +static u64 +read_cmb (struct ccw_device *cdev, int index) +{ + /* yes, we have to put it on the stack + * because the cmb must only be accessed + * atomically, e.g. with mvc */ + struct cmb cmb; + unsigned long flags; + u32 val; + + spin_lock_irqsave(cdev->ccwlock, flags); + if (!cdev->private->cmb) { + spin_unlock_irqrestore(cdev->ccwlock, flags); + return 0; + } + + cmb = *(struct cmb*)cdev->private->cmb; + spin_unlock_irqrestore(cdev->ccwlock, flags); + + switch (index) { + case cmb_ssch_rsch_count: + return cmb.ssch_rsch_count; + case cmb_sample_count: + return cmb.sample_count; + case cmb_device_connect_time: + val = cmb.device_connect_time; + break; + case cmb_function_pending_time: + val = cmb.function_pending_time; + break; + case cmb_device_disconnect_time: + val = cmb.device_disconnect_time; + break; + case cmb_control_unit_queuing_time: + val = cmb.control_unit_queuing_time; + break; + case cmb_device_active_only_time: + val = cmb.device_active_only_time; + break; + default: + return 0; + } + return time_to_avg_nsec(val, cmb.sample_count); +} + +static int +readall_cmb (struct ccw_device *cdev, struct cmbdata *data) +{ + /* yes, we have to put it on the stack + * because the cmb must only be accessed + * atomically, e.g. with mvc */ + struct cmb cmb; + unsigned long flags; + u64 time; + + spin_lock_irqsave(cdev->ccwlock, flags); + if (!cdev->private->cmb) { + spin_unlock_irqrestore(cdev->ccwlock, flags); + return -ENODEV; + } + + cmb = *(struct cmb*)cdev->private->cmb; + time = get_clock() - cdev->private->cmb_start_time; + spin_unlock_irqrestore(cdev->ccwlock, flags); + + memset(data, 0, sizeof(struct cmbdata)); + + /* we only know values before device_busy_time */ + data->size = offsetof(struct cmbdata, device_busy_time); + + /* convert to nanoseconds */ + data->elapsed_time = (time * 1000) >> 12; + + /* copy data to new structure */ + data->ssch_rsch_count = cmb.ssch_rsch_count; + data->sample_count = cmb.sample_count; + + /* time fields are converted to nanoseconds while copying */ + data->device_connect_time = time_to_nsec(cmb.device_connect_time); + data->function_pending_time = time_to_nsec(cmb.function_pending_time); + data->device_disconnect_time = time_to_nsec(cmb.device_disconnect_time); + data->control_unit_queuing_time + = time_to_nsec(cmb.control_unit_queuing_time); + data->device_active_only_time + = time_to_nsec(cmb.device_active_only_time); + + return 0; +} + +static void +reset_cmb(struct ccw_device *cdev) +{ + struct cmb *cmb; + spin_lock_irq(cdev->ccwlock); + cmb = cdev->private->cmb; + if (cmb) + memset (cmb, 0, sizeof (*cmb)); + cdev->private->cmb_start_time = get_clock(); + spin_unlock_irq(cdev->ccwlock); +} + +static struct attribute_group cmf_attr_group; + +static struct cmb_operations cmbops_basic = { + .alloc = alloc_cmb, + .free = free_cmb, + .set = set_cmb, + .read = read_cmb, + .readall = readall_cmb, + .reset = reset_cmb, + .attr_group = &cmf_attr_group, +}; + +/* ******** extended cmb handling ********/ + +/** + * struct cmbe - extended channel measurement block + * + * cmb as used by the hardware, may be in any 64 bit physical location, + * the fields are described in z/Architecture Principles of Operation, + * third edition, chapter 17. + */ +struct cmbe { + u32 ssch_rsch_count; + u32 sample_count; + u32 device_connect_time; + u32 function_pending_time; + u32 device_disconnect_time; + u32 control_unit_queuing_time; + u32 device_active_only_time; + u32 device_busy_time; + u32 initial_command_response_time; + u32 reserved[7]; +}; + +/* kmalloc only guarantees 8 byte alignment, but we need cmbe + * pointers to be naturally aligned. Make sure to allocate + * enough space for two cmbes */ +static inline struct cmbe* cmbe_align(struct cmbe *c) +{ + unsigned long addr; + addr = ((unsigned long)c + sizeof (struct cmbe) - sizeof(long)) & + ~(sizeof (struct cmbe) - sizeof(long)); + return (struct cmbe*)addr; +} + +static int +alloc_cmbe (struct ccw_device *cdev) +{ + struct cmbe *cmbe; + cmbe = kmalloc (sizeof (*cmbe) * 2, GFP_KERNEL); + if (!cmbe) + return -ENOMEM; + + spin_lock_irq(cdev->ccwlock); + if (cdev->private->cmb) { + kfree(cmbe); + spin_unlock_irq(cdev->ccwlock); + return -EBUSY; + } + + cdev->private->cmb = cmbe; + spin_unlock_irq(cdev->ccwlock); + + /* activate global measurement if this is the first channel */ + spin_lock(&cmb_area.lock); + if (list_empty(&cmb_area.list)) + cmf_activate(NULL, 1); + list_add_tail(&cdev->private->cmb_list, &cmb_area.list); + spin_unlock(&cmb_area.lock); + + return 0; +} + +static void +free_cmbe (struct ccw_device *cdev) +{ + spin_lock_irq(cdev->ccwlock); + if (cdev->private->cmb) + kfree(cdev->private->cmb); + cdev->private->cmb = NULL; + spin_unlock_irq(cdev->ccwlock); + + /* deactivate global measurement if this is the last channel */ + spin_lock(&cmb_area.lock); + list_del_init(&cdev->private->cmb_list); + if (list_empty(&cmb_area.list)) + cmf_activate(NULL, 0); + spin_unlock(&cmb_area.lock); +} + +static int +set_cmbe(struct ccw_device *cdev, u32 mme) +{ + unsigned long mba; + + if (!cdev->private->cmb) + return -EINVAL; + mba = mme ? (unsigned long) cmbe_align(cdev->private->cmb) : 0; + + return set_schib_wait(cdev, mme, 1, mba); +} + + +u64 +read_cmbe (struct ccw_device *cdev, int index) +{ + /* yes, we have to put it on the stack + * because the cmb must only be accessed + * atomically, e.g. with mvc */ + struct cmbe cmb; + unsigned long flags; + u32 val; + + spin_lock_irqsave(cdev->ccwlock, flags); + if (!cdev->private->cmb) { + spin_unlock_irqrestore(cdev->ccwlock, flags); + return 0; + } + + cmb = *cmbe_align(cdev->private->cmb); + spin_unlock_irqrestore(cdev->ccwlock, flags); + + switch (index) { + case cmb_ssch_rsch_count: + return cmb.ssch_rsch_count; + case cmb_sample_count: + return cmb.sample_count; + case cmb_device_connect_time: + val = cmb.device_connect_time; + break; + case cmb_function_pending_time: + val = cmb.function_pending_time; + break; + case cmb_device_disconnect_time: + val = cmb.device_disconnect_time; + break; + case cmb_control_unit_queuing_time: + val = cmb.control_unit_queuing_time; + break; + case cmb_device_active_only_time: + val = cmb.device_active_only_time; + break; + case cmb_device_busy_time: + val = cmb.device_busy_time; + break; + case cmb_initial_command_response_time: + val = cmb.initial_command_response_time; + break; + default: + return 0; + } + return time_to_avg_nsec(val, cmb.sample_count); +} + +static int +readall_cmbe (struct ccw_device *cdev, struct cmbdata *data) +{ + /* yes, we have to put it on the stack + * because the cmb must only be accessed + * atomically, e.g. with mvc */ + struct cmbe cmb; + unsigned long flags; + u64 time; + + spin_lock_irqsave(cdev->ccwlock, flags); + if (!cdev->private->cmb) { + spin_unlock_irqrestore(cdev->ccwlock, flags); + return -ENODEV; + } + + cmb = *cmbe_align(cdev->private->cmb); + time = get_clock() - cdev->private->cmb_start_time; + spin_unlock_irqrestore(cdev->ccwlock, flags); + + memset (data, 0, sizeof(struct cmbdata)); + + /* we only know values before device_busy_time */ + data->size = offsetof(struct cmbdata, device_busy_time); + + /* conver to nanoseconds */ + data->elapsed_time = (time * 1000) >> 12; + + /* copy data to new structure */ + data->ssch_rsch_count = cmb.ssch_rsch_count; + data->sample_count = cmb.sample_count; + + /* time fields are converted to nanoseconds while copying */ + data->device_connect_time = time_to_nsec(cmb.device_connect_time); + data->function_pending_time = time_to_nsec(cmb.function_pending_time); + data->device_disconnect_time = time_to_nsec(cmb.device_disconnect_time); + data->control_unit_queuing_time + = time_to_nsec(cmb.control_unit_queuing_time); + data->device_active_only_time + = time_to_nsec(cmb.device_active_only_time); + data->device_busy_time = time_to_nsec(cmb.device_busy_time); + data->initial_command_response_time + = time_to_nsec(cmb.initial_command_response_time); + + return 0; +} + +static void +reset_cmbe(struct ccw_device *cdev) +{ + struct cmbe *cmb; + spin_lock_irq(cdev->ccwlock); + cmb = cmbe_align(cdev->private->cmb); + if (cmb) + memset (cmb, 0, sizeof (*cmb)); + cdev->private->cmb_start_time = get_clock(); + spin_unlock_irq(cdev->ccwlock); +} + +static struct attribute_group cmf_attr_group_ext; + +static struct cmb_operations cmbops_extended = { + .alloc = alloc_cmbe, + .free = free_cmbe, + .set = set_cmbe, + .read = read_cmbe, + .readall = readall_cmbe, + .reset = reset_cmbe, + .attr_group = &cmf_attr_group_ext, +}; + + +static ssize_t +cmb_show_attr(struct device *dev, char *buf, enum cmb_index idx) +{ + return sprintf(buf, "%lld\n", + (unsigned long long) cmf_read(to_ccwdev(dev), idx)); +} + +static ssize_t +cmb_show_avg_sample_interval(struct device *dev, char *buf) +{ + struct ccw_device *cdev; + long interval; + unsigned long count; + + cdev = to_ccwdev(dev); + interval = get_clock() - cdev->private->cmb_start_time; + count = cmf_read(cdev, cmb_sample_count); + if (count) + interval /= count; + else + interval = -1; + return sprintf(buf, "%ld\n", interval); +} + +static ssize_t +cmb_show_avg_utilization(struct device *dev, char *buf) +{ + struct cmbdata data; + u64 utilization; + unsigned long t, u; + int ret; + + ret = cmf_readall(to_ccwdev(dev), &data); + if (ret) + return ret; + + utilization = data.device_connect_time + + data.function_pending_time + + data.device_disconnect_time; + + /* shift to avoid long long division */ + while (-1ul < (data.elapsed_time | utilization)) { + utilization >>= 8; + data.elapsed_time >>= 8; + } + + /* calculate value in 0.1 percent units */ + t = (unsigned long) data.elapsed_time / 1000; + u = (unsigned long) utilization / t; + + return sprintf(buf, "%02ld.%01ld%%\n", u/ 10, u - (u/ 10) * 10); +} + +#define cmf_attr(name) \ +static ssize_t show_ ## name (struct device * dev, char * buf) \ +{ return cmb_show_attr((dev), buf, cmb_ ## name); } \ +static DEVICE_ATTR(name, 0444, show_ ## name, NULL); + +#define cmf_attr_avg(name) \ +static ssize_t show_avg_ ## name (struct device * dev, char * buf) \ +{ return cmb_show_attr((dev), buf, cmb_ ## name); } \ +static DEVICE_ATTR(avg_ ## name, 0444, show_avg_ ## name, NULL); + +cmf_attr(ssch_rsch_count); +cmf_attr(sample_count); +cmf_attr_avg(device_connect_time); +cmf_attr_avg(function_pending_time); +cmf_attr_avg(device_disconnect_time); +cmf_attr_avg(control_unit_queuing_time); +cmf_attr_avg(device_active_only_time); +cmf_attr_avg(device_busy_time); +cmf_attr_avg(initial_command_response_time); + +static DEVICE_ATTR(avg_sample_interval, 0444, cmb_show_avg_sample_interval, NULL); +static DEVICE_ATTR(avg_utilization, 0444, cmb_show_avg_utilization, NULL); + +static struct attribute *cmf_attributes[] = { + &dev_attr_avg_sample_interval.attr, + &dev_attr_avg_utilization.attr, + &dev_attr_ssch_rsch_count.attr, + &dev_attr_sample_count.attr, + &dev_attr_avg_device_connect_time.attr, + &dev_attr_avg_function_pending_time.attr, + &dev_attr_avg_device_disconnect_time.attr, + &dev_attr_avg_control_unit_queuing_time.attr, + &dev_attr_avg_device_active_only_time.attr, + 0, +}; + +static struct attribute_group cmf_attr_group = { + .name = "cmf", + .attrs = cmf_attributes, +}; + +static struct attribute *cmf_attributes_ext[] = { + &dev_attr_avg_sample_interval.attr, + &dev_attr_avg_utilization.attr, + &dev_attr_ssch_rsch_count.attr, + &dev_attr_sample_count.attr, + &dev_attr_avg_device_connect_time.attr, + &dev_attr_avg_function_pending_time.attr, + &dev_attr_avg_device_disconnect_time.attr, + &dev_attr_avg_control_unit_queuing_time.attr, + &dev_attr_avg_device_active_only_time.attr, + &dev_attr_avg_device_busy_time.attr, + &dev_attr_avg_initial_command_response_time.attr, + 0, +}; + +static struct attribute_group cmf_attr_group_ext = { + .name = "cmf", + .attrs = cmf_attributes_ext, +}; + +static ssize_t cmb_enable_show(struct device *dev, char *buf) +{ + return sprintf(buf, "%d\n", to_ccwdev(dev)->private->cmb ? 1 : 0); +} + +static ssize_t cmb_enable_store(struct device *dev, const char *buf, size_t c) +{ + struct ccw_device *cdev; + int ret; + + cdev = to_ccwdev(dev); + + switch (buf[0]) { + case '0': + ret = disable_cmf(cdev); + if (ret) + printk(KERN_INFO "disable_cmf failed (%d)\n", ret); + break; + case '1': + ret = enable_cmf(cdev); + if (ret && ret != -EBUSY) + printk(KERN_INFO "enable_cmf failed (%d)\n", ret); + break; + } + + return c; +} + +DEVICE_ATTR(cmb_enable, 0644, cmb_enable_show, cmb_enable_store); + +/* enable_cmf/disable_cmf: module interface for cmf (de)activation */ +int +enable_cmf(struct ccw_device *cdev) +{ + int ret; + + ret = cmbops->alloc(cdev); + cmbops->reset(cdev); + if (ret) + return ret; + ret = cmbops->set(cdev, 2); + if (ret) { + cmbops->free(cdev); + return ret; + } + ret = sysfs_create_group(&cdev->dev.kobj, cmbops->attr_group); + if (!ret) + return 0; + cmbops->set(cdev, 0); //FIXME: this can fail + cmbops->free(cdev); + return ret; +} + +int +disable_cmf(struct ccw_device *cdev) +{ + int ret; + + ret = cmbops->set(cdev, 0); + if (ret) + return ret; + cmbops->free(cdev); + sysfs_remove_group(&cdev->dev.kobj, cmbops->attr_group); + return ret; +} + +u64 +cmf_read(struct ccw_device *cdev, int index) +{ + return cmbops->read(cdev, index); +} + +int +cmf_readall(struct ccw_device *cdev, struct cmbdata *data) +{ + return cmbops->readall(cdev, data); +} + +static int __init +init_cmf(void) +{ + char *format_string; + char *detect_string = "parameter"; + + /* We cannot really autoprobe this. If the user did not give a parameter, + see if we are running on z990 or up, otherwise fall back to basic mode. */ + + if (format == CMF_AUTODETECT) { + if (!css_characteristics_avail || + !css_general_characteristics.ext_mb) { + format = CMF_BASIC; + } else { + format = CMF_EXTENDED; + } + detect_string = "autodetected"; + } else { + detect_string = "parameter"; + } + + switch (format) { + case CMF_BASIC: + format_string = "basic"; + cmbops = &cmbops_basic; + if (cmb_area.num_channels > 4096 || cmb_area.num_channels < 1) { + printk(KERN_ERR "Basic channel measurement facility" + " can only use 1 to 4096 devices\n" + KERN_ERR "when the cmf driver is built" + " as a loadable module\n"); + return 1; + } + break; + case CMF_EXTENDED: + format_string = "extended"; + cmbops = &cmbops_extended; + break; + default: + printk(KERN_ERR "Invalid format %d for channel " + "measurement facility\n", format); + return 1; + } + + printk(KERN_INFO "Channel measurement facility using %s format (%s)\n", + format_string, detect_string); + return 0; +} + +module_init(init_cmf); + + +MODULE_AUTHOR("Arnd Bergmann <arndb@de.ibm.com>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("channel measurement facility base driver\n" + "Copyright 2003 IBM Corporation\n"); + +EXPORT_SYMBOL_GPL(enable_cmf); +EXPORT_SYMBOL_GPL(disable_cmf); +EXPORT_SYMBOL_GPL(cmf_read); +EXPORT_SYMBOL_GPL(cmf_readall); diff --git a/drivers/s390/cio/css.c b/drivers/s390/cio/css.c new file mode 100644 index 000000000000..87bd70eeabed --- /dev/null +++ b/drivers/s390/cio/css.c @@ -0,0 +1,575 @@ +/* + * drivers/s390/cio/css.c + * driver for channel subsystem + * $Revision: 1.85 $ + * + * Copyright (C) 2002 IBM Deutschland Entwicklung GmbH, + * IBM Corporation + * Author(s): Arnd Bergmann (arndb@de.ibm.com) + * Cornelia Huck (cohuck@de.ibm.com) + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/list.h> + +#include "css.h" +#include "cio.h" +#include "cio_debug.h" +#include "ioasm.h" +#include "chsc.h" + +unsigned int highest_subchannel; +int need_rescan = 0; +int css_init_done = 0; + +struct pgid global_pgid; +int css_characteristics_avail = 0; + +struct device css_bus_device = { + .bus_id = "css0", +}; + +static struct subchannel * +css_alloc_subchannel(int irq) +{ + struct subchannel *sch; + int ret; + + sch = kmalloc (sizeof (*sch), GFP_KERNEL | GFP_DMA); + if (sch == NULL) + return ERR_PTR(-ENOMEM); + ret = cio_validate_subchannel (sch, irq); + if (ret < 0) { + kfree(sch); + return ERR_PTR(ret); + } + if (irq > highest_subchannel) + highest_subchannel = irq; + + if (sch->st != SUBCHANNEL_TYPE_IO) { + /* For now we ignore all non-io subchannels. */ + kfree(sch); + return ERR_PTR(-EINVAL); + } + + /* + * Set intparm to subchannel address. + * This is fine even on 64bit since the subchannel is always located + * under 2G. + */ + sch->schib.pmcw.intparm = (__u32)(unsigned long)sch; + ret = cio_modify(sch); + if (ret) { + kfree(sch); + return ERR_PTR(ret); + } + return sch; +} + +static void +css_free_subchannel(struct subchannel *sch) +{ + if (sch) { + /* Reset intparm to zeroes. */ + sch->schib.pmcw.intparm = 0; + cio_modify(sch); + kfree(sch); + } + +} + +static void +css_subchannel_release(struct device *dev) +{ + struct subchannel *sch; + + sch = to_subchannel(dev); + if (!cio_is_console(sch->irq)) + kfree(sch); +} + +extern int css_get_ssd_info(struct subchannel *sch); + +static int +css_register_subchannel(struct subchannel *sch) +{ + int ret; + + /* Initialize the subchannel structure */ + sch->dev.parent = &css_bus_device; + sch->dev.bus = &css_bus_type; + sch->dev.release = &css_subchannel_release; + + /* make it known to the system */ + ret = device_register(&sch->dev); + if (ret) + printk (KERN_WARNING "%s: could not register %s\n", + __func__, sch->dev.bus_id); + else + css_get_ssd_info(sch); + return ret; +} + +int +css_probe_device(int irq) +{ + int ret; + struct subchannel *sch; + + sch = css_alloc_subchannel(irq); + if (IS_ERR(sch)) + return PTR_ERR(sch); + ret = css_register_subchannel(sch); + if (ret) + css_free_subchannel(sch); + return ret; +} + +struct subchannel * +get_subchannel_by_schid(int irq) +{ + struct subchannel *sch; + struct list_head *entry; + struct device *dev; + + if (!get_bus(&css_bus_type)) + return NULL; + down_read(&css_bus_type.subsys.rwsem); + sch = NULL; + list_for_each(entry, &css_bus_type.devices.list) { + dev = get_device(container_of(entry, + struct device, bus_list)); + if (!dev) + continue; + sch = to_subchannel(dev); + if (sch->irq == irq) + break; + put_device(dev); + sch = NULL; + } + up_read(&css_bus_type.subsys.rwsem); + put_bus(&css_bus_type); + + return sch; +} + +static inline int +css_get_subchannel_status(struct subchannel *sch, int schid) +{ + struct schib schib; + int cc; + + cc = stsch(schid, &schib); + if (cc) + return CIO_GONE; + if (!schib.pmcw.dnv) + return CIO_GONE; + if (sch && sch->schib.pmcw.dnv && + (schib.pmcw.dev != sch->schib.pmcw.dev)) + return CIO_REVALIDATE; + if (sch && !sch->lpm) + return CIO_NO_PATH; + return CIO_OPER; +} + +static int +css_evaluate_subchannel(int irq, int slow) +{ + int event, ret, disc; + struct subchannel *sch; + unsigned long flags; + + sch = get_subchannel_by_schid(irq); + disc = sch ? device_is_disconnected(sch) : 0; + if (disc && slow) { + if (sch) + put_device(&sch->dev); + return 0; /* Already processed. */ + } + /* + * We've got a machine check, so running I/O won't get an interrupt. + * Kill any pending timers. + */ + if (sch) + device_kill_pending_timer(sch); + 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); + CIO_MSG_EVENT(4, "Evaluating schid %04x, event %d, %s, %s path.\n", + irq, event, sch?(disc?"disconnected":"normal"):"unknown", + slow?"slow":"fast"); + switch (event) { + case CIO_NO_PATH: + case CIO_GONE: + if (!sch) { + /* Never used this subchannel. Ignore. */ + ret = 0; + break; + } + if (disc && (event == CIO_NO_PATH)) { + /* + * Uargh, hack again. Because we don't get a machine + * check on configure on, our path bookkeeping can + * be out of date here (it's fine while we only do + * logical varying or get chsc machine checks). We + * need to force reprobing or we might miss devices + * coming operational again. It won't do harm in real + * no path situations. + */ + spin_lock_irqsave(&sch->lock, flags); + device_trigger_reprobe(sch); + spin_unlock_irqrestore(&sch->lock, flags); + ret = 0; + break; + } + if (sch->driver && sch->driver->notify && + sch->driver->notify(&sch->dev, event)) { + cio_disable_subchannel(sch); + device_set_disconnected(sch); + ret = 0; + break; + } + /* + * Unregister subchannel. + * The device will be killed automatically. + */ + cio_disable_subchannel(sch); + device_unregister(&sch->dev); + /* Reset intparm to zeroes. */ + sch->schib.pmcw.intparm = 0; + cio_modify(sch); + put_device(&sch->dev); + ret = 0; + break; + case CIO_REVALIDATE: + /* + * Revalidation machine check. Sick. + * We don't notify the driver since we have to throw the device + * away in any case. + */ + if (!disc) { + device_unregister(&sch->dev); + /* Reset intparm to zeroes. */ + sch->schib.pmcw.intparm = 0; + cio_modify(sch); + put_device(&sch->dev); + ret = css_probe_device(irq); + } else { + /* + * We can't immediately deregister the disconnected + * device since it might block. + */ + spin_lock_irqsave(&sch->lock, flags); + device_trigger_reprobe(sch); + spin_unlock_irqrestore(&sch->lock, flags); + ret = 0; + } + break; + case CIO_OPER: + if (disc) { + spin_lock_irqsave(&sch->lock, flags); + /* Get device operational again. */ + device_trigger_reprobe(sch); + spin_unlock_irqrestore(&sch->lock, flags); + } + ret = sch ? 0 : css_probe_device(irq); + break; + default: + BUG(); + ret = 0; + } + return ret; +} + +static void +css_rescan_devices(void) +{ + int irq, ret; + + for (irq = 0; irq < __MAX_SUBCHANNELS; irq++) { + ret = css_evaluate_subchannel(irq, 1); + /* No more memory. It doesn't make sense to continue. No + * panic because this can happen in midflight and just + * because we can't use a new device is no reason to crash + * the system. */ + if (ret == -ENOMEM) + break; + /* -ENXIO indicates that there are no more subchannels. */ + if (ret == -ENXIO) + break; + } +} + +struct slow_subchannel { + struct list_head slow_list; + unsigned long schid; +}; + +static LIST_HEAD(slow_subchannels_head); +static DEFINE_SPINLOCK(slow_subchannel_lock); + +static void +css_trigger_slow_path(void) +{ + CIO_TRACE_EVENT(4, "slowpath"); + + if (need_rescan) { + need_rescan = 0; + css_rescan_devices(); + return; + } + + spin_lock_irq(&slow_subchannel_lock); + while (!list_empty(&slow_subchannels_head)) { + struct slow_subchannel *slow_sch = + list_entry(slow_subchannels_head.next, + struct slow_subchannel, slow_list); + + list_del_init(slow_subchannels_head.next); + spin_unlock_irq(&slow_subchannel_lock); + css_evaluate_subchannel(slow_sch->schid, 1); + spin_lock_irq(&slow_subchannel_lock); + kfree(slow_sch); + } + spin_unlock_irq(&slow_subchannel_lock); +} + +typedef void (*workfunc)(void *); +DECLARE_WORK(slow_path_work, (workfunc)css_trigger_slow_path, NULL); +struct workqueue_struct *slow_path_wq; + +/* + * Rescan for new devices. FIXME: This is slow. + * This function is called when we have lost CRWs due to overflows and we have + * to do subchannel housekeeping. + */ +void +css_reiterate_subchannels(void) +{ + css_clear_subchannel_slow_list(); + need_rescan = 1; +} + +/* + * Called from the machine check handler for subchannel report words. + */ +int +css_process_crw(int irq) +{ + int ret; + + CIO_CRW_EVENT(2, "source is subchannel %04X\n", irq); + + if (need_rescan) + /* We need to iterate all subchannels anyway. */ + return -EAGAIN; + /* + * Since we are always presented with IPI in the CRW, we have to + * use stsch() to find out if the subchannel in question has come + * or gone. + */ + ret = css_evaluate_subchannel(irq, 0); + if (ret == -EAGAIN) { + if (css_enqueue_subchannel_slow(irq)) { + css_clear_subchannel_slow_list(); + need_rescan = 1; + } + } + return ret; +} + +static void __init +css_generate_pgid(void) +{ + /* Let's build our path group ID here. */ + if (css_characteristics_avail && css_general_characteristics.mcss) + global_pgid.cpu_addr = 0x8000; + else { +#ifdef CONFIG_SMP + global_pgid.cpu_addr = hard_smp_processor_id(); +#else + global_pgid.cpu_addr = 0; +#endif + } + global_pgid.cpu_id = ((cpuid_t *) __LC_CPUID)->ident; + global_pgid.cpu_model = ((cpuid_t *) __LC_CPUID)->machine; + global_pgid.tod_high = (__u32) (get_clock() >> 32); +} + +/* + * Now that the driver core is running, we can setup our channel subsystem. + * The struct subchannel's are created during probing (except for the + * static console subchannel). + */ +static int __init +init_channel_subsystem (void) +{ + int ret, irq; + + if (chsc_determine_css_characteristics() == 0) + css_characteristics_avail = 1; + + css_generate_pgid(); + + if ((ret = bus_register(&css_bus_type))) + goto out; + if ((ret = device_register (&css_bus_device))) + goto out_bus; + + css_init_done = 1; + + ctl_set_bit(6, 28); + + for (irq = 0; irq < __MAX_SUBCHANNELS; irq++) { + struct subchannel *sch; + + if (cio_is_console(irq)) + sch = cio_get_console_subchannel(); + else { + sch = css_alloc_subchannel(irq); + if (IS_ERR(sch)) + ret = PTR_ERR(sch); + else + ret = 0; + if (ret == -ENOMEM) + panic("Out of memory in " + "init_channel_subsystem\n"); + /* -ENXIO: no more subchannels. */ + if (ret == -ENXIO) + break; + if (ret) + continue; + } + /* + * We register ALL valid subchannels in ioinfo, even those + * that have been present before init_channel_subsystem. + * These subchannels can't have been registered yet (kmalloc + * not working) so we do it now. This is true e.g. for the + * console subchannel. + */ + css_register_subchannel(sch); + } + return 0; + +out_bus: + bus_unregister(&css_bus_type); +out: + return ret; +} + +/* + * find a driver for a subchannel. They identify by the subchannel + * type with the exception that the console subchannel driver has its own + * subchannel type although the device is an i/o subchannel + */ +static int +css_bus_match (struct device *dev, struct device_driver *drv) +{ + struct subchannel *sch = container_of (dev, struct subchannel, dev); + struct css_driver *driver = container_of (drv, struct css_driver, drv); + + if (sch->st == driver->subchannel_type) + return 1; + + return 0; +} + +struct bus_type css_bus_type = { + .name = "css", + .match = &css_bus_match, +}; + +subsys_initcall(init_channel_subsystem); + +/* + * Register root devices for some drivers. The release function must not be + * in the device drivers, so we do it here. + */ +static void +s390_root_dev_release(struct device *dev) +{ + kfree(dev); +} + +struct device * +s390_root_dev_register(const char *name) +{ + struct device *dev; + int ret; + + if (!strlen(name)) + return ERR_PTR(-EINVAL); + dev = kmalloc(sizeof(struct device), GFP_KERNEL); + if (!dev) + return ERR_PTR(-ENOMEM); + memset(dev, 0, sizeof(struct device)); + strncpy(dev->bus_id, name, min(strlen(name), (size_t)BUS_ID_SIZE)); + dev->release = s390_root_dev_release; + ret = device_register(dev); + if (ret) { + kfree(dev); + return ERR_PTR(ret); + } + return dev; +} + +void +s390_root_dev_unregister(struct device *dev) +{ + if (dev) + device_unregister(dev); +} + +int +css_enqueue_subchannel_slow(unsigned long schid) +{ + struct slow_subchannel *new_slow_sch; + unsigned long flags; + + new_slow_sch = kmalloc(sizeof(struct slow_subchannel), GFP_ATOMIC); + if (!new_slow_sch) + return -ENOMEM; + memset(new_slow_sch, 0, sizeof(struct slow_subchannel)); + new_slow_sch->schid = schid; + spin_lock_irqsave(&slow_subchannel_lock, flags); + list_add_tail(&new_slow_sch->slow_list, &slow_subchannels_head); + spin_unlock_irqrestore(&slow_subchannel_lock, flags); + return 0; +} + +void +css_clear_subchannel_slow_list(void) +{ + unsigned long flags; + + spin_lock_irqsave(&slow_subchannel_lock, flags); + while (!list_empty(&slow_subchannels_head)) { + struct slow_subchannel *slow_sch = + list_entry(slow_subchannels_head.next, + struct slow_subchannel, slow_list); + + list_del_init(slow_subchannels_head.next); + kfree(slow_sch); + } + spin_unlock_irqrestore(&slow_subchannel_lock, flags); +} + + + +int +css_slow_subchannels_exist(void) +{ + return (!list_empty(&slow_subchannels_head)); +} + +MODULE_LICENSE("GPL"); +EXPORT_SYMBOL(css_bus_type); +EXPORT_SYMBOL(s390_root_dev_register); +EXPORT_SYMBOL(s390_root_dev_unregister); +EXPORT_SYMBOL_GPL(css_characteristics_avail); diff --git a/drivers/s390/cio/css.h b/drivers/s390/cio/css.h new file mode 100644 index 000000000000..2004a6c49388 --- /dev/null +++ b/drivers/s390/cio/css.h @@ -0,0 +1,155 @@ +#ifndef _CSS_H +#define _CSS_H + +#include <linux/wait.h> +#include <linux/workqueue.h> + +#include <asm/cio.h> + +/* + * path grouping stuff + */ +#define SPID_FUNC_SINGLE_PATH 0x00 +#define SPID_FUNC_MULTI_PATH 0x80 +#define SPID_FUNC_ESTABLISH 0x00 +#define SPID_FUNC_RESIGN 0x40 +#define SPID_FUNC_DISBAND 0x20 + +#define SNID_STATE1_RESET 0 +#define SNID_STATE1_UNGROUPED 2 +#define SNID_STATE1_GROUPED 3 + +#define SNID_STATE2_NOT_RESVD 0 +#define SNID_STATE2_RESVD_ELSE 2 +#define SNID_STATE2_RESVD_SELF 3 + +#define SNID_STATE3_MULTI_PATH 1 +#define SNID_STATE3_SINGLE_PATH 0 + +struct path_state { + __u8 state1 : 2; /* path state value 1 */ + __u8 state2 : 2; /* path state value 2 */ + __u8 state3 : 1; /* path state value 3 */ + __u8 resvd : 3; /* reserved */ +} __attribute__ ((packed)); + +struct pgid { + union { + __u8 fc; /* SPID function code */ + struct path_state ps; /* SNID path state */ + } inf; + __u32 cpu_addr : 16; /* CPU address */ + __u32 cpu_id : 24; /* CPU identification */ + __u32 cpu_model : 16; /* CPU model */ + __u32 tod_high; /* high word TOD clock */ +} __attribute__ ((packed)); + +extern struct pgid global_pgid; + +#define MAX_CIWS 8 + +/* + * sense-id response buffer layout + */ +struct senseid { + /* common part */ + __u8 reserved; /* always 0x'FF' */ + __u16 cu_type; /* control unit type */ + __u8 cu_model; /* control unit model */ + __u16 dev_type; /* device type */ + __u8 dev_model; /* device model */ + __u8 unused; /* padding byte */ + /* extended part */ + struct ciw ciw[MAX_CIWS]; /* variable # of CIWs */ +} __attribute__ ((packed,aligned(4))); + +struct ccw_device_private { + int state; /* device state */ + atomic_t onoff; + unsigned long registered; + __u16 devno; /* device number */ + __u16 irq; /* subchannel number */ + __u8 imask; /* lpm mask for SNID/SID/SPGID */ + int iretry; /* retry counter SNID/SID/SPGID */ + struct { + unsigned int fast:1; /* post with "channel end" */ + unsigned int repall:1; /* report every interrupt status */ + unsigned int pgroup:1; /* do path grouping */ + unsigned int force:1; /* allow forced online */ + } __attribute__ ((packed)) options; + struct { + unsigned int pgid_single:1; /* use single path for Set PGID */ + unsigned int esid:1; /* Ext. SenseID supported by HW */ + unsigned int dosense:1; /* delayed SENSE required */ + unsigned int doverify:1; /* delayed path verification */ + unsigned int donotify:1; /* call notify function */ + unsigned int recog_done:1; /* dev. recog. complete */ + unsigned int fake_irb:1; /* deliver faked irb */ + } __attribute__((packed)) flags; + unsigned long intparm; /* user interruption parameter */ + struct qdio_irq *qdio_data; + struct irb irb; /* device status */ + struct senseid senseid; /* SenseID info */ + struct pgid pgid; /* path group ID */ + struct ccw1 iccws[2]; /* ccws for SNID/SID/SPGID commands */ + struct work_struct kick_work; + wait_queue_head_t wait_q; + struct timer_list timer; + void *cmb; /* measurement information */ + 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 */ +}; + +/* + * A css driver handles all subchannels of one type. + * Currently, we only care about I/O subchannels (type 0), these + * have a ccw_device connected to them. + */ +struct css_driver { + unsigned int subchannel_type; + struct device_driver drv; + void (*irq)(struct device *); + int (*notify)(struct device *, int); + void (*verify)(struct device *); + void (*termination)(struct device *); +}; + +/* + * all css_drivers have the css_bus_type + */ +extern struct bus_type css_bus_type; +extern struct css_driver io_subchannel_driver; + +int css_probe_device(int irq); +extern struct subchannel * get_subchannel_by_schid(int irq); +extern unsigned int highest_subchannel; +extern int css_init_done; + +#define __MAX_SUBCHANNELS 65536 + +extern struct bus_type css_bus_type; +extern struct device css_bus_device; + +/* Some helper functions for disconnected state. */ +int device_is_disconnected(struct subchannel *); +void device_set_disconnected(struct subchannel *); +void device_trigger_reprobe(struct subchannel *); + +/* Helper functions for vary on/off. */ +int device_is_online(struct subchannel *); +void device_set_waiting(struct subchannel *); + +/* Machine check helper function. */ +void device_kill_pending_timer(struct subchannel *); + +/* Helper functions to build lists for the slow path. */ +int css_enqueue_subchannel_slow(unsigned long schid); +void css_walk_subchannel_slow_list(void (*fn)(unsigned long)); +void css_clear_subchannel_slow_list(void); +int css_slow_subchannels_exist(void); +extern int need_rescan; + +extern struct workqueue_struct *slow_path_wq; +extern struct work_struct slow_path_work; +#endif diff --git a/drivers/s390/cio/device.c b/drivers/s390/cio/device.c new file mode 100644 index 000000000000..df0325505e4e --- /dev/null +++ b/drivers/s390/cio/device.c @@ -0,0 +1,1135 @@ +/* + * drivers/s390/cio/device.c + * bus driver for ccw devices + * $Revision: 1.131 $ + * + * Copyright (C) 2002 IBM Deutschland Entwicklung GmbH, + * IBM Corporation + * Author(s): Arnd Bergmann (arndb@de.ibm.com) + * Cornelia Huck (cohuck@de.ibm.com) + * Martin Schwidefsky (schwidefsky@de.ibm.com) + */ +#include <linux/config.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/errno.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/device.h> +#include <linux/workqueue.h> + +#include <asm/ccwdev.h> +#include <asm/cio.h> + +#include "cio.h" +#include "css.h" +#include "device.h" +#include "ioasm.h" + +/******************* bus type handling ***********************/ + +/* The Linux driver model distinguishes between a bus type and + * the bus itself. Of course we only have one channel + * subsystem driver and one channel system per machine, but + * we still use the abstraction. T.R. says it's a good idea. */ +static int +ccw_bus_match (struct device * dev, struct device_driver * drv) +{ + struct ccw_device *cdev = to_ccwdev(dev); + struct ccw_driver *cdrv = to_ccwdrv(drv); + const struct ccw_device_id *ids = cdrv->ids, *found; + + if (!ids) + return 0; + + found = ccw_device_id_match(ids, &cdev->id); + if (!found) + return 0; + + cdev->id.driver_info = found->driver_info; + + return 1; +} + +/* + * Hotplugging interface for ccw devices. + * Heavily modeled on pci and usb hotplug. + */ +static int +ccw_hotplug (struct device *dev, char **envp, int num_envp, + char *buffer, int buffer_size) +{ + struct ccw_device *cdev = to_ccwdev(dev); + int i = 0; + int length = 0; + + if (!cdev) + return -ENODEV; + + /* what we want to pass to /sbin/hotplug */ + + envp[i++] = buffer; + length += scnprintf(buffer, buffer_size - length, "CU_TYPE=%04X", + cdev->id.cu_type); + if ((buffer_size - length <= 0) || (i >= num_envp)) + return -ENOMEM; + ++length; + buffer += length; + + envp[i++] = buffer; + length += scnprintf(buffer, buffer_size - length, "CU_MODEL=%02X", + cdev->id.cu_model); + if ((buffer_size - length <= 0) || (i >= num_envp)) + return -ENOMEM; + ++length; + buffer += length; + + /* The next two can be zero, that's ok for us */ + envp[i++] = buffer; + length += scnprintf(buffer, buffer_size - length, "DEV_TYPE=%04X", + cdev->id.dev_type); + if ((buffer_size - length <= 0) || (i >= num_envp)) + return -ENOMEM; + ++length; + buffer += length; + + envp[i++] = buffer; + length += scnprintf(buffer, buffer_size - length, "DEV_MODEL=%02X", + cdev->id.dev_model); + if ((buffer_size - length <= 0) || (i >= num_envp)) + return -ENOMEM; + + envp[i] = 0; + + return 0; +} + +struct bus_type ccw_bus_type = { + .name = "ccw", + .match = &ccw_bus_match, + .hotplug = &ccw_hotplug, +}; + +static int io_subchannel_probe (struct device *); +static int io_subchannel_remove (struct device *); +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, + .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, +}; + +struct workqueue_struct *ccw_device_work; +struct workqueue_struct *ccw_device_notify_work; +static wait_queue_head_t ccw_device_init_wq; +static atomic_t ccw_device_init_count; + +static int __init +init_ccw_bus_type (void) +{ + int ret; + + init_waitqueue_head(&ccw_device_init_wq); + atomic_set(&ccw_device_init_count, 0); + + ccw_device_work = create_singlethread_workqueue("cio"); + if (!ccw_device_work) + return -ENOMEM; /* FIXME: better errno ? */ + ccw_device_notify_work = create_singlethread_workqueue("cio_notify"); + if (!ccw_device_notify_work) { + 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; + + if ((ret = driver_register(&io_subchannel_driver.drv))) + goto out_err; + + wait_event(ccw_device_init_wq, + atomic_read(&ccw_device_init_count) == 0); + flush_workqueue(ccw_device_work); + return 0; +out_err: + if (ccw_device_work) + 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; +} + +static void __exit +cleanup_ccw_bus_type (void) +{ + driver_unregister(&io_subchannel_driver.drv); + bus_unregister(&ccw_bus_type); + destroy_workqueue(ccw_device_notify_work); + destroy_workqueue(ccw_device_work); +} + +subsys_initcall(init_ccw_bus_type); +module_exit(cleanup_ccw_bus_type); + +/************************ device handling **************************/ + +/* + * A ccw_device has some interfaces in sysfs in addition to the + * standard ones. + * The following entries are designed to export the information which + * resided in 2.4 in /proc/subchannels. Subchannel and device number + * are obvious, so they don't have an entry :) + * TODO: Split chpids and pimpampom up? Where is "in use" in the tree? + */ +static ssize_t +chpids_show (struct device * dev, char * buf) +{ + struct subchannel *sch = to_subchannel(dev); + struct ssd_info *ssd = &sch->ssd_info; + ssize_t ret = 0; + int chp; + + for (chp = 0; chp < 8; chp++) + ret += sprintf (buf+ret, "%02x ", ssd->chpid[chp]); + + ret += sprintf (buf+ret, "\n"); + return min((ssize_t)PAGE_SIZE, ret); +} + +static ssize_t +pimpampom_show (struct device * dev, char * buf) +{ + struct subchannel *sch = to_subchannel(dev); + struct pmcw *pmcw = &sch->schib.pmcw; + + return sprintf (buf, "%02x %02x %02x\n", + pmcw->pim, pmcw->pam, pmcw->pom); +} + +static ssize_t +devtype_show (struct device *dev, char *buf) +{ + struct ccw_device *cdev = to_ccwdev(dev); + struct ccw_device_id *id = &(cdev->id); + + if (id->dev_type != 0) + return sprintf(buf, "%04x/%02x\n", + id->dev_type, id->dev_model); + else + return sprintf(buf, "n/a\n"); +} + +static ssize_t +cutype_show (struct device *dev, char *buf) +{ + struct ccw_device *cdev = to_ccwdev(dev); + struct ccw_device_id *id = &(cdev->id); + + return sprintf(buf, "%04x/%02x\n", + id->cu_type, id->cu_model); +} + +static ssize_t +online_show (struct device *dev, char *buf) +{ + struct ccw_device *cdev = to_ccwdev(dev); + + return sprintf(buf, cdev->online ? "1\n" : "0\n"); +} + +static void +ccw_device_remove_disconnected(struct ccw_device *cdev) +{ + struct subchannel *sch; + /* + * Forced offline in disconnected state means + * 'throw away device'. + */ + sch = to_subchannel(cdev->dev.parent); + device_unregister(&sch->dev); + /* Reset intparm to zeroes. */ + sch->schib.pmcw.intparm = 0; + cio_modify(sch); + put_device(&sch->dev); +} + +int +ccw_device_set_offline(struct ccw_device *cdev) +{ + int ret; + + if (!cdev) + return -ENODEV; + if (!cdev->online || !cdev->drv) + return -EINVAL; + + if (cdev->drv->set_offline) { + ret = cdev->drv->set_offline(cdev); + if (ret != 0) + return ret; + } + 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)); + else { + pr_debug("ccw_device_offline returned %d, device %s\n", + ret, cdev->dev.bus_id); + cdev->online = 1; + } + return ret; +} + +int +ccw_device_set_online(struct ccw_device *cdev) +{ + int ret; + + if (!cdev) + return -ENODEV; + if (cdev->online || !cdev->drv) + return -EINVAL; + + spin_lock_irq(cdev->ccwlock); + ret = ccw_device_online(cdev); + spin_unlock_irq(cdev->ccwlock); + if (ret == 0) + wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev)); + else { + pr_debug("ccw_device_online returned %d, device %s\n", + ret, cdev->dev.bus_id); + return ret; + } + if (cdev->private->state != DEV_STATE_ONLINE) + return -ENODEV; + if (!cdev->drv->set_online || cdev->drv->set_online(cdev) == 0) { + cdev->online = 1; + return 0; + } + spin_lock_irq(cdev->ccwlock); + ret = ccw_device_offline(cdev); + spin_unlock_irq(cdev->ccwlock); + if (ret == 0) + wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev)); + else + pr_debug("ccw_device_offline returned %d, device %s\n", + ret, cdev->dev.bus_id); + return (ret = 0) ? -ENODEV : ret; +} + +static ssize_t +online_store (struct device *dev, 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)) + return -EAGAIN; + + if (cdev->drv && !try_module_get(cdev->drv->owner)) { + atomic_set(&cdev->private->onoff, 0); + return -EINVAL; + } + if (!strncmp(buf, "force\n", count)) { + force = 1; + i = 1; + } else { + force = 0; + i = simple_strtoul(buf, &tmp, 16); + } + if (i == 1) { + /* Do device recognition, if needed. */ + if (cdev->id.cu_type == 0) { + ret = ccw_device_recognition(cdev); + if (ret) { + printk(KERN_WARNING"Couldn't start recognition " + "for device %s (ret=%d)\n", + cdev->dev.bus_id, ret); + goto out; + } + wait_event(cdev->private->wait_q, + cdev->private->flags.recog_done); + } + if (cdev->drv && cdev->drv->set_online) + ccw_device_set_online(cdev); + } else if (i == 0) { + if (cdev->private->state == DEV_STATE_DISCONNECTED) + ccw_device_remove_disconnected(cdev); + else if (cdev->drv && cdev->drv->set_offline) + ccw_device_set_offline(cdev); + } + if (force && cdev->private->state == DEV_STATE_BOXED) { + ret = ccw_device_stlck(cdev); + if (ret) { + printk(KERN_WARNING"ccw_device_stlck for device %s " + "returned %d!\n", cdev->dev.bus_id, ret); + goto out; + } + /* Do device recognition, if needed. */ + if (cdev->id.cu_type == 0) { + cdev->private->state = DEV_STATE_NOT_OPER; + ret = ccw_device_recognition(cdev); + if (ret) { + printk(KERN_WARNING"Couldn't start recognition " + "for device %s (ret=%d)\n", + cdev->dev.bus_id, ret); + goto out; + } + wait_event(cdev->private->wait_q, + cdev->private->flags.recog_done); + } + if (cdev->drv && cdev->drv->set_online) + ccw_device_set_online(cdev); + } + out: + if (cdev->drv) + module_put(cdev->drv->owner); + atomic_set(&cdev->private->onoff, 0); + return count; +} + +static ssize_t +available_show (struct device *dev, char *buf) +{ + struct ccw_device *cdev = to_ccwdev(dev); + struct subchannel *sch; + + switch (cdev->private->state) { + case DEV_STATE_BOXED: + return sprintf(buf, "boxed\n"); + case DEV_STATE_DISCONNECTED: + case DEV_STATE_DISCONNECTED_SENSE_ID: + case DEV_STATE_NOT_OPER: + sch = to_subchannel(dev->parent); + if (!sch->lpm) + return sprintf(buf, "no path\n"); + else + return sprintf(buf, "no device\n"); + default: + /* All other states considered fine. */ + return sprintf(buf, "good\n"); + } +} + +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(online, 0644, online_show, online_store); +extern struct device_attribute dev_attr_cmb_enable; +static DEVICE_ATTR(availability, 0444, available_show, NULL); + +static struct attribute * subch_attrs[] = { + &dev_attr_chpids.attr, + &dev_attr_pimpampom.attr, + NULL, +}; + +static struct attribute_group subch_attr_group = { + .attrs = subch_attrs, +}; + +static inline int +subchannel_add_files (struct device *dev) +{ + return sysfs_create_group(&dev->kobj, &subch_attr_group); +} + +static struct attribute * ccwdev_attrs[] = { + &dev_attr_devtype.attr, + &dev_attr_cutype.attr, + &dev_attr_online.attr, + &dev_attr_cmb_enable.attr, + &dev_attr_availability.attr, + NULL, +}; + +static struct attribute_group ccwdev_attr_group = { + .attrs = ccwdev_attrs, +}; + +static inline int +device_add_files (struct device *dev) +{ + return sysfs_create_group(&dev->kobj, &ccwdev_attr_group); +} + +static inline void +device_remove_files(struct device *dev) +{ + sysfs_remove_group(&dev->kobj, &ccwdev_attr_group); +} + +/* this is a simple abstraction for device_register that sets the + * correct bus type and adds the bus specific files */ +int +ccw_device_register(struct ccw_device *cdev) +{ + struct device *dev = &cdev->dev; + int ret; + + dev->bus = &ccw_bus_type; + + if ((ret = device_add(dev))) + return ret; + + 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; +} + +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; + + 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) && + (cdev != sibling)) { + 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; +} + +static void +ccw_device_add_changed(void *data) +{ + + struct ccw_device *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); + +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); + } + } + /* Update ssd info here. */ + css_get_ssd_info(sch); + cdev->private->devno = sch->schib.pmcw.dev; + } else + need_rename = 0; + device_remove_files(&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); + PREPARE_WORK(&cdev->private->kick_work, + ccw_device_add_changed, (void *)cdev); + queue_work(ccw_device_work, &cdev->private->kick_work); +} + +static void +ccw_device_release(struct device *dev) +{ + struct ccw_device *cdev; + + cdev = to_ccwdev(dev); + kfree(cdev->private); + kfree(cdev); +} + +/* + * Register recognized device. + */ +static void +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)) { + bus_rescan_devices(&ccw_bus_type); + goto out; + } + /* make it known to the system */ + ret = ccw_device_register(cdev); + if (ret) { + printk (KERN_WARNING "%s: could not register %s\n", + __func__, cdev->dev.bus_id); + put_device(&cdev->dev); + 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; + } + + ret = subchannel_add_files(cdev->dev.parent); + if (ret) + printk(KERN_WARNING "%s: could not add attributes to %s\n", + __func__, sch->dev.bus_id); + put_device(&cdev->dev); +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 +ccw_device_call_sch_unregister(void *data) +{ + struct ccw_device *cdev = data; + struct subchannel *sch; + + sch = to_subchannel(cdev->dev.parent); + device_unregister(&sch->dev); + /* Reset intparm to zeroes. */ + sch->schib.pmcw.intparm = 0; + cio_modify(sch); + put_device(&cdev->dev); + put_device(&sch->dev); +} + +/* + * subchannel recognition done. Called from the state machine. + */ +void +io_subchannel_recog_done(struct ccw_device *cdev) +{ + struct subchannel *sch; + + if (css_init_done == 0) { + cdev->private->flags.recog_done = 1; + return; + } + switch (cdev->private->state) { + case DEV_STATE_NOT_OPER: + cdev->private->flags.recog_done = 1; + /* Remove device found not operational. */ + if (!get_device(&cdev->dev)) + break; + sch = to_subchannel(cdev->dev.parent); + 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. */ + case DEV_STATE_OFFLINE: + /* + * We can't register the device in interrupt context so + * we schedule a work item. + */ + if (!get_device(&cdev->dev)) + break; + PREPARE_WORK(&cdev->private->kick_work, + io_subchannel_register, (void *) cdev); + queue_work(slow_path_wq, &cdev->private->kick_work); + break; + } +} + +static int +io_subchannel_recog(struct ccw_device *cdev, struct subchannel *sch) +{ + int rc; + struct ccw_device_private *priv; + + 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->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); + + /* Increase counter of devices currently in recognition. */ + atomic_inc(&ccw_device_init_count); + + /* Start async. device sensing. */ + spin_lock_irq(&sch->lock); + rc = ccw_device_recognition(cdev); + spin_unlock_irq(&sch->lock); + if (rc) { + if (atomic_dec_and_test(&ccw_device_init_count)) + wake_up(&ccw_device_init_wq); + } + return rc; +} + +static int +io_subchannel_probe (struct device *pdev) +{ + 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. + * Register it and exit. This happens for all early + * device, e.g. the console. + */ + cdev = sch->dev.driver_data; + device_initialize(&cdev->dev); + ccw_device_register(cdev); + subchannel_add_files(&sch->dev); + /* + * Check if the device is already online. If it is + * the reference count needs to be corrected + * (see ccw_device_online and css_init_done for the + * ugly details). + */ + if (cdev->private->state != DEV_STATE_NOT_OPER && + cdev->private->state != DEV_STATE_OFFLINE && + cdev->private->state != DEV_STATE_BOXED) + get_device(&cdev->dev); + return 0; + } + cdev = kmalloc (sizeof(*cdev), GFP_KERNEL); + if (!cdev) + return -ENOMEM; + memset(cdev, 0, sizeof(struct ccw_device)); + cdev->private = kmalloc(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, + .release = ccw_device_release, + }; + INIT_LIST_HEAD(&cdev->private->kick_work.entry); + /* Do first half of device_register. */ + device_initialize(&cdev->dev); + + if (!get_device(&sch->dev)) { + if (cdev->dev.release) + cdev->dev.release(&cdev->dev); + return -ENODEV; + } + + rc = io_subchannel_recog(cdev, to_subchannel(pdev)); + if (rc) { + 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); + } + + 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) +{ + struct ccw_device *cdev; + unsigned long flags; + + if (!dev->driver_data) + return 0; + cdev = dev->driver_data; + /* Set ccw device to not operational and drop reference. */ + spin_lock_irqsave(cdev->ccwlock, flags); + dev->driver_data = NULL; + cdev->private->state = DEV_STATE_NOT_OPER; + spin_unlock_irqrestore(cdev->ccwlock, flags); + /* + * Put unregistration on workqueue to avoid livelocks on the css bus + * semaphore. + */ + 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; +} + +static int +io_subchannel_notify(struct device *dev, int event) +{ + struct ccw_device *cdev; + + cdev = dev->driver_data; + if (!cdev) + return 0; + if (!cdev->drv) + return 0; + if (!cdev->online) + return 0; + return cdev->drv->notify ? cdev->drv->notify(cdev, event) : 0; +} + +static void +io_subchannel_verify(struct device *dev) +{ + struct ccw_device *cdev; + + cdev = dev->driver_data; + if (cdev) + dev_fsm_event(cdev, DEV_EVENT_VERIFY); +} + +static void +io_subchannel_ioterm(struct device *dev) +{ + struct ccw_device *cdev; + + cdev = dev->driver_data; + if (!cdev) + return; + cdev->private->state = DEV_STATE_CLEAR_VERIFY; + if (cdev->handler) + cdev->handler(cdev, cdev->private->intparm, + 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; +static int console_cdev_in_use; + +static int +ccw_device_console_enable (struct ccw_device *cdev, struct subchannel *sch) +{ + int rc; + + /* Initialize the ccw_device structure. */ + 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; + + /* Now wait for the async. recognition to come to an end. */ + spin_lock_irq(cdev->ccwlock); + while (!dev_fsm_final_state(cdev)) + wait_cons_dev(); + rc = -EIO; + if (cdev->private->state != DEV_STATE_OFFLINE) + goto out_unlock; + ccw_device_online(cdev); + while (!dev_fsm_final_state(cdev)) + wait_cons_dev(); + if (cdev->private->state != DEV_STATE_ONLINE) + goto out_unlock; + rc = 0; +out_unlock: + spin_unlock_irq(cdev->ccwlock); + return 0; +} + +struct ccw_device * +ccw_device_probe_console(void) +{ + struct subchannel *sch; + int ret; + + if (xchg(&console_cdev_in_use, 1) != 0) + return NULL; + sch = cio_probe_console(); + if (IS_ERR(sch)) { + console_cdev_in_use = 0; + return (void *) sch; + } + memset(&console_cdev, 0, sizeof(struct ccw_device)); + memset(&console_private, 0, sizeof(struct ccw_device_private)); + console_cdev.private = &console_private; + ret = ccw_device_console_enable(&console_cdev, sch); + if (ret) { + cio_release_console(); + console_cdev_in_use = 0; + return ERR_PTR(ret); + } + console_cdev.online = 1; + return &console_cdev; +} +#endif + +/* + * get ccw_device matching the busid, but only if owned by cdrv + */ +struct ccw_device * +get_ccwdev_by_busid(struct ccw_driver *cdrv, const char *bus_id) +{ + struct device *d, *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); + + 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); + put_driver(drv); + + return dev ? to_ccwdev(dev) : 0; +} + +/************************** device driver handling ************************/ + +/* This is the implementation of the ccw_driver class. The probe, remove + * and release methods are initially very similar to the device_driver + * implementations, with the difference that they have ccw_device + * arguments. + * + * A ccw driver also contains the information that is needed for + * device matching. + */ +static int +ccw_device_probe (struct device *dev) +{ + struct ccw_device *cdev = to_ccwdev(dev); + struct ccw_driver *cdrv = to_ccwdrv(dev->driver); + int ret; + + cdev->drv = cdrv; /* to let the driver call _set_online */ + + ret = cdrv->probe ? cdrv->probe(cdev) : -ENODEV; + + if (ret) { + cdev->drv = 0; + return ret; + } + + return 0; +} + +static int +ccw_device_remove (struct device *dev) +{ + struct ccw_device *cdev = to_ccwdev(dev); + struct ccw_driver *cdrv = cdev->drv; + int ret; + + pr_debug("removing device %s\n", cdev->dev.bus_id); + if (cdrv->remove) + cdrv->remove(cdev); + if (cdev->online) { + cdev->online = 0; + spin_lock_irq(cdev->ccwlock); + ret = ccw_device_offline(cdev); + spin_unlock_irq(cdev->ccwlock); + if (ret == 0) + wait_event(cdev->private->wait_q, + dev_fsm_final_state(cdev)); + else + //FIXME: we can't fail! + pr_debug("ccw_device_offline returned %d, device %s\n", + ret, cdev->dev.bus_id); + } + ccw_device_set_timeout(cdev, 0); + cdev->drv = 0; + return 0; +} + +int +ccw_driver_register (struct ccw_driver *cdriver) +{ + struct device_driver *drv = &cdriver->driver; + + drv->bus = &ccw_bus_type; + drv->name = cdriver->name; + drv->probe = ccw_device_probe; + drv->remove = ccw_device_remove; + + return driver_register(drv); +} + +void +ccw_driver_unregister (struct ccw_driver *cdriver) +{ + driver_unregister(&cdriver->driver); +} + +MODULE_LICENSE("GPL"); +EXPORT_SYMBOL(ccw_device_set_online); +EXPORT_SYMBOL(ccw_device_set_offline); +EXPORT_SYMBOL(ccw_driver_register); +EXPORT_SYMBOL(ccw_driver_unregister); +EXPORT_SYMBOL(get_ccwdev_by_busid); +EXPORT_SYMBOL(ccw_bus_type); +EXPORT_SYMBOL(ccw_device_work); +EXPORT_SYMBOL(ccw_device_notify_work); diff --git a/drivers/s390/cio/device.h b/drivers/s390/cio/device.h new file mode 100644 index 000000000000..a3aa056d7245 --- /dev/null +++ b/drivers/s390/cio/device.h @@ -0,0 +1,115 @@ +#ifndef S390_DEVICE_H +#define S390_DEVICE_H + +/* + * states of the device statemachine + */ +enum dev_state { + DEV_STATE_NOT_OPER, + DEV_STATE_SENSE_PGID, + DEV_STATE_SENSE_ID, + DEV_STATE_OFFLINE, + DEV_STATE_VERIFY, + DEV_STATE_ONLINE, + DEV_STATE_W4SENSE, + DEV_STATE_DISBAND_PGID, + DEV_STATE_BOXED, + /* states to wait for i/o completion before doing something */ + 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, + DEV_STATE_CMFCHANGE, + /* last element! */ + NR_DEV_STATES +}; + +/* + * asynchronous events of the device statemachine + */ +enum dev_event { + DEV_EVENT_NOTOPER, + DEV_EVENT_INTERRUPT, + DEV_EVENT_TIMEOUT, + DEV_EVENT_VERIFY, + /* last element! */ + NR_DEV_EVENTS +}; + +struct ccw_device; + +/* + * action called through jumptable + */ +typedef void (fsm_func_t)(struct ccw_device *, enum dev_event); +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); +} + +/* + * Delivers 1 if the device state is final. + */ +static inline int +dev_fsm_final_state(struct ccw_device *cdev) +{ + return (cdev->private->state == DEV_STATE_NOT_OPER || + cdev->private->state == DEV_STATE_OFFLINE || + cdev->private->state == DEV_STATE_ONLINE || + cdev->private->state == DEV_STATE_BOXED); +} + +extern struct workqueue_struct *ccw_device_work; +extern struct workqueue_struct *ccw_device_notify_work; + +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 *); +void ccw_device_call_sch_unregister(void *); + +int ccw_device_recognition(struct ccw_device *); +int ccw_device_online(struct ccw_device *); +int ccw_device_offline(struct ccw_device *); + +/* Function prototypes for device status and basic sense stuff. */ +void ccw_device_accumulate_irb(struct ccw_device *, struct irb *); +void ccw_device_accumulate_basic_sense(struct ccw_device *, struct irb *); +int ccw_device_accumulate_and_sense(struct ccw_device *, struct irb *); +int ccw_device_do_sense(struct ccw_device *, struct irb *); + +/* Function prototypes for sense id stuff. */ +void ccw_device_sense_id_start(struct ccw_device *); +void ccw_device_sense_id_irq(struct ccw_device *, enum dev_event); +void ccw_device_sense_id_done(struct ccw_device *, int); + +/* Function prototypes for path grouping stuff. */ +void ccw_device_sense_pgid_start(struct ccw_device *); +void ccw_device_sense_pgid_irq(struct ccw_device *, enum dev_event); +void ccw_device_sense_pgid_done(struct ccw_device *, int); + +void ccw_device_verify_start(struct ccw_device *); +void ccw_device_verify_irq(struct ccw_device *, enum dev_event); +void ccw_device_verify_done(struct ccw_device *, int); + +void ccw_device_disband_start(struct ccw_device *); +void ccw_device_disband_irq(struct ccw_device *, enum dev_event); +void ccw_device_disband_done(struct ccw_device *, int); + +int ccw_device_call_handler(struct ccw_device *); + +int ccw_device_stlck(struct ccw_device *); + +/* qdio needs this. */ +void ccw_device_set_timeout(struct ccw_device *, int); + +void retry_set_schib(struct ccw_device *cdev); +#endif diff --git a/drivers/s390/cio/device_fsm.c b/drivers/s390/cio/device_fsm.c new file mode 100644 index 000000000000..9b7f6f548b1d --- /dev/null +++ b/drivers/s390/cio/device_fsm.c @@ -0,0 +1,1250 @@ +/* + * drivers/s390/cio/device_fsm.c + * finite state machine for device handling + * + * Copyright (C) 2002 IBM Deutschland Entwicklung GmbH, + * IBM Corporation + * Author(s): Cornelia Huck(cohuck@de.ibm.com) + * Martin Schwidefsky (schwidefsky@de.ibm.com) + */ + +#include <linux/module.h> +#include <linux/config.h> +#include <linux/init.h> + +#include <asm/ccwdev.h> +#include <asm/qdio.h> + +#include "cio.h" +#include "cio_debug.h" +#include "css.h" +#include "device.h" +#include "chsc.h" +#include "ioasm.h" +#include "qdio.h" + +int +device_is_online(struct subchannel *sch) +{ + struct ccw_device *cdev; + + if (!sch->dev.driver_data) + return 0; + cdev = sch->dev.driver_data; + return (cdev->private->state == DEV_STATE_ONLINE); +} + +int +device_is_disconnected(struct subchannel *sch) +{ + struct ccw_device *cdev; + + if (!sch->dev.driver_data) + return 0; + cdev = sch->dev.driver_data; + return (cdev->private->state == DEV_STATE_DISCONNECTED || + cdev->private->state == DEV_STATE_DISCONNECTED_SENSE_ID); +} + +void +device_set_disconnected(struct subchannel *sch) +{ + struct ccw_device *cdev; + + if (!sch->dev.driver_data) + return; + cdev = sch->dev.driver_data; + ccw_device_set_timeout(cdev, 0); + cdev->private->flags.fake_irb = 0; + cdev->private->state = DEV_STATE_DISCONNECTED; +} + +void +device_set_waiting(struct subchannel *sch) +{ + struct ccw_device *cdev; + + if (!sch->dev.driver_data) + return; + cdev = sch->dev.driver_data; + ccw_device_set_timeout(cdev, 10*HZ); + cdev->private->state = DEV_STATE_WAIT4IO; +} + +/* + * Timeout function. It just triggers a DEV_EVENT_TIMEOUT. + */ +static void +ccw_device_timeout(unsigned long data) +{ + struct ccw_device *cdev; + + cdev = (struct ccw_device *) data; + spin_lock_irq(cdev->ccwlock); + dev_fsm_event(cdev, DEV_EVENT_TIMEOUT); + spin_unlock_irq(cdev->ccwlock); +} + +/* + * Set timeout + */ +void +ccw_device_set_timeout(struct ccw_device *cdev, int expires) +{ + if (expires == 0) { + del_timer(&cdev->private->timer); + return; + } + if (timer_pending(&cdev->private->timer)) { + if (mod_timer(&cdev->private->timer, jiffies + expires)) + return; + } + cdev->private->timer.function = ccw_device_timeout; + cdev->private->timer.data = (unsigned long) cdev; + cdev->private->timer.expires = jiffies + expires; + add_timer(&cdev->private->timer); +} + +/* Kill any pending timers after machine check. */ +void +device_kill_pending_timer(struct subchannel *sch) +{ + struct ccw_device *cdev; + + if (!sch->dev.driver_data) + return; + cdev = sch->dev.driver_data; + ccw_device_set_timeout(cdev, 0); +} + +/* + * Cancel running i/o. This is called repeatedly since halt/clear are + * asynchronous operations. We do one try with cio_cancel, two tries + * with cio_halt, 255 tries with cio_clear. If everythings fails panic. + * Returns 0 if device now idle, -ENODEV for device not operational and + * -EBUSY if an interrupt is expected (either from halt/clear or from a + * status pending). + */ +int +ccw_device_cancel_halt_clear(struct ccw_device *cdev) +{ + struct subchannel *sch; + int ret; + + sch = to_subchannel(cdev->dev.parent); + ret = stsch(sch->irq, &sch->schib); + if (ret || !sch->schib.pmcw.dnv) + return -ENODEV; + if (!sch->schib.pmcw.ena || sch->schib.scsw.actl == 0) + /* Not operational or no activity -> done. */ + return 0; + /* Stage 1: cancel io. */ + if (!(sch->schib.scsw.actl & SCSW_ACTL_HALT_PEND) && + !(sch->schib.scsw.actl & SCSW_ACTL_CLEAR_PEND)) { + ret = cio_cancel(sch); + if (ret != -EINVAL) + return ret; + /* cancel io unsuccessful. From now on it is asynchronous. */ + cdev->private->iretry = 3; /* 3 halt retries. */ + } + if (!(sch->schib.scsw.actl & SCSW_ACTL_CLEAR_PEND)) { + /* Stage 2: halt io. */ + if (cdev->private->iretry) { + cdev->private->iretry--; + ret = cio_halt(sch); + return (ret == 0) ? -EBUSY : ret; + } + /* halt io unsuccessful. */ + cdev->private->iretry = 255; /* 255 clear retries. */ + } + /* Stage 3: clear io. */ + if (cdev->private->iretry) { + cdev->private->iretry--; + ret = cio_clear (sch); + return (ret == 0) ? -EBUSY : ret; + } + panic("Can't stop i/o on subchannel.\n"); +} + +static int +ccw_device_handle_oper(struct ccw_device *cdev) +{ + struct subchannel *sch; + + sch = to_subchannel(cdev->dev.parent); + cdev->private->flags.recog_done = 1; + /* + * Check if cu type and device type still match. If + * not, it is certainly another device and we have to + * de- and re-register. Also check here for non-matching devno. + */ + if (cdev->id.cu_type != cdev->private->senseid.cu_type || + cdev->id.cu_model != cdev->private->senseid.cu_model || + cdev->id.dev_type != cdev->private->senseid.dev_type || + cdev->id.dev_model != cdev->private->senseid.dev_model || + cdev->private->devno != sch->schib.pmcw.dev) { + PREPARE_WORK(&cdev->private->kick_work, + ccw_device_do_unreg_rereg, (void *)cdev); + queue_work(ccw_device_work, &cdev->private->kick_work); + return 0; + } + cdev->private->flags.donotify = 1; + return 1; +} + +/* + * The machine won't give us any notification by machine check if a chpid has + * been varied online on the SE so we have to find out by magic (i. e. driving + * the channel subsystem to device selection and updating our path masks). + */ +static inline void +__recover_lost_chpids(struct subchannel *sch, int old_lpm) +{ + int mask, i; + + for (i = 0; i<8; i++) { + mask = 0x80 >> i; + if (!(sch->lpm & mask)) + continue; + if (old_lpm & mask) + continue; + chpid_is_actually_online(sch->schib.pmcw.chpid[i]); + } +} + +/* + * Stop device recognition. + */ +static void +ccw_device_recog_done(struct ccw_device *cdev, int state) +{ + struct subchannel *sch; + int notify, old_lpm, same_dev; + + sch = to_subchannel(cdev->dev.parent); + + ccw_device_set_timeout(cdev, 0); + cio_disable_subchannel(sch); + /* + * Now that we tried recognition, we have performed device selection + * through ssch() and the path information is up to date. + */ + old_lpm = sch->lpm; + stsch(sch->irq, &sch->schib); + sch->lpm = sch->schib.pmcw.pim & + sch->schib.pmcw.pam & + sch->schib.pmcw.pom & + sch->opm; + if (cdev->private->state == DEV_STATE_DISCONNECTED_SENSE_ID) + /* Force reprobe on all chpids. */ + old_lpm = 0; + if (sch->lpm != old_lpm) + __recover_lost_chpids(sch, old_lpm); + if (cdev->private->state == DEV_STATE_DISCONNECTED_SENSE_ID) { + if (state == DEV_STATE_NOT_OPER) { + cdev->private->flags.recog_done = 1; + cdev->private->state = DEV_STATE_DISCONNECTED; + return; + } + /* Boxed devices don't need extra treatment. */ + } + notify = 0; + same_dev = 0; /* Keep the compiler quiet... */ + switch (state) { + case DEV_STATE_NOT_OPER: + CIO_DEBUG(KERN_WARNING, 2, + "SenseID : unknown device %04x on subchannel %04x\n", + cdev->private->devno, sch->irq); + break; + case DEV_STATE_OFFLINE: + if (cdev->private->state == DEV_STATE_DISCONNECTED_SENSE_ID) { + same_dev = ccw_device_handle_oper(cdev); + notify = 1; + } + /* fill out sense information */ + cdev->id = (struct ccw_device_id) { + .cu_type = cdev->private->senseid.cu_type, + .cu_model = cdev->private->senseid.cu_model, + .dev_type = cdev->private->senseid.dev_type, + .dev_model = cdev->private->senseid.dev_model, + }; + if (notify) { + cdev->private->state = DEV_STATE_OFFLINE; + if (same_dev) { + /* Get device online again. */ + ccw_device_online(cdev); + wake_up(&cdev->private->wait_q); + } + return; + } + /* Issue device info message. */ + CIO_DEBUG(KERN_INFO, 2, "SenseID : device %04x reports: " + "CU Type/Mod = %04X/%02X, Dev Type/Mod = " + "%04X/%02X\n", cdev->private->devno, + cdev->id.cu_type, cdev->id.cu_model, + cdev->id.dev_type, cdev->id.dev_model); + break; + case DEV_STATE_BOXED: + CIO_DEBUG(KERN_WARNING, 2, + "SenseID : boxed device %04x on subchannel %04x\n", + cdev->private->devno, sch->irq); + break; + } + cdev->private->state = state; + io_subchannel_recog_done(cdev); + if (state != DEV_STATE_NOT_OPER) + wake_up(&cdev->private->wait_q); +} + +/* + * Function called from device_id.c after sense id has completed. + */ +void +ccw_device_sense_id_done(struct ccw_device *cdev, int err) +{ + switch (err) { + case 0: + ccw_device_recog_done(cdev, DEV_STATE_OFFLINE); + break; + case -ETIME: /* Sense id stopped by timeout. */ + ccw_device_recog_done(cdev, DEV_STATE_BOXED); + break; + default: + ccw_device_recog_done(cdev, DEV_STATE_NOT_OPER); + break; + } +} + +static void +ccw_device_oper_notify(void *data) +{ + struct ccw_device *cdev; + struct subchannel *sch; + int ret; + + cdev = (struct ccw_device *)data; + sch = to_subchannel(cdev->dev.parent); + ret = (sch->driver && sch->driver->notify) ? + sch->driver->notify(&sch->dev, CIO_OPER) : 0; + if (!ret) + /* Driver doesn't want device back. */ + ccw_device_do_unreg_rereg((void *)cdev); + else + wake_up(&cdev->private->wait_q); +} + +/* + * Finished with online/offline processing. + */ +static void +ccw_device_done(struct ccw_device *cdev, int state) +{ + struct subchannel *sch; + + sch = to_subchannel(cdev->dev.parent); + + if (state != DEV_STATE_ONLINE) + cio_disable_subchannel(sch); + + /* Reset device status. */ + memset(&cdev->private->irb, 0, sizeof(struct irb)); + + cdev->private->state = state; + + + if (state == DEV_STATE_BOXED) + CIO_DEBUG(KERN_WARNING, 2, + "Boxed device %04x on subchannel %04x\n", + cdev->private->devno, sch->irq); + + if (cdev->private->flags.donotify) { + cdev->private->flags.donotify = 0; + PREPARE_WORK(&cdev->private->kick_work, ccw_device_oper_notify, + (void *)cdev); + queue_work(ccw_device_notify_work, &cdev->private->kick_work); + } + wake_up(&cdev->private->wait_q); + + if (css_init_done && state != DEV_STATE_ONLINE) + put_device (&cdev->dev); +} + +/* + * Function called from device_pgid.c after sense path ground has completed. + */ +void +ccw_device_sense_pgid_done(struct ccw_device *cdev, int err) +{ + struct subchannel *sch; + + sch = to_subchannel(cdev->dev.parent); + switch (err) { + case 0: + /* Start Path Group verification. */ + sch->vpm = 0; /* Start with no path groups set. */ + cdev->private->state = DEV_STATE_VERIFY; + ccw_device_verify_start(cdev); + break; + case -ETIME: /* Sense path group id stopped by timeout. */ + case -EUSERS: /* device is reserved for someone else. */ + ccw_device_done(cdev, DEV_STATE_BOXED); + break; + case -EOPNOTSUPP: /* path grouping not supported, just set online. */ + cdev->private->options.pgroup = 0; + ccw_device_done(cdev, DEV_STATE_ONLINE); + break; + default: + ccw_device_done(cdev, DEV_STATE_NOT_OPER); + break; + } +} + +/* + * Start device recognition. + */ +int +ccw_device_recognition(struct ccw_device *cdev) +{ + struct subchannel *sch; + int ret; + + if ((cdev->private->state != DEV_STATE_NOT_OPER) && + (cdev->private->state != DEV_STATE_BOXED)) + return -EINVAL; + sch = to_subchannel(cdev->dev.parent); + ret = cio_enable_subchannel(sch, sch->schib.pmcw.isc); + if (ret != 0) + /* Couldn't enable the subchannel for i/o. Sick device. */ + return ret; + + /* After 60s the device recognition is considered to have failed. */ + ccw_device_set_timeout(cdev, 60*HZ); + + /* + * We used to start here with a sense pgid to find out whether a device + * is locked by someone else. Unfortunately, the sense pgid command + * code has other meanings on devices predating the path grouping + * algorithm, so we start with sense id and box the device after an + * timeout (or if sense pgid during path verification detects the device + * is locked, as may happen on newer devices). + */ + cdev->private->flags.recog_done = 0; + cdev->private->state = DEV_STATE_SENSE_ID; + ccw_device_sense_id_start(cdev); + return 0; +} + +/* + * Handle timeout in device recognition. + */ +static void +ccw_device_recog_timeout(struct ccw_device *cdev, enum dev_event dev_event) +{ + int ret; + + ret = ccw_device_cancel_halt_clear(cdev); + switch (ret) { + case 0: + ccw_device_recog_done(cdev, DEV_STATE_BOXED); + break; + case -ENODEV: + ccw_device_recog_done(cdev, DEV_STATE_NOT_OPER); + break; + default: + ccw_device_set_timeout(cdev, 3*HZ); + } +} + + +static void +ccw_device_nopath_notify(void *data) +{ + struct ccw_device *cdev; + struct subchannel *sch; + int ret; + + cdev = (struct ccw_device *)data; + sch = to_subchannel(cdev->dev.parent); + /* Extra sanity. */ + if (sch->lpm) + return; + ret = (sch->driver && sch->driver->notify) ? + sch->driver->notify(&sch->dev, CIO_NO_PATH) : 0; + if (!ret) { + if (get_device(&sch->dev)) { + /* Driver doesn't want to keep device. */ + cio_disable_subchannel(sch); + if (get_device(&cdev->dev)) { + PREPARE_WORK(&cdev->private->kick_work, + ccw_device_call_sch_unregister, + (void *)cdev); + queue_work(ccw_device_work, + &cdev->private->kick_work); + } else + put_device(&sch->dev); + } + } else { + cio_disable_subchannel(sch); + ccw_device_set_timeout(cdev, 0); + cdev->private->flags.fake_irb = 0; + cdev->private->state = DEV_STATE_DISCONNECTED; + wake_up(&cdev->private->wait_q); + } +} + +void +ccw_device_verify_done(struct ccw_device *cdev, int err) +{ + cdev->private->flags.doverify = 0; + switch (err) { + case -EOPNOTSUPP: /* path grouping not supported, just set online. */ + cdev->private->options.pgroup = 0; + case 0: + ccw_device_done(cdev, DEV_STATE_ONLINE); + /* Deliver fake irb to device driver, if needed. */ + if (cdev->private->flags.fake_irb) { + memset(&cdev->private->irb, 0, sizeof(struct irb)); + cdev->private->irb.scsw = (struct scsw) { + .cc = 1, + .fctl = SCSW_FCTL_START_FUNC, + .actl = SCSW_ACTL_START_PEND, + .stctl = SCSW_STCTL_STATUS_PEND, + }; + cdev->private->flags.fake_irb = 0; + if (cdev->handler) + cdev->handler(cdev, cdev->private->intparm, + &cdev->private->irb); + memset(&cdev->private->irb, 0, sizeof(struct irb)); + } + break; + case -ETIME: + ccw_device_done(cdev, DEV_STATE_BOXED); + break; + default: + PREPARE_WORK(&cdev->private->kick_work, + ccw_device_nopath_notify, (void *)cdev); + queue_work(ccw_device_notify_work, &cdev->private->kick_work); + ccw_device_done(cdev, DEV_STATE_NOT_OPER); + break; + } +} + +/* + * Get device online. + */ +int +ccw_device_online(struct ccw_device *cdev) +{ + struct subchannel *sch; + int ret; + + if ((cdev->private->state != DEV_STATE_OFFLINE) && + (cdev->private->state != DEV_STATE_BOXED)) + return -EINVAL; + sch = to_subchannel(cdev->dev.parent); + if (css_init_done && !get_device(&cdev->dev)) + return -ENODEV; + ret = cio_enable_subchannel(sch, sch->schib.pmcw.isc); + if (ret != 0) { + /* Couldn't enable the subchannel for i/o. Sick device. */ + if (ret == -ENODEV) + dev_fsm_event(cdev, DEV_EVENT_NOTOPER); + return ret; + } + /* Do we want to do path grouping? */ + if (!cdev->private->options.pgroup) { + /* No, set state online immediately. */ + ccw_device_done(cdev, DEV_STATE_ONLINE); + return 0; + } + /* Do a SensePGID first. */ + cdev->private->state = DEV_STATE_SENSE_PGID; + ccw_device_sense_pgid_start(cdev); + return 0; +} + +void +ccw_device_disband_done(struct ccw_device *cdev, int err) +{ + switch (err) { + case 0: + ccw_device_done(cdev, DEV_STATE_OFFLINE); + break; + case -ETIME: + ccw_device_done(cdev, DEV_STATE_BOXED); + break; + default: + ccw_device_done(cdev, DEV_STATE_NOT_OPER); + break; + } +} + +/* + * Shutdown device. + */ +int +ccw_device_offline(struct ccw_device *cdev) +{ + struct subchannel *sch; + + sch = to_subchannel(cdev->dev.parent); + if (stsch(sch->irq, &sch->schib) || !sch->schib.pmcw.dnv) + return -ENODEV; + if (cdev->private->state != DEV_STATE_ONLINE) { + if (sch->schib.scsw.actl != 0) + return -EBUSY; + return -EINVAL; + } + if (sch->schib.scsw.actl != 0) + return -EBUSY; + /* Are we doing path grouping? */ + if (!cdev->private->options.pgroup) { + /* No, set state offline immediately. */ + ccw_device_done(cdev, DEV_STATE_OFFLINE); + return 0; + } + /* Start Set Path Group commands. */ + cdev->private->state = DEV_STATE_DISBAND_PGID; + ccw_device_disband_start(cdev); + return 0; +} + +/* + * Handle timeout in device online/offline process. + */ +static void +ccw_device_onoff_timeout(struct ccw_device *cdev, enum dev_event dev_event) +{ + int ret; + + ret = ccw_device_cancel_halt_clear(cdev); + switch (ret) { + case 0: + ccw_device_done(cdev, DEV_STATE_BOXED); + break; + case -ENODEV: + ccw_device_done(cdev, DEV_STATE_NOT_OPER); + break; + default: + ccw_device_set_timeout(cdev, 3*HZ); + } +} + +/* + * Handle not oper event in device recognition. + */ +static void +ccw_device_recog_notoper(struct ccw_device *cdev, enum dev_event dev_event) +{ + ccw_device_recog_done(cdev, DEV_STATE_NOT_OPER); +} + +/* + * Handle not operational event while offline. + */ +static void +ccw_device_offline_notoper(struct ccw_device *cdev, enum dev_event dev_event) +{ + struct subchannel *sch; + + cdev->private->state = DEV_STATE_NOT_OPER; + sch = to_subchannel(cdev->dev.parent); + if (get_device(&cdev->dev)) { + PREPARE_WORK(&cdev->private->kick_work, + ccw_device_call_sch_unregister, (void *)cdev); + queue_work(ccw_device_work, &cdev->private->kick_work); + } + wake_up(&cdev->private->wait_q); +} + +/* + * Handle not operational event while online. + */ +static void +ccw_device_online_notoper(struct ccw_device *cdev, enum dev_event dev_event) +{ + struct subchannel *sch; + + sch = to_subchannel(cdev->dev.parent); + if (sch->driver->notify && + sch->driver->notify(&sch->dev, sch->lpm ? CIO_GONE : CIO_NO_PATH)) { + ccw_device_set_timeout(cdev, 0); + cdev->private->flags.fake_irb = 0; + cdev->private->state = DEV_STATE_DISCONNECTED; + wake_up(&cdev->private->wait_q); + return; + } + cdev->private->state = DEV_STATE_NOT_OPER; + cio_disable_subchannel(sch); + if (sch->schib.scsw.actl != 0) { + // FIXME: not-oper indication to device driver ? + ccw_device_call_handler(cdev); + } + if (get_device(&cdev->dev)) { + PREPARE_WORK(&cdev->private->kick_work, + ccw_device_call_sch_unregister, (void *)cdev); + queue_work(ccw_device_work, &cdev->private->kick_work); + } + wake_up(&cdev->private->wait_q); +} + +/* + * Handle path verification event. + */ +static void +ccw_device_online_verify(struct ccw_device *cdev, enum dev_event dev_event) +{ + struct subchannel *sch; + + if (!cdev->private->options.pgroup) + return; + if (cdev->private->state == DEV_STATE_W4SENSE) { + cdev->private->flags.doverify = 1; + return; + } + sch = to_subchannel(cdev->dev.parent); + /* + * Since we might not just be coming from an interrupt from the + * subchannel we have to update the schib. + */ + stsch(sch->irq, &sch->schib); + + if (sch->schib.scsw.actl != 0 || + (cdev->private->irb.scsw.stctl & SCSW_STCTL_STATUS_PEND)) { + /* + * No final status yet or final status not yet delivered + * to the device driver. Can't do path verfication now, + * delay until final status was delivered. + */ + cdev->private->flags.doverify = 1; + return; + } + /* Device is idle, we can do the path verification. */ + cdev->private->state = DEV_STATE_VERIFY; + ccw_device_verify_start(cdev); +} + +/* + * Got an interrupt for a normal io (state online). + */ +static void +ccw_device_irq(struct ccw_device *cdev, enum dev_event dev_event) +{ + struct irb *irb; + + irb = (struct irb *) __LC_IRB; + /* Check for unsolicited interrupt. */ + if ((irb->scsw.stctl == + (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) + && (!irb->scsw.cc)) { + if ((irb->scsw.dstat & DEV_STAT_UNIT_CHECK) && + !irb->esw.esw0.erw.cons) { + /* Unit check but no sense data. Need basic sense. */ + if (ccw_device_do_sense(cdev, irb) != 0) + goto call_handler_unsol; + memcpy(irb, &cdev->private->irb, sizeof(struct irb)); + cdev->private->state = DEV_STATE_W4SENSE; + cdev->private->intparm = 0; + return; + } +call_handler_unsol: + if (cdev->handler) + cdev->handler (cdev, 0, irb); + return; + } + /* Accumulate status and find out if a basic sense is needed. */ + ccw_device_accumulate_irb(cdev, irb); + if (cdev->private->flags.dosense) { + if (ccw_device_do_sense(cdev, irb) == 0) { + cdev->private->state = DEV_STATE_W4SENSE; + } + return; + } + /* Call the handler. */ + if (ccw_device_call_handler(cdev) && cdev->private->flags.doverify) + /* Start delayed path verification. */ + ccw_device_online_verify(cdev, 0); +} + +/* + * Got an timeout in online state. + */ +static void +ccw_device_online_timeout(struct ccw_device *cdev, enum dev_event dev_event) +{ + int ret; + + ccw_device_set_timeout(cdev, 0); + ret = ccw_device_cancel_halt_clear(cdev); + if (ret == -EBUSY) { + ccw_device_set_timeout(cdev, 3*HZ); + cdev->private->state = DEV_STATE_TIMEOUT_KILL; + return; + } + 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_notify_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)); +} + +/* + * Got an interrupt for a basic sense. + */ +void +ccw_device_w4sense(struct ccw_device *cdev, enum dev_event dev_event) +{ + struct irb *irb; + + irb = (struct irb *) __LC_IRB; + /* Check for unsolicited interrupt. */ + if (irb->scsw.stctl == + (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) { + if (irb->scsw.cc == 1) + /* Basic sense hasn't started. Try again. */ + ccw_device_do_sense(cdev, irb); + else { + printk("Huh? %s(%s): unsolicited interrupt...\n", + __FUNCTION__, cdev->dev.bus_id); + if (cdev->handler) + cdev->handler (cdev, 0, irb); + } + return; + } + /* Add basic sense info to irb. */ + ccw_device_accumulate_basic_sense(cdev, irb); + if (cdev->private->flags.dosense) { + /* Another basic sense is needed. */ + ccw_device_do_sense(cdev, irb); + return; + } + cdev->private->state = DEV_STATE_ONLINE; + /* Call the handler. */ + if (ccw_device_call_handler(cdev) && cdev->private->flags.doverify) + /* Start delayed path verification. */ + ccw_device_online_verify(cdev, 0); +} + +static void +ccw_device_clear_verify(struct ccw_device *cdev, enum dev_event dev_event) +{ + struct irb *irb; + + irb = (struct irb *) __LC_IRB; + /* Accumulate status. We don't do basic sense. */ + ccw_device_accumulate_irb(cdev, irb); + /* Try to start delayed device verification. */ + ccw_device_online_verify(cdev, 0); + /* Note: Don't call handler for cio initiated clear! */ +} + +static void +ccw_device_killing_irq(struct ccw_device *cdev, enum dev_event dev_event) +{ + struct subchannel *sch; + + sch = to_subchannel(cdev->dev.parent); + ccw_device_set_timeout(cdev, 0); + /* OK, i/o is dead now. Call interrupt handler. */ + cdev->private->state = DEV_STATE_ONLINE; + if (cdev->handler) + cdev->handler(cdev, cdev->private->intparm, + ERR_PTR(-ETIMEDOUT)); + if (!sch->lpm) { + PREPARE_WORK(&cdev->private->kick_work, + ccw_device_nopath_notify, (void *)cdev); + queue_work(ccw_device_notify_work, &cdev->private->kick_work); + } else if (cdev->private->flags.doverify) + /* Start delayed path verification. */ + ccw_device_online_verify(cdev, 0); +} + +static void +ccw_device_killing_timeout(struct ccw_device *cdev, enum dev_event dev_event) +{ + int ret; + + ret = ccw_device_cancel_halt_clear(cdev); + if (ret == -EBUSY) { + ccw_device_set_timeout(cdev, 3*HZ); + return; + } + 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_notify_work, + &cdev->private->kick_work); + } else + dev_fsm_event(cdev, DEV_EVENT_NOTOPER); + return; + } + //FIXME: Can we get here? + cdev->private->state = DEV_STATE_ONLINE; + if (cdev->handler) + cdev->handler(cdev, cdev->private->intparm, + ERR_PTR(-ETIMEDOUT)); +} + +static void +ccw_device_wait4io_irq(struct ccw_device *cdev, enum dev_event dev_event) +{ + struct irb *irb; + struct subchannel *sch; + + irb = (struct irb *) __LC_IRB; + /* + * Accumulate status and find out if a basic sense is needed. + * This is fine since we have already adapted the lpm. + */ + ccw_device_accumulate_irb(cdev, irb); + if (cdev->private->flags.dosense) { + if (ccw_device_do_sense(cdev, irb) == 0) { + cdev->private->state = DEV_STATE_W4SENSE; + } + return; + } + + /* Iff device is idle, reset timeout. */ + sch = to_subchannel(cdev->dev.parent); + if (!stsch(sch->irq, &sch->schib)) + if (sch->schib.scsw.actl == 0) + ccw_device_set_timeout(cdev, 0); + /* Call the handler. */ + ccw_device_call_handler(cdev); + if (!sch->lpm) { + PREPARE_WORK(&cdev->private->kick_work, + ccw_device_nopath_notify, (void *)cdev); + queue_work(ccw_device_notify_work, &cdev->private->kick_work); + } else if (cdev->private->flags.doverify) + ccw_device_online_verify(cdev, 0); +} + +static void +ccw_device_wait4io_timeout(struct ccw_device *cdev, enum dev_event dev_event) +{ + 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) { + ccw_device_set_timeout(cdev, 3*HZ); + cdev->private->state = DEV_STATE_TIMEOUT_KILL; + return; + } + if (ret == -ENODEV) { + if (!sch->lpm) { + PREPARE_WORK(&cdev->private->kick_work, + ccw_device_nopath_notify, (void *)cdev); + queue_work(ccw_device_notify_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)); + if (!sch->lpm) { + PREPARE_WORK(&cdev->private->kick_work, + ccw_device_nopath_notify, (void *)cdev); + queue_work(ccw_device_notify_work, &cdev->private->kick_work); + } else if (cdev->private->flags.doverify) + /* Start delayed path verification. */ + ccw_device_online_verify(cdev, 0); +} + +static void +ccw_device_wait4io_verify(struct ccw_device *cdev, enum dev_event dev_event) +{ + /* When the I/O has terminated, we have to start verification. */ + if (cdev->private->options.pgroup) + cdev->private->flags.doverify = 1; +} + +static void +ccw_device_stlck_done(struct ccw_device *cdev, enum dev_event dev_event) +{ + struct irb *irb; + + switch (dev_event) { + case DEV_EVENT_INTERRUPT: + irb = (struct irb *) __LC_IRB; + /* Check for unsolicited interrupt. */ + if ((irb->scsw.stctl == + (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) && + (!irb->scsw.cc)) + /* FIXME: we should restart stlck here, but this + * is extremely unlikely ... */ + goto out_wakeup; + + ccw_device_accumulate_irb(cdev, irb); + /* We don't care about basic sense etc. */ + break; + default: /* timeout */ + break; + } +out_wakeup: + wake_up(&cdev->private->wait_q); +} + +static void +ccw_device_start_id(struct ccw_device *cdev, enum dev_event dev_event) +{ + struct subchannel *sch; + + sch = to_subchannel(cdev->dev.parent); + if (cio_enable_subchannel(sch, sch->schib.pmcw.isc) != 0) + /* Couldn't enable the subchannel for i/o. Sick device. */ + return; + + /* After 60s the device recognition is considered to have failed. */ + ccw_device_set_timeout(cdev, 60*HZ); + + cdev->private->state = DEV_STATE_DISCONNECTED_SENSE_ID; + ccw_device_sense_id_start(cdev); +} + +void +device_trigger_reprobe(struct subchannel *sch) +{ + struct ccw_device *cdev; + + if (!sch->dev.driver_data) + return; + cdev = sch->dev.driver_data; + if (cdev->private->state != DEV_STATE_DISCONNECTED) + return; + + /* Update some values. */ + if (stsch(sch->irq, &sch->schib)) + return; + + /* + * The pim, pam, pom values may not be accurate, but they are the best + * we have before performing device selection :/ + */ + sch->lpm = sch->schib.pmcw.pim & + sch->schib.pmcw.pam & + sch->schib.pmcw.pom & + sch->opm; + /* Re-set some bits in the pmcw that were lost. */ + sch->schib.pmcw.isc = 3; + sch->schib.pmcw.csense = 1; + sch->schib.pmcw.ena = 0; + if ((sch->lpm & (sch->lpm - 1)) != 0) + sch->schib.pmcw.mp = 1; + sch->schib.pmcw.intparm = (__u32)(unsigned long)sch; + /* We should also udate ssd info, but this has to wait. */ + ccw_device_start_id(cdev, 0); +} + +static void +ccw_device_offline_irq(struct ccw_device *cdev, enum dev_event dev_event) +{ + struct subchannel *sch; + + sch = to_subchannel(cdev->dev.parent); + /* + * An interrupt in state offline means a previous disable was not + * successful. Try again. + */ + cio_disable_subchannel(sch); +} + +static void +ccw_device_change_cmfstate(struct ccw_device *cdev, enum dev_event dev_event) +{ + retry_set_schib(cdev); + cdev->private->state = DEV_STATE_ONLINE; + dev_fsm_event(cdev, 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 + * state offline. + */ +static void +ccw_device_nop(struct ccw_device *cdev, enum dev_event dev_event) +{ +} + +/* + * Bug operation action. + */ +static void +ccw_device_bug(struct ccw_device *cdev, enum dev_event dev_event) +{ + printk(KERN_EMERG "dev_jumptable[%i][%i] == NULL\n", + cdev->private->state, dev_event); + BUG(); +} + +/* + * device statemachine + */ +fsm_func_t *dev_jumptable[NR_DEV_STATES][NR_DEV_EVENTS] = { + [DEV_STATE_NOT_OPER] = { + [DEV_EVENT_NOTOPER] = ccw_device_nop, + [DEV_EVENT_INTERRUPT] = ccw_device_bug, + [DEV_EVENT_TIMEOUT] = ccw_device_nop, + [DEV_EVENT_VERIFY] = ccw_device_nop, + }, + [DEV_STATE_SENSE_PGID] = { + [DEV_EVENT_NOTOPER] = ccw_device_online_notoper, + [DEV_EVENT_INTERRUPT] = ccw_device_sense_pgid_irq, + [DEV_EVENT_TIMEOUT] = ccw_device_onoff_timeout, + [DEV_EVENT_VERIFY] = ccw_device_nop, + }, + [DEV_STATE_SENSE_ID] = { + [DEV_EVENT_NOTOPER] = ccw_device_recog_notoper, + [DEV_EVENT_INTERRUPT] = ccw_device_sense_id_irq, + [DEV_EVENT_TIMEOUT] = ccw_device_recog_timeout, + [DEV_EVENT_VERIFY] = ccw_device_nop, + }, + [DEV_STATE_OFFLINE] = { + [DEV_EVENT_NOTOPER] = ccw_device_offline_notoper, + [DEV_EVENT_INTERRUPT] = ccw_device_offline_irq, + [DEV_EVENT_TIMEOUT] = ccw_device_nop, + [DEV_EVENT_VERIFY] = ccw_device_nop, + }, + [DEV_STATE_VERIFY] = { + [DEV_EVENT_NOTOPER] = ccw_device_online_notoper, + [DEV_EVENT_INTERRUPT] = ccw_device_verify_irq, + [DEV_EVENT_TIMEOUT] = ccw_device_onoff_timeout, + [DEV_EVENT_VERIFY] = ccw_device_nop, + }, + [DEV_STATE_ONLINE] = { + [DEV_EVENT_NOTOPER] = ccw_device_online_notoper, + [DEV_EVENT_INTERRUPT] = ccw_device_irq, + [DEV_EVENT_TIMEOUT] = ccw_device_online_timeout, + [DEV_EVENT_VERIFY] = ccw_device_online_verify, + }, + [DEV_STATE_W4SENSE] = { + [DEV_EVENT_NOTOPER] = ccw_device_online_notoper, + [DEV_EVENT_INTERRUPT] = ccw_device_w4sense, + [DEV_EVENT_TIMEOUT] = ccw_device_nop, + [DEV_EVENT_VERIFY] = ccw_device_online_verify, + }, + [DEV_STATE_DISBAND_PGID] = { + [DEV_EVENT_NOTOPER] = ccw_device_online_notoper, + [DEV_EVENT_INTERRUPT] = ccw_device_disband_irq, + [DEV_EVENT_TIMEOUT] = ccw_device_onoff_timeout, + [DEV_EVENT_VERIFY] = ccw_device_nop, + }, + [DEV_STATE_BOXED] = { + [DEV_EVENT_NOTOPER] = ccw_device_offline_notoper, + [DEV_EVENT_INTERRUPT] = ccw_device_stlck_done, + [DEV_EVENT_TIMEOUT] = ccw_device_stlck_done, + [DEV_EVENT_VERIFY] = ccw_device_nop, + }, + /* states to wait for i/o completion before doing something */ + [DEV_STATE_CLEAR_VERIFY] = { + [DEV_EVENT_NOTOPER] = ccw_device_online_notoper, + [DEV_EVENT_INTERRUPT] = ccw_device_clear_verify, + [DEV_EVENT_TIMEOUT] = ccw_device_nop, + [DEV_EVENT_VERIFY] = ccw_device_nop, + }, + [DEV_STATE_TIMEOUT_KILL] = { + [DEV_EVENT_NOTOPER] = ccw_device_online_notoper, + [DEV_EVENT_INTERRUPT] = ccw_device_killing_irq, + [DEV_EVENT_TIMEOUT] = ccw_device_killing_timeout, + [DEV_EVENT_VERIFY] = ccw_device_nop, //FIXME + }, + [DEV_STATE_WAIT4IO] = { + [DEV_EVENT_NOTOPER] = ccw_device_online_notoper, + [DEV_EVENT_INTERRUPT] = ccw_device_wait4io_irq, + [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, + [DEV_EVENT_INTERRUPT] = ccw_device_start_id, + [DEV_EVENT_TIMEOUT] = ccw_device_bug, + [DEV_EVENT_VERIFY] = ccw_device_nop, + }, + [DEV_STATE_DISCONNECTED_SENSE_ID] = { + [DEV_EVENT_NOTOPER] = ccw_device_recog_notoper, + [DEV_EVENT_INTERRUPT] = ccw_device_sense_id_irq, + [DEV_EVENT_TIMEOUT] = ccw_device_recog_timeout, + [DEV_EVENT_VERIFY] = ccw_device_nop, + }, + [DEV_STATE_CMFCHANGE] = { + [DEV_EVENT_NOTOPER] = ccw_device_change_cmfstate, + [DEV_EVENT_INTERRUPT] = ccw_device_change_cmfstate, + [DEV_EVENT_TIMEOUT] = ccw_device_change_cmfstate, + [DEV_EVENT_VERIFY] = ccw_device_change_cmfstate, + }, +}; + +/* + * io_subchannel_irq is called for "real" interrupts or for status + * pending conditions on msch. + */ +void +io_subchannel_irq (struct device *pdev) +{ + struct ccw_device *cdev; + + cdev = to_subchannel(pdev)->dev.driver_data; + + CIO_TRACE_EVENT (3, "IRQ"); + CIO_TRACE_EVENT (3, pdev->bus_id); + if (cdev) + dev_fsm_event(cdev, DEV_EVENT_INTERRUPT); +} + +EXPORT_SYMBOL_GPL(ccw_device_set_timeout); diff --git a/drivers/s390/cio/device_id.c b/drivers/s390/cio/device_id.c new file mode 100644 index 000000000000..0e68fb511dc9 --- /dev/null +++ b/drivers/s390/cio/device_id.c @@ -0,0 +1,355 @@ +/* + * drivers/s390/cio/device_id.c + * + * Copyright (C) 2002 IBM Deutschland Entwicklung GmbH, + * IBM Corporation + * Author(s): Cornelia Huck(cohuck@de.ibm.com) + * Martin Schwidefsky (schwidefsky@de.ibm.com) + * + * Sense ID functions. + */ + +#include <linux/module.h> +#include <linux/config.h> +#include <linux/init.h> + +#include <asm/ccwdev.h> +#include <asm/delay.h> +#include <asm/cio.h> +#include <asm/lowcore.h> + +#include "cio.h" +#include "cio_debug.h" +#include "css.h" +#include "device.h" +#include "ioasm.h" + +/* + * diag210 is used under VM to get information about a virtual device + */ +#ifdef CONFIG_ARCH_S390X +int +diag210(struct diag210 * addr) +{ + /* + * diag 210 needs its data below the 2GB border, so we + * use a static data area to be sure + */ + static struct diag210 diag210_tmp; + static DEFINE_SPINLOCK(diag210_lock); + unsigned long flags; + int ccode; + + spin_lock_irqsave(&diag210_lock, flags); + diag210_tmp = *addr; + + asm volatile ( + " lhi %0,-1\n" + " sam31\n" + " diag %1,0,0x210\n" + "0: ipm %0\n" + " srl %0,28\n" + "1: sam64\n" + ".section __ex_table,\"a\"\n" + " .align 8\n" + " .quad 0b,1b\n" + ".previous" + : "=&d" (ccode) : "a" (__pa(&diag210_tmp)) : "cc", "memory" ); + + *addr = diag210_tmp; + spin_unlock_irqrestore(&diag210_lock, flags); + + return ccode; +} +#else +int +diag210(struct diag210 * addr) +{ + int ccode; + + asm volatile ( + " lhi %0,-1\n" + " diag %1,0,0x210\n" + "0: ipm %0\n" + " srl %0,28\n" + "1:\n" + ".section __ex_table,\"a\"\n" + " .align 4\n" + " .long 0b,1b\n" + ".previous" + : "=&d" (ccode) : "a" (__pa(addr)) : "cc", "memory" ); + + return ccode; +} +#endif + +/* + * Input : + * devno - device number + * ps - pointer to sense ID data area + * Output : none + */ +static void +VM_virtual_device_info (__u16 devno, struct senseid *ps) +{ + static struct { + int vrdcvcla, vrdcvtyp, cu_type; + } vm_devices[] = { + { 0x08, 0x01, 0x3480 }, + { 0x08, 0x02, 0x3430 }, + { 0x08, 0x10, 0x3420 }, + { 0x08, 0x42, 0x3424 }, + { 0x08, 0x44, 0x9348 }, + { 0x08, 0x81, 0x3490 }, + { 0x08, 0x82, 0x3422 }, + { 0x10, 0x41, 0x1403 }, + { 0x10, 0x42, 0x3211 }, + { 0x10, 0x43, 0x3203 }, + { 0x10, 0x45, 0x3800 }, + { 0x10, 0x47, 0x3262 }, + { 0x10, 0x48, 0x3820 }, + { 0x10, 0x49, 0x3800 }, + { 0x10, 0x4a, 0x4245 }, + { 0x10, 0x4b, 0x4248 }, + { 0x10, 0x4d, 0x3800 }, + { 0x10, 0x4e, 0x3820 }, + { 0x10, 0x4f, 0x3820 }, + { 0x10, 0x82, 0x2540 }, + { 0x10, 0x84, 0x3525 }, + { 0x20, 0x81, 0x2501 }, + { 0x20, 0x82, 0x2540 }, + { 0x20, 0x84, 0x3505 }, + { 0x40, 0x01, 0x3278 }, + { 0x40, 0x04, 0x3277 }, + { 0x40, 0x80, 0x2250 }, + { 0x40, 0xc0, 0x5080 }, + { 0x80, 0x00, 0x3215 }, + }; + struct diag210 diag_data; + int ccode, i; + + CIO_TRACE_EVENT (4, "VMvdinf"); + + diag_data = (struct diag210) { + .vrdcdvno = devno, + .vrdclen = sizeof (diag_data), + }; + + ccode = diag210 (&diag_data); + ps->reserved = 0xff; + + /* Special case for bloody osa devices. */ + if (diag_data.vrdcvcla == 0x02 && + diag_data.vrdcvtyp == 0x20) { + ps->cu_type = 0x3088; + ps->cu_model = 0x60; + return; + } + for (i = 0; i < sizeof(vm_devices) / sizeof(vm_devices[0]); i++) + if (diag_data.vrdcvcla == vm_devices[i].vrdcvcla && + diag_data.vrdcvtyp == vm_devices[i].vrdcvtyp) { + ps->cu_type = vm_devices[i].cu_type; + return; + } + CIO_MSG_EVENT(0, "DIAG X'210' for device %04X returned (cc = %d):" + "vdev class : %02X, vdev type : %04X \n ... " + "rdev class : %02X, rdev type : %04X, " + "rdev model: %02X\n", + devno, ccode, + diag_data.vrdcvcla, diag_data.vrdcvtyp, + diag_data.vrdcrccl, diag_data.vrdccrty, + diag_data.vrdccrmd); +} + +/* + * Start Sense ID helper function. + * Try to obtain the 'control unit'/'device type' information + * associated with the subchannel. + */ +static int +__ccw_device_sense_id_start(struct ccw_device *cdev) +{ + struct subchannel *sch; + struct ccw1 *ccw; + int ret; + + sch = to_subchannel(cdev->dev.parent); + /* Setup sense channel program. */ + ccw = cdev->private->iccws; + if (sch->schib.pmcw.pim != 0x80) { + /* more than one path installed. */ + ccw->cmd_code = CCW_CMD_SUSPEND_RECONN; + ccw->cda = 0; + ccw->count = 0; + ccw->flags = CCW_FLAG_SLI | CCW_FLAG_CC; + ccw++; + } + ccw->cmd_code = CCW_CMD_SENSE_ID; + ccw->cda = (__u32) __pa (&cdev->private->senseid); + ccw->count = sizeof (struct senseid); + ccw->flags = CCW_FLAG_SLI; + + /* Reset device status. */ + memset(&cdev->private->irb, 0, sizeof(struct irb)); + + /* Try on every path. */ + ret = -ENODEV; + while (cdev->private->imask != 0) { + if ((sch->opm & cdev->private->imask) != 0 && + cdev->private->iretry > 0) { + cdev->private->iretry--; + ret = cio_start (sch, cdev->private->iccws, + cdev->private->imask); + /* ret is 0, -EBUSY, -EACCES or -ENODEV */ + if (ret != -EACCES) + return ret; + } + cdev->private->imask >>= 1; + cdev->private->iretry = 5; + } + return ret; +} + +void +ccw_device_sense_id_start(struct ccw_device *cdev) +{ + int ret; + + memset (&cdev->private->senseid, 0, sizeof (struct senseid)); + cdev->private->senseid.cu_type = 0xFFFF; + cdev->private->imask = 0x80; + cdev->private->iretry = 5; + ret = __ccw_device_sense_id_start(cdev); + if (ret && ret != -EBUSY) + ccw_device_sense_id_done(cdev, ret); +} + +/* + * Called from interrupt context to check if a valid answer + * to Sense ID was received. + */ +static int +ccw_device_check_sense_id(struct ccw_device *cdev) +{ + struct subchannel *sch; + struct irb *irb; + + sch = to_subchannel(cdev->dev.parent); + irb = &cdev->private->irb; + /* Did we get a proper answer ? */ + if (cdev->private->senseid.cu_type != 0xFFFF && + cdev->private->senseid.reserved == 0xFF) { + if (irb->scsw.count < sizeof (struct senseid) - 8) + cdev->private->flags.esid = 1; + return 0; /* Success */ + } + /* Check the error cases. */ + if (irb->scsw.fctl & (SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC)) + return -ETIME; + if (irb->esw.esw0.erw.cons && (irb->ecw[0] & SNS0_CMD_REJECT)) { + /* + * if the device doesn't support the SenseID + * command further retries wouldn't help ... + * NB: We don't check here for intervention required like we + * did before, because tape devices with no tape inserted + * may present this status *in conjunction with* the + * sense id information. So, for intervention required, + * we use the "whack it until it talks" strategy... + */ + CIO_MSG_EVENT(2, "SenseID : device %04x on Subchannel %04x " + "reports cmd reject\n", + cdev->private->devno, sch->irq); + return -EOPNOTSUPP; + } + if (irb->esw.esw0.erw.cons) { + CIO_MSG_EVENT(2, "SenseID : UC on dev %04x, " + "lpum %02X, cnt %02d, sns :" + " %02X%02X%02X%02X %02X%02X%02X%02X ...\n", + cdev->private->devno, + irb->esw.esw0.sublog.lpum, + irb->esw.esw0.erw.scnt, + irb->ecw[0], irb->ecw[1], + irb->ecw[2], irb->ecw[3], + irb->ecw[4], irb->ecw[5], + irb->ecw[6], irb->ecw[7]); + return -EAGAIN; + } + if (irb->scsw.cc == 3) { + if ((sch->orb.lpm & + sch->schib.pmcw.pim & sch->schib.pmcw.pam) != 0) + CIO_MSG_EVENT(2, "SenseID : path %02X for device %04x on" + " subchannel %04x is 'not operational'\n", + sch->orb.lpm, cdev->private->devno, + sch->irq); + return -EACCES; + } + /* Hmm, whatever happened, try again. */ + CIO_MSG_EVENT(2, "SenseID : start_IO() for device %04x on " + "subchannel %04x returns status %02X%02X\n", + cdev->private->devno, sch->irq, + irb->scsw.dstat, irb->scsw.cstat); + return -EAGAIN; +} + +/* + * Got interrupt for Sense ID. + */ +void +ccw_device_sense_id_irq(struct ccw_device *cdev, enum dev_event dev_event) +{ + struct subchannel *sch; + struct irb *irb; + int ret; + + sch = to_subchannel(cdev->dev.parent); + irb = (struct irb *) __LC_IRB; + /* Retry sense id, if needed. */ + if (irb->scsw.stctl == + (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) { + if ((irb->scsw.cc == 1) || !irb->scsw.actl) { + ret = __ccw_device_sense_id_start(cdev); + if (ret && ret != -EBUSY) + ccw_device_sense_id_done(cdev, ret); + } + return; + } + if (ccw_device_accumulate_and_sense(cdev, irb) != 0) + return; + ret = ccw_device_check_sense_id(cdev); + memset(&cdev->private->irb, 0, sizeof(struct irb)); + switch (ret) { + /* 0, -ETIME, -EOPNOTSUPP, -EAGAIN or -EACCES */ + case 0: /* Sense id succeeded. */ + case -ETIME: /* Sense id stopped by timeout. */ + ccw_device_sense_id_done(cdev, ret); + break; + case -EACCES: /* channel is not operational. */ + sch->lpm &= ~cdev->private->imask; + cdev->private->imask >>= 1; + cdev->private->iretry = 5; + /* fall through. */ + case -EAGAIN: /* try again. */ + ret = __ccw_device_sense_id_start(cdev); + if (ret == 0 || ret == -EBUSY) + break; + /* fall through. */ + default: /* Sense ID failed. Try asking VM. */ + if (MACHINE_IS_VM) { + VM_virtual_device_info (cdev->private->devno, + &cdev->private->senseid); + if (cdev->private->senseid.cu_type != 0xFFFF) { + /* Got the device information from VM. */ + ccw_device_sense_id_done(cdev, 0); + return; + } + } + /* + * If we can't couldn't identify the device type we + * consider the device "not operational". + */ + ccw_device_sense_id_done(cdev, -ENODEV); + break; + } +} + +EXPORT_SYMBOL(diag210); diff --git a/drivers/s390/cio/device_ops.c b/drivers/s390/cio/device_ops.c new file mode 100644 index 000000000000..11e260e0b9c9 --- /dev/null +++ b/drivers/s390/cio/device_ops.c @@ -0,0 +1,603 @@ +/* + * drivers/s390/cio/device_ops.c + * + * $Revision: 1.55 $ + * + * Copyright (C) 2002 IBM Deutschland Entwicklung GmbH, + * IBM Corporation + * Author(s): Martin Schwidefsky (schwidefsky@de.ibm.com) + * Cornelia Huck (cohuck@de.ibm.com) + */ +#include <linux/config.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/device.h> +#include <linux/delay.h> + +#include <asm/ccwdev.h> +#include <asm/idals.h> +#include <asm/qdio.h> + +#include "cio.h" +#include "cio_debug.h" +#include "css.h" +#include "chsc.h" +#include "device.h" +#include "qdio.h" + +int +ccw_device_set_options(struct ccw_device *cdev, unsigned long flags) +{ + /* + * The flag usage is mutal exclusive ... + */ + if ((flags & CCWDEV_EARLY_NOTIFICATION) && + (flags & CCWDEV_REPORT_ALL)) + return -EINVAL; + cdev->private->options.fast = (flags & CCWDEV_EARLY_NOTIFICATION) != 0; + cdev->private->options.repall = (flags & CCWDEV_REPORT_ALL) != 0; + cdev->private->options.pgroup = (flags & CCWDEV_DO_PATHGROUP) != 0; + cdev->private->options.force = (flags & CCWDEV_ALLOW_FORCE) != 0; + return 0; +} + +int +ccw_device_clear(struct ccw_device *cdev, unsigned long intparm) +{ + struct subchannel *sch; + int ret; + + if (!cdev) + return -ENODEV; + if (cdev->private->state == DEV_STATE_NOT_OPER) + return -ENODEV; + if (cdev->private->state != DEV_STATE_ONLINE && + cdev->private->state != DEV_STATE_WAIT4IO && + cdev->private->state != DEV_STATE_W4SENSE) + return -EINVAL; + sch = to_subchannel(cdev->dev.parent); + if (!sch) + return -ENODEV; + ret = cio_clear(sch); + if (ret == 0) + cdev->private->intparm = intparm; + return ret; +} + +int +ccw_device_start_key(struct ccw_device *cdev, struct ccw1 *cpa, + unsigned long intparm, __u8 lpm, __u8 key, + unsigned long flags) +{ + struct subchannel *sch; + int ret; + + if (!cdev) + return -ENODEV; + sch = to_subchannel(cdev->dev.parent); + if (!sch) + return -ENODEV; + if (cdev->private->state == DEV_STATE_NOT_OPER) + return -ENODEV; + if (cdev->private->state == DEV_STATE_VERIFY) { + /* Remember to fake irb when finished. */ + if (!cdev->private->flags.fake_irb) { + cdev->private->flags.fake_irb = 1; + cdev->private->intparm = intparm; + return 0; + } else + /* There's already a fake I/O around. */ + return -EBUSY; + } + if (cdev->private->state != DEV_STATE_ONLINE || + ((sch->schib.scsw.stctl & SCSW_STCTL_PRIM_STATUS) && + !(sch->schib.scsw.stctl & SCSW_STCTL_SEC_STATUS)) || + cdev->private->flags.doverify) + return -EBUSY; + ret = cio_set_options (sch, flags); + if (ret) + return ret; + ret = cio_start_key (sch, cpa, lpm, key); + if (ret == 0) + cdev->private->intparm = intparm; + return ret; +} + + +int +ccw_device_start_timeout_key(struct ccw_device *cdev, struct ccw1 *cpa, + unsigned long intparm, __u8 lpm, __u8 key, + unsigned long flags, int expires) +{ + int ret; + + if (!cdev) + return -ENODEV; + ccw_device_set_timeout(cdev, expires); + ret = ccw_device_start_key(cdev, cpa, intparm, lpm, key, flags); + if (ret != 0) + ccw_device_set_timeout(cdev, 0); + return ret; +} + +int +ccw_device_start(struct ccw_device *cdev, struct ccw1 *cpa, + unsigned long intparm, __u8 lpm, unsigned long flags) +{ + return ccw_device_start_key(cdev, cpa, intparm, lpm, + default_storage_key, flags); +} + +int +ccw_device_start_timeout(struct ccw_device *cdev, struct ccw1 *cpa, + unsigned long intparm, __u8 lpm, unsigned long flags, + int expires) +{ + return ccw_device_start_timeout_key(cdev, cpa, intparm, lpm, + default_storage_key, flags, + expires); +} + + +int +ccw_device_halt(struct ccw_device *cdev, unsigned long intparm) +{ + struct subchannel *sch; + int ret; + + if (!cdev) + return -ENODEV; + if (cdev->private->state == DEV_STATE_NOT_OPER) + return -ENODEV; + if (cdev->private->state != DEV_STATE_ONLINE && + cdev->private->state != DEV_STATE_WAIT4IO && + cdev->private->state != DEV_STATE_W4SENSE) + return -EINVAL; + sch = to_subchannel(cdev->dev.parent); + if (!sch) + return -ENODEV; + ret = cio_halt(sch); + if (ret == 0) + cdev->private->intparm = intparm; + return ret; +} + +int +ccw_device_resume(struct ccw_device *cdev) +{ + struct subchannel *sch; + + if (!cdev) + return -ENODEV; + sch = to_subchannel(cdev->dev.parent); + if (!sch) + return -ENODEV; + if (cdev->private->state == DEV_STATE_NOT_OPER) + return -ENODEV; + if (cdev->private->state != DEV_STATE_ONLINE || + !(sch->schib.scsw.actl & SCSW_ACTL_SUSPENDED)) + return -EINVAL; + return cio_resume(sch); +} + +/* + * Pass interrupt to device driver. + */ +int +ccw_device_call_handler(struct ccw_device *cdev) +{ + struct subchannel *sch; + unsigned int stctl; + int ending_status; + + sch = to_subchannel(cdev->dev.parent); + + /* + * we allow for the device action handler if . + * - we received ending status + * - the action handler requested to see all interrupts + * - we received an intermediate status + * - fast notification was requested (primary status) + * - unsolicited interrupts + */ + stctl = cdev->private->irb.scsw.stctl; + ending_status = (stctl & SCSW_STCTL_SEC_STATUS) || + (stctl == (SCSW_STCTL_ALERT_STATUS | SCSW_STCTL_STATUS_PEND)) || + (stctl == SCSW_STCTL_STATUS_PEND); + if (!ending_status && + !cdev->private->options.repall && + !(stctl & SCSW_STCTL_INTER_STATUS) && + !(cdev->private->options.fast && + (stctl & SCSW_STCTL_PRIM_STATUS))) + return 0; + + /* + * Now we are ready to call the device driver interrupt handler. + */ + if (cdev->handler) + cdev->handler(cdev, cdev->private->intparm, + &cdev->private->irb); + + /* + * Clear the old and now useless interrupt response block. + */ + memset(&cdev->private->irb, 0, sizeof(struct irb)); + + return 1; +} + +/* + * Search for CIW command in extended sense data. + */ +struct ciw * +ccw_device_get_ciw(struct ccw_device *cdev, __u32 ct) +{ + int ciw_cnt; + + if (cdev->private->flags.esid == 0) + return NULL; + for (ciw_cnt = 0; ciw_cnt < MAX_CIWS; ciw_cnt++) + if (cdev->private->senseid.ciw[ciw_cnt].ct == ct) + return cdev->private->senseid.ciw + ciw_cnt; + return NULL; +} + +__u8 +ccw_device_get_path_mask(struct ccw_device *cdev) +{ + struct subchannel *sch; + + sch = to_subchannel(cdev->dev.parent); + if (!sch) + return 0; + else + return sch->vpm; +} + +static void +ccw_device_wake_up(struct ccw_device *cdev, unsigned long ip, struct irb *irb) +{ + if (!ip) + /* unsolicited interrupt */ + return; + + /* Abuse intparm for error reporting. */ + if (IS_ERR(irb)) + cdev->private->intparm = -EIO; + else if ((irb->scsw.dstat != + (DEV_STAT_CHN_END|DEV_STAT_DEV_END)) || + (irb->scsw.cstat != 0)) { + /* + * We didn't get channel end / device end. Check if path + * verification has been started; we can retry after it has + * finished. We also retry unit checks except for command reject + * or intervention required. + */ + if (cdev->private->flags.doverify || + cdev->private->state == DEV_STATE_VERIFY) + cdev->private->intparm = -EAGAIN; + if ((irb->scsw.dstat & DEV_STAT_UNIT_CHECK) && + !(irb->ecw[0] & + (SNS0_CMD_REJECT | SNS0_INTERVENTION_REQ))) + cdev->private->intparm = -EAGAIN; + else + cdev->private->intparm = -EIO; + + } else + cdev->private->intparm = 0; + wake_up(&cdev->private->wait_q); +} + +static inline int +__ccw_device_retry_loop(struct ccw_device *cdev, struct ccw1 *ccw, long magic, __u8 lpm) +{ + int ret; + struct subchannel *sch; + + sch = to_subchannel(cdev->dev.parent); + do { + ret = cio_start (sch, ccw, lpm); + if ((ret == -EBUSY) || (ret == -EACCES)) { + /* Try again later. */ + spin_unlock_irq(&sch->lock); + msleep(10); + spin_lock_irq(&sch->lock); + continue; + } + if (ret != 0) + /* Non-retryable error. */ + break; + /* Wait for end of request. */ + cdev->private->intparm = magic; + spin_unlock_irq(&sch->lock); + wait_event(cdev->private->wait_q, + (cdev->private->intparm == -EIO) || + (cdev->private->intparm == -EAGAIN) || + (cdev->private->intparm == 0)); + spin_lock_irq(&sch->lock); + /* Check at least for channel end / device end */ + if (cdev->private->intparm == -EIO) { + /* Non-retryable error. */ + ret = -EIO; + break; + } + if (cdev->private->intparm == 0) + /* Success. */ + break; + /* Try again later. */ + spin_unlock_irq(&sch->lock); + msleep(10); + spin_lock_irq(&sch->lock); + } while (1); + + return ret; +} + +/** + * read_dev_chars() - read device characteristics + * @param cdev target ccw device + * @param buffer pointer to buffer for rdc data + * @param length size of rdc data + * @returns 0 for success, negative error value on failure + * + * Context: + * called for online device, lock not held + **/ +int +read_dev_chars (struct ccw_device *cdev, void **buffer, int length) +{ + void (*handler)(struct ccw_device *, unsigned long, struct irb *); + struct subchannel *sch; + int ret; + struct ccw1 *rdc_ccw; + + if (!cdev) + return -ENODEV; + if (!buffer || !length) + return -EINVAL; + sch = to_subchannel(cdev->dev.parent); + + CIO_TRACE_EVENT (4, "rddevch"); + CIO_TRACE_EVENT (4, sch->dev.bus_id); + + rdc_ccw = kmalloc(sizeof(struct ccw1), GFP_KERNEL | GFP_DMA); + if (!rdc_ccw) + return -ENOMEM; + memset(rdc_ccw, 0, sizeof(struct ccw1)); + rdc_ccw->cmd_code = CCW_CMD_RDC; + rdc_ccw->count = length; + rdc_ccw->flags = CCW_FLAG_SLI; + ret = set_normalized_cda (rdc_ccw, (*buffer)); + if (ret != 0) { + kfree(rdc_ccw); + return ret; + } + + spin_lock_irq(&sch->lock); + /* Save interrupt handler. */ + handler = cdev->handler; + /* Temporarily install own handler. */ + cdev->handler = ccw_device_wake_up; + if (cdev->private->state != DEV_STATE_ONLINE) + ret = -ENODEV; + else if (((sch->schib.scsw.stctl & SCSW_STCTL_PRIM_STATUS) && + !(sch->schib.scsw.stctl & SCSW_STCTL_SEC_STATUS)) || + cdev->private->flags.doverify) + ret = -EBUSY; + else + /* 0x00D9C4C3 == ebcdic "RDC" */ + ret = __ccw_device_retry_loop(cdev, rdc_ccw, 0x00D9C4C3, 0); + + /* Restore interrupt handler. */ + cdev->handler = handler; + spin_unlock_irq(&sch->lock); + + clear_normalized_cda (rdc_ccw); + kfree(rdc_ccw); + + return ret; +} + +/* + * Read Configuration data using path mask + */ +int +read_conf_data_lpm (struct ccw_device *cdev, void **buffer, int *length, __u8 lpm) +{ + void (*handler)(struct ccw_device *, unsigned long, struct irb *); + struct subchannel *sch; + struct ciw *ciw; + char *rcd_buf; + int ret; + struct ccw1 *rcd_ccw; + + if (!cdev) + return -ENODEV; + if (!buffer || !length) + return -EINVAL; + sch = to_subchannel(cdev->dev.parent); + + CIO_TRACE_EVENT (4, "rdconf"); + CIO_TRACE_EVENT (4, sch->dev.bus_id); + + /* + * scan for RCD command in extended SenseID data + */ + ciw = ccw_device_get_ciw(cdev, CIW_TYPE_RCD); + if (!ciw || ciw->cmd == 0) + return -EOPNOTSUPP; + + rcd_ccw = kmalloc(sizeof(struct ccw1), GFP_KERNEL | GFP_DMA); + if (!rcd_ccw) + return -ENOMEM; + memset(rcd_ccw, 0, sizeof(struct ccw1)); + rcd_buf = kmalloc(ciw->count, GFP_KERNEL | GFP_DMA); + if (!rcd_buf) { + kfree(rcd_ccw); + return -ENOMEM; + } + memset (rcd_buf, 0, ciw->count); + rcd_ccw->cmd_code = ciw->cmd; + rcd_ccw->cda = (__u32) __pa (rcd_buf); + rcd_ccw->count = ciw->count; + rcd_ccw->flags = CCW_FLAG_SLI; + + spin_lock_irq(&sch->lock); + /* Save interrupt handler. */ + handler = cdev->handler; + /* Temporarily install own handler. */ + cdev->handler = ccw_device_wake_up; + if (cdev->private->state != DEV_STATE_ONLINE) + ret = -ENODEV; + else if (((sch->schib.scsw.stctl & SCSW_STCTL_PRIM_STATUS) && + !(sch->schib.scsw.stctl & SCSW_STCTL_SEC_STATUS)) || + cdev->private->flags.doverify) + ret = -EBUSY; + else + /* 0x00D9C3C4 == ebcdic "RCD" */ + ret = __ccw_device_retry_loop(cdev, rcd_ccw, 0x00D9C3C4, lpm); + + /* Restore interrupt handler. */ + cdev->handler = handler; + spin_unlock_irq(&sch->lock); + + /* + * on success we update the user input parms + */ + if (ret) { + kfree (rcd_buf); + *buffer = NULL; + *length = 0; + } else { + *length = ciw->count; + *buffer = rcd_buf; + } + kfree(rcd_ccw); + + return ret; +} + +/* + * Read Configuration data + */ +int +read_conf_data (struct ccw_device *cdev, void **buffer, int *length) +{ + return read_conf_data_lpm (cdev, buffer, length, 0); +} + +/* + * Try to break the lock on a boxed device. + */ +int +ccw_device_stlck(struct ccw_device *cdev) +{ + void *buf, *buf2; + unsigned long flags; + struct subchannel *sch; + int ret; + + if (!cdev) + return -ENODEV; + + if (cdev->drv && !cdev->private->options.force) + return -EINVAL; + + sch = to_subchannel(cdev->dev.parent); + + CIO_TRACE_EVENT(2, "stl lock"); + CIO_TRACE_EVENT(2, cdev->dev.bus_id); + + buf = kmalloc(32*sizeof(char), GFP_DMA|GFP_KERNEL); + if (!buf) + return -ENOMEM; + buf2 = kmalloc(32*sizeof(char), GFP_DMA|GFP_KERNEL); + if (!buf2) { + kfree(buf); + return -ENOMEM; + } + spin_lock_irqsave(&sch->lock, flags); + ret = cio_enable_subchannel(sch, 3); + if (ret) + goto out_unlock; + /* + * Setup ccw. We chain an unconditional reserve and a release so we + * only break the lock. + */ + cdev->private->iccws[0].cmd_code = CCW_CMD_STLCK; + cdev->private->iccws[0].cda = (__u32) __pa(buf); + cdev->private->iccws[0].count = 32; + cdev->private->iccws[0].flags = CCW_FLAG_CC; + cdev->private->iccws[1].cmd_code = CCW_CMD_RELEASE; + cdev->private->iccws[1].cda = (__u32) __pa(buf2); + cdev->private->iccws[1].count = 32; + cdev->private->iccws[1].flags = 0; + ret = cio_start(sch, cdev->private->iccws, 0); + if (ret) { + cio_disable_subchannel(sch); //FIXME: return code? + goto out_unlock; + } + cdev->private->irb.scsw.actl |= SCSW_ACTL_START_PEND; + spin_unlock_irqrestore(&sch->lock, flags); + wait_event(cdev->private->wait_q, cdev->private->irb.scsw.actl == 0); + spin_lock_irqsave(&sch->lock, flags); + cio_disable_subchannel(sch); //FIXME: return code? + if ((cdev->private->irb.scsw.dstat != + (DEV_STAT_CHN_END|DEV_STAT_DEV_END)) || + (cdev->private->irb.scsw.cstat != 0)) + ret = -EIO; + /* Clear irb. */ + memset(&cdev->private->irb, 0, sizeof(struct irb)); +out_unlock: + if (buf) + kfree(buf); + if (buf2) + kfree(buf2); + spin_unlock_irqrestore(&sch->lock, flags); + return ret; +} + +void * +ccw_device_get_chp_desc(struct ccw_device *cdev, int chp_no) +{ + struct subchannel *sch; + + sch = to_subchannel(cdev->dev.parent); + return chsc_get_chp_desc(sch, chp_no); +} + +// FIXME: these have to go: + +int +_ccw_device_get_subchannel_number(struct ccw_device *cdev) +{ + return cdev->private->irq; +} + +int +_ccw_device_get_device_number(struct ccw_device *cdev) +{ + return cdev->private->devno; +} + + +MODULE_LICENSE("GPL"); +EXPORT_SYMBOL(ccw_device_set_options); +EXPORT_SYMBOL(ccw_device_clear); +EXPORT_SYMBOL(ccw_device_halt); +EXPORT_SYMBOL(ccw_device_resume); +EXPORT_SYMBOL(ccw_device_start_timeout); +EXPORT_SYMBOL(ccw_device_start); +EXPORT_SYMBOL(ccw_device_start_timeout_key); +EXPORT_SYMBOL(ccw_device_start_key); +EXPORT_SYMBOL(ccw_device_get_ciw); +EXPORT_SYMBOL(ccw_device_get_path_mask); +EXPORT_SYMBOL(read_conf_data); +EXPORT_SYMBOL(read_dev_chars); +EXPORT_SYMBOL(_ccw_device_get_subchannel_number); +EXPORT_SYMBOL(_ccw_device_get_device_number); +EXPORT_SYMBOL_GPL(ccw_device_get_chp_desc); +EXPORT_SYMBOL_GPL(read_conf_data_lpm); diff --git a/drivers/s390/cio/device_pgid.c b/drivers/s390/cio/device_pgid.c new file mode 100644 index 000000000000..0adac8a67331 --- /dev/null +++ b/drivers/s390/cio/device_pgid.c @@ -0,0 +1,448 @@ +/* + * drivers/s390/cio/device_pgid.c + * + * Copyright (C) 2002 IBM Deutschland Entwicklung GmbH, + * IBM Corporation + * Author(s): Cornelia Huck(cohuck@de.ibm.com) + * Martin Schwidefsky (schwidefsky@de.ibm.com) + * + * Path Group ID functions. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/init.h> + +#include <asm/ccwdev.h> +#include <asm/cio.h> +#include <asm/delay.h> +#include <asm/lowcore.h> + +#include "cio.h" +#include "cio_debug.h" +#include "css.h" +#include "device.h" + +/* + * Start Sense Path Group ID helper function. Used in ccw_device_recog + * and ccw_device_sense_pgid. + */ +static int +__ccw_device_sense_pgid_start(struct ccw_device *cdev) +{ + struct subchannel *sch; + struct ccw1 *ccw; + int ret; + + sch = to_subchannel(cdev->dev.parent); + /* Setup sense path group id channel program. */ + ccw = cdev->private->iccws; + ccw->cmd_code = CCW_CMD_SENSE_PGID; + ccw->cda = (__u32) __pa (&cdev->private->pgid); + ccw->count = sizeof (struct pgid); + ccw->flags = CCW_FLAG_SLI; + + /* Reset device status. */ + memset(&cdev->private->irb, 0, sizeof(struct irb)); + /* Try on every path. */ + ret = -ENODEV; + while (cdev->private->imask != 0) { + /* Try every path multiple times. */ + if (cdev->private->iretry > 0) { + cdev->private->iretry--; + ret = cio_start (sch, cdev->private->iccws, + cdev->private->imask); + /* ret is 0, -EBUSY, -EACCES or -ENODEV */ + if (ret != -EACCES) + return ret; + CIO_MSG_EVENT(2, "SNID - Device %04x on Subchannel " + "%04x, lpm %02X, became 'not " + "operational'\n", + cdev->private->devno, sch->irq, + cdev->private->imask); + + } + cdev->private->imask >>= 1; + cdev->private->iretry = 5; + } + return ret; +} + +void +ccw_device_sense_pgid_start(struct ccw_device *cdev) +{ + int ret; + + cdev->private->state = DEV_STATE_SENSE_PGID; + cdev->private->imask = 0x80; + cdev->private->iretry = 5; + memset (&cdev->private->pgid, 0, sizeof (struct pgid)); + ret = __ccw_device_sense_pgid_start(cdev); + if (ret && ret != -EBUSY) + ccw_device_sense_pgid_done(cdev, ret); +} + +/* + * Called from interrupt context to check if a valid answer + * to Sense Path Group ID was received. + */ +static int +__ccw_device_check_sense_pgid(struct ccw_device *cdev) +{ + struct subchannel *sch; + struct irb *irb; + + sch = to_subchannel(cdev->dev.parent); + irb = &cdev->private->irb; + if (irb->scsw.fctl & (SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC)) + return -ETIME; + if (irb->esw.esw0.erw.cons && + (irb->ecw[0]&(SNS0_CMD_REJECT|SNS0_INTERVENTION_REQ))) { + /* + * If the device doesn't support the Sense Path Group ID + * command further retries wouldn't help ... + */ + return -EOPNOTSUPP; + } + if (irb->esw.esw0.erw.cons) { + CIO_MSG_EVENT(2, "SNID - device %04x, unit check, " + "lpum %02X, cnt %02d, sns : " + "%02X%02X%02X%02X %02X%02X%02X%02X ...\n", + cdev->private->devno, + irb->esw.esw0.sublog.lpum, + irb->esw.esw0.erw.scnt, + irb->ecw[0], irb->ecw[1], + irb->ecw[2], irb->ecw[3], + irb->ecw[4], irb->ecw[5], + irb->ecw[6], irb->ecw[7]); + return -EAGAIN; + } + if (irb->scsw.cc == 3) { + CIO_MSG_EVENT(2, "SNID - Device %04x on Subchannel " + "%04x, lpm %02X, became 'not operational'\n", + cdev->private->devno, sch->irq, sch->orb.lpm); + return -EACCES; + } + if (cdev->private->pgid.inf.ps.state2 == SNID_STATE2_RESVD_ELSE) { + CIO_MSG_EVENT(2, "SNID - Device %04x on Subchannel %04x " + "is reserved by someone else\n", + cdev->private->devno, sch->irq); + return -EUSERS; + } + return 0; +} + +/* + * Got interrupt for Sense Path Group ID. + */ +void +ccw_device_sense_pgid_irq(struct ccw_device *cdev, enum dev_event dev_event) +{ + struct subchannel *sch; + struct irb *irb; + int ret; + + irb = (struct irb *) __LC_IRB; + /* Retry sense pgid for cc=1. */ + if (irb->scsw.stctl == + (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) { + if (irb->scsw.cc == 1) { + ret = __ccw_device_sense_pgid_start(cdev); + if (ret && ret != -EBUSY) + ccw_device_sense_pgid_done(cdev, ret); + } + return; + } + if (ccw_device_accumulate_and_sense(cdev, irb) != 0) + return; + sch = to_subchannel(cdev->dev.parent); + ret = __ccw_device_check_sense_pgid(cdev); + memset(&cdev->private->irb, 0, sizeof(struct irb)); + switch (ret) { + /* 0, -ETIME, -EOPNOTSUPP, -EAGAIN, -EACCES or -EUSERS */ + case 0: /* Sense Path Group ID successful. */ + if (cdev->private->pgid.inf.ps.state1 == SNID_STATE1_RESET) + memcpy(&cdev->private->pgid, &global_pgid, + sizeof(struct pgid)); + ccw_device_sense_pgid_done(cdev, 0); + break; + case -EOPNOTSUPP: /* Sense Path Group ID not supported */ + ccw_device_sense_pgid_done(cdev, -EOPNOTSUPP); + break; + case -ETIME: /* Sense path group id stopped by timeout. */ + ccw_device_sense_pgid_done(cdev, -ETIME); + break; + case -EACCES: /* channel is not operational. */ + sch->lpm &= ~cdev->private->imask; + cdev->private->imask >>= 1; + cdev->private->iretry = 5; + /* Fall through. */ + case -EAGAIN: /* Try again. */ + ret = __ccw_device_sense_pgid_start(cdev); + if (ret != 0 && ret != -EBUSY) + ccw_device_sense_pgid_done(cdev, -ENODEV); + break; + case -EUSERS: /* device is reserved for someone else. */ + ccw_device_sense_pgid_done(cdev, -EUSERS); + break; + } +} + +/* + * Path Group ID helper function. + */ +static int +__ccw_device_do_pgid(struct ccw_device *cdev, __u8 func) +{ + struct subchannel *sch; + struct ccw1 *ccw; + int ret; + + sch = to_subchannel(cdev->dev.parent); + + /* Setup sense path group id channel program. */ + cdev->private->pgid.inf.fc = func; + ccw = cdev->private->iccws; + if (!cdev->private->flags.pgid_single) { + cdev->private->pgid.inf.fc |= SPID_FUNC_MULTI_PATH; + ccw->cmd_code = CCW_CMD_SUSPEND_RECONN; + ccw->cda = 0; + ccw->count = 0; + ccw->flags = CCW_FLAG_SLI | CCW_FLAG_CC; + ccw++; + } else + cdev->private->pgid.inf.fc |= SPID_FUNC_SINGLE_PATH; + + ccw->cmd_code = CCW_CMD_SET_PGID; + ccw->cda = (__u32) __pa (&cdev->private->pgid); + ccw->count = sizeof (struct pgid); + ccw->flags = CCW_FLAG_SLI; + + /* Reset device status. */ + memset(&cdev->private->irb, 0, sizeof(struct irb)); + + /* Try multiple times. */ + ret = -ENODEV; + if (cdev->private->iretry > 0) { + cdev->private->iretry--; + ret = cio_start (sch, cdev->private->iccws, + cdev->private->imask); + /* ret is 0, -EBUSY, -EACCES or -ENODEV */ + if ((ret != -EACCES) && (ret != -ENODEV)) + return ret; + } + /* PGID command failed on this path. Switch it off. */ + sch->lpm &= ~cdev->private->imask; + sch->vpm &= ~cdev->private->imask; + CIO_MSG_EVENT(2, "SPID - Device %04x on Subchannel " + "%04x, lpm %02X, became 'not operational'\n", + cdev->private->devno, sch->irq, cdev->private->imask); + return ret; +} + +/* + * Called from interrupt context to check if a valid answer + * to Set Path Group ID was received. + */ +static int +__ccw_device_check_pgid(struct ccw_device *cdev) +{ + struct subchannel *sch; + struct irb *irb; + + sch = to_subchannel(cdev->dev.parent); + irb = &cdev->private->irb; + if (irb->scsw.fctl & (SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC)) + return -ETIME; + if (irb->esw.esw0.erw.cons) { + if (irb->ecw[0] & SNS0_CMD_REJECT) + return -EOPNOTSUPP; + /* Hmm, whatever happened, try again. */ + CIO_MSG_EVENT(2, "SPID - device %04x, unit check, cnt %02d, " + "sns : %02X%02X%02X%02X %02X%02X%02X%02X ...\n", + cdev->private->devno, irb->esw.esw0.erw.scnt, + irb->ecw[0], irb->ecw[1], + irb->ecw[2], irb->ecw[3], + irb->ecw[4], irb->ecw[5], + irb->ecw[6], irb->ecw[7]); + return -EAGAIN; + } + if (irb->scsw.cc == 3) { + CIO_MSG_EVENT(2, "SPID - Device %04x on Subchannel " + "%04x, lpm %02X, became 'not operational'\n", + cdev->private->devno, sch->irq, + cdev->private->imask); + return -EACCES; + } + return 0; +} + +static void +__ccw_device_verify_start(struct ccw_device *cdev) +{ + struct subchannel *sch; + __u8 imask, func; + int ret; + + sch = to_subchannel(cdev->dev.parent); + while (sch->vpm != sch->lpm) { + /* Find first unequal bit in vpm vs. lpm */ + for (imask = 0x80; imask != 0; imask >>= 1) + if ((sch->vpm & imask) != (sch->lpm & imask)) + break; + cdev->private->imask = imask; + func = (sch->vpm & imask) ? + SPID_FUNC_RESIGN : SPID_FUNC_ESTABLISH; + ret = __ccw_device_do_pgid(cdev, func); + if (ret == 0 || ret == -EBUSY) + return; + cdev->private->iretry = 5; + } + ccw_device_verify_done(cdev, (sch->lpm != 0) ? 0 : -ENODEV); +} + +/* + * Got interrupt for Set Path Group ID. + */ +void +ccw_device_verify_irq(struct ccw_device *cdev, enum dev_event dev_event) +{ + struct subchannel *sch; + struct irb *irb; + int ret; + + irb = (struct irb *) __LC_IRB; + /* Retry set pgid for cc=1. */ + if (irb->scsw.stctl == + (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) { + if (irb->scsw.cc == 1) + __ccw_device_verify_start(cdev); + return; + } + if (ccw_device_accumulate_and_sense(cdev, irb) != 0) + return; + sch = to_subchannel(cdev->dev.parent); + ret = __ccw_device_check_pgid(cdev); + memset(&cdev->private->irb, 0, sizeof(struct irb)); + switch (ret) { + /* 0, -ETIME, -EAGAIN, -EOPNOTSUPP or -EACCES */ + case 0: + /* Establish or Resign Path Group done. Update vpm. */ + if ((sch->lpm & cdev->private->imask) != 0) + sch->vpm |= cdev->private->imask; + else + sch->vpm &= ~cdev->private->imask; + cdev->private->iretry = 5; + __ccw_device_verify_start(cdev); + break; + case -EOPNOTSUPP: + /* + * One of those strange devices which claim to be able + * to do multipathing but not for Set Path Group ID. + */ + if (cdev->private->flags.pgid_single) { + ccw_device_verify_done(cdev, -EOPNOTSUPP); + break; + } + cdev->private->flags.pgid_single = 1; + /* fall through. */ + case -EAGAIN: /* Try again. */ + __ccw_device_verify_start(cdev); + break; + case -ETIME: /* Set path group id stopped by timeout. */ + ccw_device_verify_done(cdev, -ETIME); + break; + case -EACCES: /* channel is not operational. */ + sch->lpm &= ~cdev->private->imask; + sch->vpm &= ~cdev->private->imask; + cdev->private->iretry = 5; + __ccw_device_verify_start(cdev); + break; + } +} + +void +ccw_device_verify_start(struct ccw_device *cdev) +{ + cdev->private->flags.pgid_single = 0; + cdev->private->iretry = 5; + __ccw_device_verify_start(cdev); +} + +static void +__ccw_device_disband_start(struct ccw_device *cdev) +{ + struct subchannel *sch; + int ret; + + sch = to_subchannel(cdev->dev.parent); + while (cdev->private->imask != 0) { + if (sch->lpm & cdev->private->imask) { + ret = __ccw_device_do_pgid(cdev, SPID_FUNC_DISBAND); + if (ret == 0) + return; + } + cdev->private->iretry = 5; + cdev->private->imask >>= 1; + } + ccw_device_verify_done(cdev, (sch->lpm != 0) ? 0 : -ENODEV); +} + +/* + * Got interrupt for Unset Path Group ID. + */ +void +ccw_device_disband_irq(struct ccw_device *cdev, enum dev_event dev_event) +{ + struct subchannel *sch; + struct irb *irb; + int ret; + + irb = (struct irb *) __LC_IRB; + /* Retry set pgid for cc=1. */ + if (irb->scsw.stctl == + (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) { + if (irb->scsw.cc == 1) + __ccw_device_disband_start(cdev); + return; + } + if (ccw_device_accumulate_and_sense(cdev, irb) != 0) + return; + sch = to_subchannel(cdev->dev.parent); + ret = __ccw_device_check_pgid(cdev); + memset(&cdev->private->irb, 0, sizeof(struct irb)); + switch (ret) { + /* 0, -ETIME, -EAGAIN, -EOPNOTSUPP or -EACCES */ + case 0: /* disband successful. */ + sch->vpm = 0; + ccw_device_disband_done(cdev, ret); + break; + case -EOPNOTSUPP: + /* + * One of those strange devices which claim to be able + * to do multipathing but not for Unset Path Group ID. + */ + cdev->private->flags.pgid_single = 1; + /* fall through. */ + case -EAGAIN: /* Try again. */ + __ccw_device_disband_start(cdev); + break; + case -ETIME: /* Set path group id stopped by timeout. */ + ccw_device_disband_done(cdev, -ETIME); + break; + case -EACCES: /* channel is not operational. */ + cdev->private->imask >>= 1; + cdev->private->iretry = 5; + __ccw_device_disband_start(cdev); + break; + } +} + +void +ccw_device_disband_start(struct ccw_device *cdev) +{ + cdev->private->flags.pgid_single = 0; + cdev->private->iretry = 5; + cdev->private->imask = 0x80; + __ccw_device_disband_start(cdev); +} diff --git a/drivers/s390/cio/device_status.c b/drivers/s390/cio/device_status.c new file mode 100644 index 000000000000..4ab2e0d95009 --- /dev/null +++ b/drivers/s390/cio/device_status.c @@ -0,0 +1,385 @@ +/* + * drivers/s390/cio/device_status.c + * + * Copyright (C) 2002 IBM Deutschland Entwicklung GmbH, + * IBM Corporation + * Author(s): Cornelia Huck(cohuck@de.ibm.com) + * Martin Schwidefsky (schwidefsky@de.ibm.com) + * + * Status accumulation and basic sense functions. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/init.h> + +#include <asm/ccwdev.h> +#include <asm/cio.h> + +#include "cio.h" +#include "cio_debug.h" +#include "css.h" +#include "device.h" +#include "ioasm.h" + +/* + * Check for any kind of channel or interface control check but don't + * issue the message for the console device + */ +static inline void +ccw_device_msg_control_check(struct ccw_device *cdev, struct irb *irb) +{ + if (!(irb->scsw.cstat & (SCHN_STAT_CHN_DATA_CHK | + SCHN_STAT_CHN_CTRL_CHK | + SCHN_STAT_INTF_CTRL_CHK))) + return; + + CIO_MSG_EVENT(0, "Channel-Check or Interface-Control-Check " + "received" + " ... device %04X on subchannel %04X, dev_stat " + ": %02X sch_stat : %02X\n", + cdev->private->devno, cdev->private->irq, + cdev->private->irb.scsw.dstat, + cdev->private->irb.scsw.cstat); + + if (irb->scsw.cc != 3) { + char dbf_text[15]; + + sprintf(dbf_text, "chk%x", cdev->private->irq); + CIO_TRACE_EVENT(0, dbf_text); + CIO_HEX_EVENT(0, &cdev->private->irb, sizeof (struct irb)); + } +} + +/* + * Some paths became not operational (pno bit in scsw is set). + */ +static void +ccw_device_path_notoper(struct ccw_device *cdev) +{ + struct subchannel *sch; + + sch = to_subchannel(cdev->dev.parent); + stsch (sch->irq, &sch->schib); + + CIO_MSG_EVENT(0, "%s(%04x) - path(s) %02x are " + "not operational \n", __FUNCTION__, sch->irq, + sch->schib.pmcw.pnom); + + sch->lpm &= ~sch->schib.pmcw.pnom; + if (cdev->private->options.pgroup) + cdev->private->flags.doverify = 1; +} + +/* + * Copy valid bits from the extended control word to device irb. + */ +static inline void +ccw_device_accumulate_ecw(struct ccw_device *cdev, struct irb *irb) +{ + /* + * Copy extended control bit if it is valid... yes there + * are condition that have to be met for the extended control + * bit to have meaning. Sick. + */ + cdev->private->irb.scsw.ectl = 0; + if ((irb->scsw.stctl & SCSW_STCTL_ALERT_STATUS) && + !(irb->scsw.stctl & SCSW_STCTL_INTER_STATUS)) + cdev->private->irb.scsw.ectl = irb->scsw.ectl; + /* Check if extended control word is valid. */ + if (!cdev->private->irb.scsw.ectl) + return; + /* Copy concurrent sense / model dependent information. */ + memcpy (&cdev->private->irb.ecw, irb->ecw, sizeof (irb->ecw)); +} + +/* + * Check if extended status word is valid. + */ +static inline int +ccw_device_accumulate_esw_valid(struct irb *irb) +{ + if (!irb->scsw.eswf && irb->scsw.stctl == SCSW_STCTL_STATUS_PEND) + return 0; + if (irb->scsw.stctl == + (SCSW_STCTL_INTER_STATUS|SCSW_STCTL_STATUS_PEND) && + !(irb->scsw.actl & SCSW_ACTL_SUSPENDED)) + return 0; + return 1; +} + +/* + * Copy valid bits from the extended status word to device irb. + */ +static inline void +ccw_device_accumulate_esw(struct ccw_device *cdev, struct irb *irb) +{ + struct irb *cdev_irb; + struct sublog *cdev_sublog, *sublog; + + if (!ccw_device_accumulate_esw_valid(irb)) + return; + + cdev_irb = &cdev->private->irb; + + /* Copy last path used mask. */ + cdev_irb->esw.esw1.lpum = irb->esw.esw1.lpum; + + /* Copy subchannel logout information if esw is of format 0. */ + if (irb->scsw.eswf) { + cdev_sublog = &cdev_irb->esw.esw0.sublog; + sublog = &irb->esw.esw0.sublog; + /* Copy extended status flags. */ + cdev_sublog->esf = sublog->esf; + /* + * Copy fields that have a meaning for channel data check + * channel control check and interface control check. + */ + if (irb->scsw.cstat & (SCHN_STAT_CHN_DATA_CHK | + SCHN_STAT_CHN_CTRL_CHK | + SCHN_STAT_INTF_CTRL_CHK)) { + /* Copy ancillary report bit. */ + cdev_sublog->arep = sublog->arep; + /* Copy field-validity-flags. */ + cdev_sublog->fvf = sublog->fvf; + /* Copy storage access code. */ + cdev_sublog->sacc = sublog->sacc; + /* Copy termination code. */ + cdev_sublog->termc = sublog->termc; + /* Copy sequence code. */ + cdev_sublog->seqc = sublog->seqc; + } + /* Copy device status check. */ + cdev_sublog->devsc = sublog->devsc; + /* Copy secondary error. */ + cdev_sublog->serr = sublog->serr; + /* Copy i/o-error alert. */ + cdev_sublog->ioerr = sublog->ioerr; + /* Copy channel path timeout bit. */ + if (irb->scsw.cstat & SCHN_STAT_INTF_CTRL_CHK) + cdev_irb->esw.esw0.erw.cpt = irb->esw.esw0.erw.cpt; + /* Copy failing storage address validity flag. */ + cdev_irb->esw.esw0.erw.fsavf = irb->esw.esw0.erw.fsavf; + if (cdev_irb->esw.esw0.erw.fsavf) { + /* ... and copy the failing storage address. */ + memcpy(cdev_irb->esw.esw0.faddr, irb->esw.esw0.faddr, + sizeof (irb->esw.esw0.faddr)); + /* ... and copy the failing storage address format. */ + cdev_irb->esw.esw0.erw.fsaf = irb->esw.esw0.erw.fsaf; + } + /* Copy secondary ccw address validity bit. */ + cdev_irb->esw.esw0.erw.scavf = irb->esw.esw0.erw.scavf; + if (irb->esw.esw0.erw.scavf) + /* ... and copy the secondary ccw address. */ + cdev_irb->esw.esw0.saddr = irb->esw.esw0.saddr; + + } + /* FIXME: DCTI for format 2? */ + + /* Copy authorization bit. */ + cdev_irb->esw.esw0.erw.auth = irb->esw.esw0.erw.auth; + /* Copy path verification required flag. */ + cdev_irb->esw.esw0.erw.pvrf = irb->esw.esw0.erw.pvrf; + if (irb->esw.esw0.erw.pvrf && cdev->private->options.pgroup) + cdev->private->flags.doverify = 1; + /* Copy concurrent sense bit. */ + cdev_irb->esw.esw0.erw.cons = irb->esw.esw0.erw.cons; + if (irb->esw.esw0.erw.cons) + cdev_irb->esw.esw0.erw.scnt = irb->esw.esw0.erw.scnt; +} + +/* + * Accumulate status from irb to devstat. + */ +void +ccw_device_accumulate_irb(struct ccw_device *cdev, struct irb *irb) +{ + struct irb *cdev_irb; + + /* + * Check if the status pending bit is set in stctl. + * If not, the remaining bit have no meaning and we must ignore them. + * The esw is not meaningful as well... + */ + if (!(irb->scsw.stctl & SCSW_STCTL_STATUS_PEND)) + return; + + /* Check for channel checks and interface control checks. */ + ccw_device_msg_control_check(cdev, irb); + + /* Check for path not operational. */ + if (irb->scsw.pno && irb->scsw.fctl != 0 && + (!(irb->scsw.stctl & SCSW_STCTL_INTER_STATUS) || + (irb->scsw.actl & SCSW_ACTL_SUSPENDED))) + ccw_device_path_notoper(cdev); + + /* + * Don't accumulate unsolicited interrupts. + */ + if ((irb->scsw.stctl == + (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) && + (!irb->scsw.cc)) + return; + + cdev_irb = &cdev->private->irb; + + /* Copy bits which are valid only for the start function. */ + if (irb->scsw.fctl & SCSW_FCTL_START_FUNC) { + /* Copy key. */ + cdev_irb->scsw.key = irb->scsw.key; + /* Copy suspend control bit. */ + cdev_irb->scsw.sctl = irb->scsw.sctl; + /* Accumulate deferred condition code. */ + cdev_irb->scsw.cc |= irb->scsw.cc; + /* Copy ccw format bit. */ + cdev_irb->scsw.fmt = irb->scsw.fmt; + /* Copy prefetch bit. */ + cdev_irb->scsw.pfch = irb->scsw.pfch; + /* Copy initial-status-interruption-control. */ + cdev_irb->scsw.isic = irb->scsw.isic; + /* Copy address limit checking control. */ + cdev_irb->scsw.alcc = irb->scsw.alcc; + /* Copy suppress suspend bit. */ + cdev_irb->scsw.ssi = irb->scsw.ssi; + } + + /* Take care of the extended control bit and extended control word. */ + ccw_device_accumulate_ecw(cdev, irb); + + /* Accumulate function control. */ + cdev_irb->scsw.fctl |= irb->scsw.fctl; + /* Copy activity control. */ + cdev_irb->scsw.actl= irb->scsw.actl; + /* Accumulate status control. */ + cdev_irb->scsw.stctl |= irb->scsw.stctl; + /* + * Copy ccw address if it is valid. This is a bit simplified + * but should be close enough for all practical purposes. + */ + if ((irb->scsw.stctl & SCSW_STCTL_PRIM_STATUS) || + ((irb->scsw.stctl == + (SCSW_STCTL_INTER_STATUS|SCSW_STCTL_STATUS_PEND)) && + (irb->scsw.actl & SCSW_ACTL_DEVACT) && + (irb->scsw.actl & SCSW_ACTL_SCHACT)) || + (irb->scsw.actl & SCSW_ACTL_SUSPENDED)) + cdev_irb->scsw.cpa = irb->scsw.cpa; + /* Accumulate device status, but not the device busy flag. */ + cdev_irb->scsw.dstat &= ~DEV_STAT_BUSY; + cdev_irb->scsw.dstat |= irb->scsw.dstat; + /* Accumulate subchannel status. */ + cdev_irb->scsw.cstat |= irb->scsw.cstat; + /* Copy residual count if it is valid. */ + if ((irb->scsw.stctl & SCSW_STCTL_PRIM_STATUS) && + (irb->scsw.cstat & ~(SCHN_STAT_PCI | SCHN_STAT_INCORR_LEN)) == 0) + cdev_irb->scsw.count = irb->scsw.count; + + /* Take care of bits in the extended status word. */ + ccw_device_accumulate_esw(cdev, irb); + + /* + * Check whether we must issue a SENSE CCW ourselves if there is no + * concurrent sense facility installed for the subchannel. + * No sense is required if no delayed sense is pending + * and we did not get a unit check without sense information. + * + * Note: We should check for ioinfo[irq]->flags.consns but VM + * violates the ESA/390 architecture and doesn't present an + * operand exception for virtual devices without concurrent + * sense facility available/supported when enabling the + * concurrent sense facility. + */ + if ((cdev_irb->scsw.dstat & DEV_STAT_UNIT_CHECK) && + !(cdev_irb->esw.esw0.erw.cons)) + cdev->private->flags.dosense = 1; +} + +/* + * Do a basic sense. + */ +int +ccw_device_do_sense(struct ccw_device *cdev, struct irb *irb) +{ + struct subchannel *sch; + + sch = to_subchannel(cdev->dev.parent); + + /* A sense is required, can we do it now ? */ + if ((irb->scsw.actl & (SCSW_ACTL_DEVACT | SCSW_ACTL_SCHACT)) != 0) + /* + * we received an Unit Check but we have no final + * status yet, therefore we must delay the SENSE + * processing. We must not report this intermediate + * status to the device interrupt handler. + */ + return -EBUSY; + + /* + * We have ending status but no sense information. Do a basic sense. + */ + sch = to_subchannel(cdev->dev.parent); + sch->sense_ccw.cmd_code = CCW_CMD_BASIC_SENSE; + sch->sense_ccw.cda = (__u32) __pa(cdev->private->irb.ecw); + sch->sense_ccw.count = SENSE_MAX_COUNT; + sch->sense_ccw.flags = CCW_FLAG_SLI; + + return cio_start (sch, &sch->sense_ccw, 0xff); +} + +/* + * Add information from basic sense to devstat. + */ +void +ccw_device_accumulate_basic_sense(struct ccw_device *cdev, struct irb *irb) +{ + /* + * Check if the status pending bit is set in stctl. + * If not, the remaining bit have no meaning and we must ignore them. + * The esw is not meaningful as well... + */ + if (!(irb->scsw.stctl & SCSW_STCTL_STATUS_PEND)) + return; + + /* Check for channel checks and interface control checks. */ + ccw_device_msg_control_check(cdev, irb); + + /* Check for path not operational. */ + if (irb->scsw.pno && irb->scsw.fctl != 0 && + (!(irb->scsw.stctl & SCSW_STCTL_INTER_STATUS) || + (irb->scsw.actl & SCSW_ACTL_SUSPENDED))) + ccw_device_path_notoper(cdev); + + if (!(irb->scsw.dstat & DEV_STAT_UNIT_CHECK) && + (irb->scsw.dstat & DEV_STAT_CHN_END)) { + cdev->private->irb.esw.esw0.erw.cons = 1; + cdev->private->flags.dosense = 0; + } + /* Check if path verification is required. */ + if (ccw_device_accumulate_esw_valid(irb) && + irb->esw.esw0.erw.pvrf && cdev->private->options.pgroup) + cdev->private->flags.doverify = 1; +} + +/* + * This function accumulates the status into the private devstat and + * starts a basic sense if one is needed. + */ +int +ccw_device_accumulate_and_sense(struct ccw_device *cdev, struct irb *irb) +{ + ccw_device_accumulate_irb(cdev, irb); + if ((irb->scsw.actl & (SCSW_ACTL_DEVACT | SCSW_ACTL_SCHACT)) != 0) + return -EBUSY; + /* Check for basic sense. */ + if (cdev->private->flags.dosense && + !(irb->scsw.dstat & DEV_STAT_UNIT_CHECK)) { + cdev->private->irb.esw.esw0.erw.cons = 1; + cdev->private->flags.dosense = 0; + return 0; + } + if (cdev->private->flags.dosense) { + ccw_device_do_sense(cdev, irb); + return -EBUSY; + } + return 0; +} + diff --git a/drivers/s390/cio/ioasm.h b/drivers/s390/cio/ioasm.h new file mode 100644 index 000000000000..c874607d9a80 --- /dev/null +++ b/drivers/s390/cio/ioasm.h @@ -0,0 +1,228 @@ +#ifndef S390_CIO_IOASM_H +#define S390_CIO_IOASM_H + +/* + * TPI info structure + */ +struct tpi_info { + __u32 reserved1 : 16; /* reserved 0x00000001 */ + __u32 irq : 16; /* aka. subchannel number */ + __u32 intparm; /* interruption parameter */ + __u32 adapter_IO : 1; + __u32 reserved2 : 1; + __u32 isc : 3; + __u32 reserved3 : 12; + __u32 int_type : 3; + __u32 reserved4 : 12; +} __attribute__ ((packed)); + + +/* + * Some S390 specific IO instructions as inline + */ + +extern __inline__ int stsch(int irq, volatile struct schib *addr) +{ + int ccode; + + __asm__ __volatile__( + " lr 1,%1\n" + " stsch 0(%2)\n" + " ipm %0\n" + " srl %0,28" + : "=d" (ccode) + : "d" (irq | 0x10000), "a" (addr) + : "cc", "1" ); + return ccode; +} + +extern __inline__ int msch(int irq, volatile struct schib *addr) +{ + int ccode; + + __asm__ __volatile__( + " lr 1,%1\n" + " msch 0(%2)\n" + " ipm %0\n" + " srl %0,28" + : "=d" (ccode) + : "d" (irq | 0x10000L), "a" (addr) + : "cc", "1" ); + return ccode; +} + +extern __inline__ int msch_err(int irq, volatile struct schib *addr) +{ + int ccode; + + __asm__ __volatile__( + " lhi %0,%3\n" + " lr 1,%1\n" + " msch 0(%2)\n" + "0: ipm %0\n" + " srl %0,28\n" + "1:\n" +#ifdef CONFIG_ARCH_S390X + ".section __ex_table,\"a\"\n" + " .align 8\n" + " .quad 0b,1b\n" + ".previous" +#else + ".section __ex_table,\"a\"\n" + " .align 4\n" + " .long 0b,1b\n" + ".previous" +#endif + : "=&d" (ccode) + : "d" (irq | 0x10000L), "a" (addr), "K" (-EIO) + : "cc", "1" ); + return ccode; +} + +extern __inline__ int tsch(int irq, volatile struct irb *addr) +{ + int ccode; + + __asm__ __volatile__( + " lr 1,%1\n" + " tsch 0(%2)\n" + " ipm %0\n" + " srl %0,28" + : "=d" (ccode) + : "d" (irq | 0x10000L), "a" (addr) + : "cc", "1" ); + return ccode; +} + +extern __inline__ int tpi( volatile struct tpi_info *addr) +{ + int ccode; + + __asm__ __volatile__( + " tpi 0(%1)\n" + " ipm %0\n" + " srl %0,28" + : "=d" (ccode) + : "a" (addr) + : "cc", "1" ); + return ccode; +} + +extern __inline__ int ssch(int irq, volatile struct orb *addr) +{ + int ccode; + + __asm__ __volatile__( + " lr 1,%1\n" + " ssch 0(%2)\n" + " ipm %0\n" + " srl %0,28" + : "=d" (ccode) + : "d" (irq | 0x10000L), "a" (addr) + : "cc", "1" ); + return ccode; +} + +extern __inline__ int rsch(int irq) +{ + int ccode; + + __asm__ __volatile__( + " lr 1,%1\n" + " rsch\n" + " ipm %0\n" + " srl %0,28" + : "=d" (ccode) + : "d" (irq | 0x10000L) + : "cc", "1" ); + return ccode; +} + +extern __inline__ int csch(int irq) +{ + int ccode; + + __asm__ __volatile__( + " lr 1,%1\n" + " csch\n" + " ipm %0\n" + " srl %0,28" + : "=d" (ccode) + : "d" (irq | 0x10000L) + : "cc", "1" ); + return ccode; +} + +extern __inline__ int hsch(int irq) +{ + int ccode; + + __asm__ __volatile__( + " lr 1,%1\n" + " hsch\n" + " ipm %0\n" + " srl %0,28" + : "=d" (ccode) + : "d" (irq | 0x10000L) + : "cc", "1" ); + return ccode; +} + +extern __inline__ int xsch(int irq) +{ + int ccode; + + __asm__ __volatile__( + " lr 1,%1\n" + " .insn rre,0xb2760000,%1,0\n" + " ipm %0\n" + " srl %0,28" + : "=d" (ccode) + : "d" (irq | 0x10000L) + : "cc", "1" ); + return ccode; +} + +extern __inline__ int chsc(void *chsc_area) +{ + int cc; + + __asm__ __volatile__ ( + ".insn rre,0xb25f0000,%1,0 \n\t" + "ipm %0 \n\t" + "srl %0,28 \n\t" + : "=d" (cc) + : "d" (chsc_area) + : "cc" ); + + return cc; +} + +extern __inline__ int iac( void) +{ + int ccode; + + __asm__ __volatile__( + " iac 1\n" + " ipm %0\n" + " srl %0,28" + : "=d" (ccode) : : "cc", "1" ); + return ccode; +} + +extern __inline__ int rchp(int chpid) +{ + int ccode; + + __asm__ __volatile__( + " lr 1,%1\n" + " rchp\n" + " ipm %0\n" + " srl %0,28" + : "=d" (ccode) + : "d" (chpid) + : "cc", "1" ); + return ccode; +} + +#endif diff --git a/drivers/s390/cio/qdio.c b/drivers/s390/cio/qdio.c new file mode 100644 index 000000000000..bbe9f45d1438 --- /dev/null +++ b/drivers/s390/cio/qdio.c @@ -0,0 +1,3468 @@ +/* + * + * linux/drivers/s390/cio/qdio.c + * + * Linux for S/390 QDIO base support, Hipersocket base support + * version 2 + * + * Copyright 2000,2002 IBM Corporation + * Author(s): Utz Bacher <utz.bacher@de.ibm.com> + * 2.6 cio integration by Cornelia Huck <cohuck@de.ibm.com> + * + * Restriction: only 63 iqdio subchannels would have its own indicator, + * after that, subsequent subchannels share one indicator + * + * + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/init.h> + +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/proc_fs.h> +#include <linux/timer.h> + +#include <asm/ccwdev.h> +#include <asm/io.h> +#include <asm/atomic.h> +#include <asm/semaphore.h> +#include <asm/timex.h> + +#include <asm/debug.h> +#include <asm/qdio.h> + +#include "cio.h" +#include "css.h" +#include "device.h" +#include "airq.h" +#include "qdio.h" +#include "ioasm.h" +#include "chsc.h" + +#define VERSION_QDIO_C "$Revision: 1.98 $" + +/****************** MODULE PARAMETER VARIABLES ********************/ +MODULE_AUTHOR("Utz Bacher <utz.bacher@de.ibm.com>"); +MODULE_DESCRIPTION("QDIO base support version 2, " \ + "Copyright 2000 IBM Corporation"); +MODULE_LICENSE("GPL"); + +/******************** HERE WE GO ***********************************/ + +static const char version[] = "QDIO base support version 2 (" + VERSION_QDIO_C "/" VERSION_QDIO_H "/" VERSION_CIO_QDIO_H ")"; + +#ifdef QDIO_PERFORMANCE_STATS +static int proc_perf_file_registration; +static unsigned long i_p_c, i_p_nc, o_p_c, o_p_nc, ii_p_c, ii_p_nc; +static struct qdio_perf_stats perf_stats; +#endif /* QDIO_PERFORMANCE_STATS */ + +static int hydra_thinints; +static int omit_svs; + +static int indicator_used[INDICATORS_PER_CACHELINE]; +static __u32 * volatile indicators; +static __u32 volatile spare_indicator; +static atomic_t spare_indicator_usecount; + +static debug_info_t *qdio_dbf_setup; +static debug_info_t *qdio_dbf_sbal; +static debug_info_t *qdio_dbf_trace; +static debug_info_t *qdio_dbf_sense; +#ifdef CONFIG_QDIO_DEBUG +static debug_info_t *qdio_dbf_slsb_out; +static debug_info_t *qdio_dbf_slsb_in; +#endif /* CONFIG_QDIO_DEBUG */ + +/* iQDIO stuff: */ +static volatile struct qdio_q *tiq_list=NULL; /* volatile as it could change + during a while loop */ +static DEFINE_SPINLOCK(ttiq_list_lock); +static int register_thinint_result; +static void tiqdio_tl(unsigned long); +static DECLARE_TASKLET(tiqdio_tasklet,tiqdio_tl,0); + +/* not a macro, as one of the arguments is atomic_read */ +static inline int +qdio_min(int a,int b) +{ + if (a<b) + return a; + else + return b; +} + +/***************** SCRUBBER HELPER ROUTINES **********************/ + +static inline volatile __u64 +qdio_get_micros(void) +{ + return (get_clock() >> 10); /* time>>12 is microseconds */ +} + +/* + * unfortunately, we can't just xchg the values; in do_QDIO we want to reserve + * the q in any case, so that we'll not be interrupted when we are in + * qdio_mark_tiq... shouldn't have a really bad impact, as reserving almost + * ever works (last famous words) + */ +static inline int +qdio_reserve_q(struct qdio_q *q) +{ + return atomic_add_return(1,&q->use_count) - 1; +} + +static inline void +qdio_release_q(struct qdio_q *q) +{ + atomic_dec(&q->use_count); +} + +static volatile inline void +qdio_set_slsb(volatile char *slsb, unsigned char value) +{ + xchg((char*)slsb,value); +} + +static inline int +qdio_siga_sync(struct qdio_q *q, unsigned int gpr2, + unsigned int gpr3) +{ + int cc; + + QDIO_DBF_TEXT4(0,trace,"sigasync"); + QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); + +#ifdef QDIO_PERFORMANCE_STATS + perf_stats.siga_syncs++; +#endif /* QDIO_PERFORMANCE_STATS */ + + cc = do_siga_sync(q->irq, gpr2, gpr3); + if (cc) + QDIO_DBF_HEX3(0,trace,&cc,sizeof(int*)); + + return cc; +} + +static inline int +qdio_siga_sync_q(struct qdio_q *q) +{ + if (q->is_input_q) + return qdio_siga_sync(q, 0, q->mask); + return qdio_siga_sync(q, q->mask, 0); +} + +/* + * returns QDIO_SIGA_ERROR_ACCESS_EXCEPTION as cc, when SIGA returns + * an access exception + */ +static inline int +qdio_siga_output(struct qdio_q *q) +{ + int cc; + __u32 busy_bit; + __u64 start_time=0; + +#ifdef QDIO_PERFORMANCE_STATS + perf_stats.siga_outs++; +#endif /* QDIO_PERFORMANCE_STATS */ + + QDIO_DBF_TEXT4(0,trace,"sigaout"); + QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); + + for (;;) { + cc = do_siga_output(q->irq, q->mask, &busy_bit); +//QDIO_PRINT_ERR("cc=%x, busy=%x\n",cc,busy_bit); + if ((cc==2) && (busy_bit) && (q->is_iqdio_q)) { + if (!start_time) + start_time=NOW; + if ((NOW-start_time)>QDIO_BUSY_BIT_PATIENCE) + break; + } else + break; + } + + if ((cc==2) && (busy_bit)) + cc |= QDIO_SIGA_ERROR_B_BIT_SET; + + if (cc) + QDIO_DBF_HEX3(0,trace,&cc,sizeof(int*)); + + return cc; +} + +static inline int +qdio_siga_input(struct qdio_q *q) +{ + int cc; + + QDIO_DBF_TEXT4(0,trace,"sigain"); + QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); + +#ifdef QDIO_PERFORMANCE_STATS + perf_stats.siga_ins++; +#endif /* QDIO_PERFORMANCE_STATS */ + + cc = do_siga_input(q->irq, q->mask); + + if (cc) + QDIO_DBF_HEX3(0,trace,&cc,sizeof(int*)); + + return cc; +} + +/* locked by the locks in qdio_activate and qdio_cleanup */ +static __u32 * volatile +qdio_get_indicator(void) +{ + int i; + + for (i=1;i<INDICATORS_PER_CACHELINE;i++) + if (!indicator_used[i]) { + indicator_used[i]=1; + return indicators+i; + } + atomic_inc(&spare_indicator_usecount); + return (__u32 * volatile) &spare_indicator; +} + +/* locked by the locks in qdio_activate and qdio_cleanup */ +static void +qdio_put_indicator(__u32 *addr) +{ + int i; + + if ( (addr) && (addr!=&spare_indicator) ) { + i=addr-indicators; + indicator_used[i]=0; + } + if (addr == &spare_indicator) + atomic_dec(&spare_indicator_usecount); +} + +static inline volatile void +tiqdio_clear_summary_bit(__u32 *location) +{ + QDIO_DBF_TEXT5(0,trace,"clrsummb"); + QDIO_DBF_HEX5(0,trace,&location,sizeof(void*)); + + xchg(location,0); +} + +static inline volatile void +tiqdio_set_summary_bit(__u32 *location) +{ + QDIO_DBF_TEXT5(0,trace,"setsummb"); + QDIO_DBF_HEX5(0,trace,&location,sizeof(void*)); + + xchg(location,-1); +} + +static inline void +tiqdio_sched_tl(void) +{ + tasklet_hi_schedule(&tiqdio_tasklet); +} + +static inline void +qdio_mark_tiq(struct qdio_q *q) +{ + unsigned long flags; + + QDIO_DBF_TEXT4(0,trace,"mark iq"); + QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); + + spin_lock_irqsave(&ttiq_list_lock,flags); + if (unlikely(atomic_read(&q->is_in_shutdown))) + goto out_unlock; + + if (!q->is_input_q) + goto out_unlock; + + if ((q->list_prev) || (q->list_next)) + goto out_unlock; + + if (!tiq_list) { + tiq_list=q; + q->list_prev=q; + q->list_next=q; + } else { + q->list_next=tiq_list; + q->list_prev=tiq_list->list_prev; + tiq_list->list_prev->list_next=q; + tiq_list->list_prev=q; + } + spin_unlock_irqrestore(&ttiq_list_lock,flags); + + tiqdio_set_summary_bit((__u32*)q->dev_st_chg_ind); + tiqdio_sched_tl(); + return; +out_unlock: + spin_unlock_irqrestore(&ttiq_list_lock,flags); + return; +} + +static inline void +qdio_mark_q(struct qdio_q *q) +{ + QDIO_DBF_TEXT4(0,trace,"mark q"); + QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); + + if (unlikely(atomic_read(&q->is_in_shutdown))) + return; + + tasklet_schedule(&q->tasklet); +} + +static inline int +qdio_stop_polling(struct qdio_q *q) +{ +#ifdef QDIO_USE_PROCESSING_STATE + int gsf; + + if (!atomic_swap(&q->polling,0)) + return 1; + + QDIO_DBF_TEXT4(0,trace,"stoppoll"); + QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); + + /* show the card that we are not polling anymore */ + if (!q->is_input_q) + return 1; + + gsf=GET_SAVED_FRONTIER(q); + set_slsb(&q->slsb.acc.val[(gsf+QDIO_MAX_BUFFERS_PER_Q-1)& + (QDIO_MAX_BUFFERS_PER_Q-1)], + SLSB_P_INPUT_NOT_INIT); + /* + * we don't issue this SYNC_MEMORY, as we trust Rick T and + * moreover will not use the PROCESSING state under VM, so + * q->polling was 0 anyway + */ + /*SYNC_MEMORY;*/ + if (q->slsb.acc.val[gsf]!=SLSB_P_INPUT_PRIMED) + return 1; + /* + * set our summary bit again, as otherwise there is a + * small window we can miss between resetting it and + * checking for PRIMED state + */ + if (q->is_thinint_q) + tiqdio_set_summary_bit((__u32*)q->dev_st_chg_ind); + return 0; + +#else /* QDIO_USE_PROCESSING_STATE */ + return 1; +#endif /* QDIO_USE_PROCESSING_STATE */ +} + +/* + * see the comment in do_QDIO and before qdio_reserve_q about the + * sophisticated locking outside of unmark_q, so that we don't need to + * disable the interrupts :-) +*/ +static inline void +qdio_unmark_q(struct qdio_q *q) +{ + unsigned long flags; + + QDIO_DBF_TEXT4(0,trace,"unmark q"); + QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); + + if ((!q->list_prev)||(!q->list_next)) + return; + + 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; + q->list_next=NULL; + q->list_prev=NULL; + } else { + q->list_next->list_prev=q->list_prev; + q->list_prev->list_next=q->list_next; + tiq_list=q->list_next; + q->list_next=NULL; + q->list_prev=NULL; + } +out: + spin_unlock_irqrestore(&ttiq_list_lock,flags); + } +} + +static inline unsigned long +tiqdio_clear_global_summary(void) +{ + unsigned long time; + + QDIO_DBF_TEXT5(0,trace,"clrglobl"); + + time = do_clear_global_summary(); + + QDIO_DBF_HEX5(0,trace,&time,sizeof(unsigned long)); + + return time; +} + + +/************************* OUTBOUND ROUTINES *******************************/ + +inline static int +qdio_get_outbound_buffer_frontier(struct qdio_q *q) +{ + int f,f_mod_no; + volatile char *slsb; + int first_not_to_check; + char dbf_text[15]; + + QDIO_DBF_TEXT4(0,trace,"getobfro"); + QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); + + slsb=&q->slsb.acc.val[0]; + f_mod_no=f=q->first_to_check; + /* + * f points to already processed elements, so f+no_used is correct... + * ... but: we don't check 128 buffers, as otherwise + * qdio_has_outbound_q_moved would return 0 + */ + first_not_to_check=f+qdio_min(atomic_read(&q->number_of_buffers_used), + (QDIO_MAX_BUFFERS_PER_Q-1)); + + if ((!q->is_iqdio_q)&&(!q->hydra_gives_outbound_pcis)) + SYNC_MEMORY; + +check_next: + if (f==first_not_to_check) + goto out; + + switch(slsb[f_mod_no]) { + + /* the adapter has not fetched the output yet */ + case SLSB_CU_OUTPUT_PRIMED: + QDIO_DBF_TEXT5(0,trace,"outpprim"); + break; + + /* the adapter got it */ + case SLSB_P_OUTPUT_EMPTY: + atomic_dec(&q->number_of_buffers_used); + f++; + f_mod_no=f&(QDIO_MAX_BUFFERS_PER_Q-1); + QDIO_DBF_TEXT5(0,trace,"outpempt"); + goto check_next; + + case SLSB_P_OUTPUT_ERROR: + QDIO_DBF_TEXT3(0,trace,"outperr"); + sprintf(dbf_text,"%x-%x-%x",f_mod_no, + q->sbal[f_mod_no]->element[14].sbalf.value, + q->sbal[f_mod_no]->element[15].sbalf.value); + QDIO_DBF_TEXT3(1,trace,dbf_text); + QDIO_DBF_HEX2(1,sbal,q->sbal[f_mod_no],256); + + /* kind of process the buffer */ + set_slsb(&q->slsb.acc.val[f_mod_no], SLSB_P_OUTPUT_NOT_INIT); + + /* + * we increment the frontier, as this buffer + * was processed obviously + */ + atomic_dec(&q->number_of_buffers_used); + f_mod_no=(f_mod_no+1)&(QDIO_MAX_BUFFERS_PER_Q-1); + + if (q->qdio_error) + q->error_status_flags|= + QDIO_STATUS_MORE_THAN_ONE_QDIO_ERROR; + q->qdio_error=SLSB_P_OUTPUT_ERROR; + q->error_status_flags|=QDIO_STATUS_LOOK_FOR_ERROR; + + break; + + /* no new buffers */ + default: + QDIO_DBF_TEXT5(0,trace,"outpni"); + } +out: + return (q->first_to_check=f_mod_no); +} + +/* all buffers are processed */ +inline static int +qdio_is_outbound_q_done(struct qdio_q *q) +{ + int no_used; +#ifdef CONFIG_QDIO_DEBUG + char dbf_text[15]; +#endif + + no_used=atomic_read(&q->number_of_buffers_used); + +#ifdef CONFIG_QDIO_DEBUG + if (no_used) { + sprintf(dbf_text,"oqisnt%02x",no_used); + QDIO_DBF_TEXT4(0,trace,dbf_text); + } else { + QDIO_DBF_TEXT4(0,trace,"oqisdone"); + } + QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); +#endif /* CONFIG_QDIO_DEBUG */ + return (no_used==0); +} + +inline static int +qdio_has_outbound_q_moved(struct qdio_q *q) +{ + int i; + + i=qdio_get_outbound_buffer_frontier(q); + + if ( (i!=GET_SAVED_FRONTIER(q)) || + (q->error_status_flags&QDIO_STATUS_LOOK_FOR_ERROR) ) { + SAVE_FRONTIER(q,i); + QDIO_DBF_TEXT4(0,trace,"oqhasmvd"); + QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); + return 1; + } else { + QDIO_DBF_TEXT4(0,trace,"oqhsntmv"); + QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); + return 0; + } +} + +inline static void +qdio_kick_outbound_q(struct qdio_q *q) +{ + int result; +#ifdef CONFIG_QDIO_DEBUG + char dbf_text[15]; + + QDIO_DBF_TEXT4(0,trace,"kickoutq"); + QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); +#endif /* CONFIG_QDIO_DEBUG */ + + if (!q->siga_out) + return; + + /* here's the story with cc=2 and busy bit set (thanks, Rick): + * VM's CP could present us cc=2 and busy bit set on SIGA-write + * during reconfiguration of their Guest LAN (only in HIPERS mode, + * QDIO mode is asynchronous -- cc=2 and busy bit there will take + * the queues down immediately; and not being under VM we have a + * problem on cc=2 and busy bit set right away). + * + * Therefore qdio_siga_output will try for a short time constantly, + * if such a condition occurs. If it doesn't change, it will + * increase the busy_siga_counter and save the timestamp, and + * schedule the queue for later processing (via mark_q, using the + * queue tasklet). __qdio_outbound_processing will check out the + * counter. If non-zero, it will call qdio_kick_outbound_q as often + * as the value of the counter. This will attempt further SIGA + * instructions. For each successful SIGA, the counter is + * decreased, for failing SIGAs the counter remains the same, after + * all. + * After some time of no movement, qdio_kick_outbound_q will + * finally fail and reflect corresponding error codes to call + * the upper layer module and have it take the queues down. + * + * Note that this is a change from the original HiperSockets design + * (saying cc=2 and busy bit means take the queues down), but in + * these days Guest LAN didn't exist... excessive cc=2 with busy bit + * conditions will still take the queues down, but the threshold is + * higher due to the Guest LAN environment. + */ + + + result=qdio_siga_output(q); + + switch (result) { + case 0: + /* went smooth this time, reset timestamp */ +#ifdef CONFIG_QDIO_DEBUG + QDIO_DBF_TEXT3(0,trace,"cc2reslv"); + sprintf(dbf_text,"%4x%2x%2x",q->irq,q->q_no, + atomic_read(&q->busy_siga_counter)); + QDIO_DBF_TEXT3(0,trace,dbf_text); +#endif /* CONFIG_QDIO_DEBUG */ + q->timing.busy_start=0; + break; + case (2|QDIO_SIGA_ERROR_B_BIT_SET): + /* cc=2 and busy bit: */ + atomic_inc(&q->busy_siga_counter); + + /* if the last siga was successful, save + * timestamp here */ + if (!q->timing.busy_start) + q->timing.busy_start=NOW; + + /* if we're in time, don't touch error_status_flags + * and siga_error */ + if (NOW-q->timing.busy_start<QDIO_BUSY_BIT_GIVE_UP) { + qdio_mark_q(q); + break; + } + QDIO_DBF_TEXT2(0,trace,"cc2REPRT"); +#ifdef CONFIG_QDIO_DEBUG + sprintf(dbf_text,"%4x%2x%2x",q->irq,q->q_no, + atomic_read(&q->busy_siga_counter)); + QDIO_DBF_TEXT3(0,trace,dbf_text); +#endif /* CONFIG_QDIO_DEBUG */ + /* else fallthrough and report error */ + default: + /* for plain cc=1, 2 or 3: */ + if (q->siga_error) + q->error_status_flags|= + QDIO_STATUS_MORE_THAN_ONE_SIGA_ERROR; + q->error_status_flags|= + QDIO_STATUS_LOOK_FOR_ERROR; + q->siga_error=result; + } +} + +inline static void +qdio_kick_outbound_handler(struct qdio_q *q) +{ + int start, end, real_end, count; +#ifdef CONFIG_QDIO_DEBUG + char dbf_text[15]; +#endif + + start = q->first_element_to_kick; + /* last_move_ftc was just updated */ + real_end = GET_SAVED_FRONTIER(q); + end = (real_end+QDIO_MAX_BUFFERS_PER_Q-1)& + (QDIO_MAX_BUFFERS_PER_Q-1); + count = (end+QDIO_MAX_BUFFERS_PER_Q+1-start)& + (QDIO_MAX_BUFFERS_PER_Q-1); + +#ifdef CONFIG_QDIO_DEBUG + QDIO_DBF_TEXT4(0,trace,"kickouth"); + QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); + + sprintf(dbf_text,"s=%2xc=%2x",start,count); + QDIO_DBF_TEXT4(0,trace,dbf_text); +#endif /* CONFIG_QDIO_DEBUG */ + + if (q->state==QDIO_IRQ_STATE_ACTIVE) + q->handler(q->cdev,QDIO_STATUS_OUTBOUND_INT| + q->error_status_flags, + q->qdio_error,q->siga_error,q->q_no,start,count, + q->int_parm); + + /* for the next time: */ + q->first_element_to_kick=real_end; + q->qdio_error=0; + q->siga_error=0; + q->error_status_flags=0; +} + +static inline void +__qdio_outbound_processing(struct qdio_q *q) +{ + int siga_attempts; + + QDIO_DBF_TEXT4(0,trace,"qoutproc"); + QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); + + if (unlikely(qdio_reserve_q(q))) { + qdio_release_q(q); +#ifdef QDIO_PERFORMANCE_STATS + o_p_c++; +#endif /* QDIO_PERFORMANCE_STATS */ + /* as we're sissies, we'll check next time */ + if (likely(!atomic_read(&q->is_in_shutdown))) { + qdio_mark_q(q); + QDIO_DBF_TEXT4(0,trace,"busy,agn"); + } + return; + } +#ifdef QDIO_PERFORMANCE_STATS + o_p_nc++; + perf_stats.tl_runs++; +#endif /* QDIO_PERFORMANCE_STATS */ + + /* see comment in qdio_kick_outbound_q */ + siga_attempts=atomic_read(&q->busy_siga_counter); + while (siga_attempts) { + atomic_dec(&q->busy_siga_counter); + qdio_kick_outbound_q(q); + siga_attempts--; + } + + if (qdio_has_outbound_q_moved(q)) + qdio_kick_outbound_handler(q); + + if (q->is_iqdio_q) { + /* + * for asynchronous queues, we better check, if the fill + * level is too high. for synchronous queues, the fill + * level will never be that high. + */ + if (atomic_read(&q->number_of_buffers_used)> + IQDIO_FILL_LEVEL_TO_POLL) + qdio_mark_q(q); + + } else if (!q->hydra_gives_outbound_pcis) + if (!qdio_is_outbound_q_done(q)) + qdio_mark_q(q); + + qdio_release_q(q); +} + +static void +qdio_outbound_processing(struct qdio_q *q) +{ + __qdio_outbound_processing(q); +} + +/************************* INBOUND ROUTINES *******************************/ + + +inline static int +qdio_get_inbound_buffer_frontier(struct qdio_q *q) +{ + int f,f_mod_no; + volatile char *slsb; + int first_not_to_check; +#ifdef CONFIG_QDIO_DEBUG + char dbf_text[15]; +#endif /* CONFIG_QDIO_DEBUG */ +#ifdef QDIO_USE_PROCESSING_STATE + int last_position=-1; +#endif /* QDIO_USE_PROCESSING_STATE */ + + QDIO_DBF_TEXT4(0,trace,"getibfro"); + QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); + + slsb=&q->slsb.acc.val[0]; + f_mod_no=f=q->first_to_check; + /* + * we don't check 128 buffers, as otherwise qdio_has_inbound_q_moved + * would return 0 + */ + first_not_to_check=f+qdio_min(atomic_read(&q->number_of_buffers_used), + (QDIO_MAX_BUFFERS_PER_Q-1)); + + /* + * we don't use this one, as a PCI or we after a thin interrupt + * will sync the queues + */ + /* SYNC_MEMORY;*/ + +check_next: + f_mod_no=f&(QDIO_MAX_BUFFERS_PER_Q-1); + if (f==first_not_to_check) + goto out; + switch (slsb[f_mod_no]) { + + /* CU_EMPTY means frontier is reached */ + case SLSB_CU_INPUT_EMPTY: + QDIO_DBF_TEXT5(0,trace,"inptempt"); + break; + + /* P_PRIMED means set slsb to P_PROCESSING and move on */ + case SLSB_P_INPUT_PRIMED: + QDIO_DBF_TEXT5(0,trace,"inptprim"); + +#ifdef QDIO_USE_PROCESSING_STATE + /* + * as soon as running under VM, polling the input queues will + * kill VM in terms of CP overhead + */ + if (q->siga_sync) { + set_slsb(&slsb[f_mod_no],SLSB_P_INPUT_NOT_INIT); + } else { + /* set the previous buffer to NOT_INIT. The current + * buffer will be set to PROCESSING at the end of + * this function to avoid further interrupts. */ + if (last_position>=0) + set_slsb(&slsb[last_position], + SLSB_P_INPUT_NOT_INIT); + atomic_set(&q->polling,1); + last_position=f_mod_no; + } +#else /* QDIO_USE_PROCESSING_STATE */ + set_slsb(&slsb[f_mod_no],SLSB_P_INPUT_NOT_INIT); +#endif /* QDIO_USE_PROCESSING_STATE */ + /* + * not needed, as the inbound queue will be synced on the next + * siga-r, resp. tiqdio_is_inbound_q_done will do the siga-s + */ + /*SYNC_MEMORY;*/ + f++; + atomic_dec(&q->number_of_buffers_used); + goto check_next; + + case SLSB_P_INPUT_NOT_INIT: + case SLSB_P_INPUT_PROCESSING: + QDIO_DBF_TEXT5(0,trace,"inpnipro"); + break; + + /* P_ERROR means frontier is reached, break and report error */ + case SLSB_P_INPUT_ERROR: +#ifdef CONFIG_QDIO_DEBUG + sprintf(dbf_text,"inperr%2x",f_mod_no); + QDIO_DBF_TEXT3(1,trace,dbf_text); +#endif /* CONFIG_QDIO_DEBUG */ + QDIO_DBF_HEX2(1,sbal,q->sbal[f_mod_no],256); + + /* kind of process the buffer */ + set_slsb(&slsb[f_mod_no],SLSB_P_INPUT_NOT_INIT); + + if (q->qdio_error) + q->error_status_flags|= + QDIO_STATUS_MORE_THAN_ONE_QDIO_ERROR; + q->qdio_error=SLSB_P_INPUT_ERROR; + q->error_status_flags|=QDIO_STATUS_LOOK_FOR_ERROR; + + /* we increment the frontier, as this buffer + * was processed obviously */ + f_mod_no=(f_mod_no+1)&(QDIO_MAX_BUFFERS_PER_Q-1); + atomic_dec(&q->number_of_buffers_used); + +#ifdef QDIO_USE_PROCESSING_STATE + last_position=-1; +#endif /* QDIO_USE_PROCESSING_STATE */ + + break; + + /* everything else means frontier not changed (HALTED or so) */ + default: + break; + } +out: + q->first_to_check=f_mod_no; + +#ifdef QDIO_USE_PROCESSING_STATE + if (last_position>=0) + set_slsb(&slsb[last_position],SLSB_P_INPUT_PROCESSING); +#endif /* QDIO_USE_PROCESSING_STATE */ + + QDIO_DBF_HEX4(0,trace,&q->first_to_check,sizeof(int)); + + return q->first_to_check; +} + +inline static int +qdio_has_inbound_q_moved(struct qdio_q *q) +{ + int i; + +#ifdef QDIO_PERFORMANCE_STATS + static int old_pcis=0; + static int old_thinints=0; + + if ((old_pcis==perf_stats.pcis)&&(old_thinints==perf_stats.thinints)) + perf_stats.start_time_inbound=NOW; + else + old_pcis=perf_stats.pcis; +#endif /* QDIO_PERFORMANCE_STATS */ + + i=qdio_get_inbound_buffer_frontier(q); + if ( (i!=GET_SAVED_FRONTIER(q)) || + (q->error_status_flags&QDIO_STATUS_LOOK_FOR_ERROR) ) { + SAVE_FRONTIER(q,i); + if ((!q->siga_sync)&&(!q->hydra_gives_outbound_pcis)) + SAVE_TIMESTAMP(q); + + QDIO_DBF_TEXT4(0,trace,"inhasmvd"); + QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); + return 1; + } else { + QDIO_DBF_TEXT4(0,trace,"inhsntmv"); + QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); + return 0; + } +} + +/* means, no more buffers to be filled */ +inline static int +tiqdio_is_inbound_q_done(struct qdio_q *q) +{ + int no_used; +#ifdef CONFIG_QDIO_DEBUG + char dbf_text[15]; +#endif + + no_used=atomic_read(&q->number_of_buffers_used); + + /* propagate the change from 82 to 80 through VM */ + SYNC_MEMORY; + +#ifdef CONFIG_QDIO_DEBUG + if (no_used) { + sprintf(dbf_text,"iqisnt%02x",no_used); + QDIO_DBF_TEXT4(0,trace,dbf_text); + } else { + QDIO_DBF_TEXT4(0,trace,"iniqisdo"); + } + QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); +#endif /* CONFIG_QDIO_DEBUG */ + + if (!no_used) + return 1; + + if (!q->siga_sync) + /* we'll check for more primed buffers in qeth_stop_polling */ + return 0; + + if (q->slsb.acc.val[q->first_to_check]!=SLSB_P_INPUT_PRIMED) + /* + * nothing more to do, if next buffer is not PRIMED. + * note that we did a SYNC_MEMORY before, that there + * has been a sychnronization. + * we will return 0 below, as there is nothing to do + * (stop_polling not necessary, as we have not been + * using the PROCESSING state + */ + return 0; + + /* + * ok, the next input buffer is primed. that means, that device state + * change indicator and adapter local summary are set, so we will find + * it next time. + * we will return 0 below, as there is nothing to do, except scheduling + * ourselves for the next time. + */ + tiqdio_set_summary_bit((__u32*)q->dev_st_chg_ind); + tiqdio_sched_tl(); + return 0; +} + +inline static int +qdio_is_inbound_q_done(struct qdio_q *q) +{ + int no_used; +#ifdef CONFIG_QDIO_DEBUG + char dbf_text[15]; +#endif + + no_used=atomic_read(&q->number_of_buffers_used); + + /* + * we need that one for synchronization with the adapter, as it + * does a kind of PCI avoidance + */ + SYNC_MEMORY; + + if (!no_used) { + QDIO_DBF_TEXT4(0,trace,"inqisdnA"); + QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); + QDIO_DBF_TEXT4(0,trace,dbf_text); + return 1; + } + + if (q->slsb.acc.val[q->first_to_check]==SLSB_P_INPUT_PRIMED) { + /* we got something to do */ + QDIO_DBF_TEXT4(0,trace,"inqisntA"); + QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); + return 0; + } + + /* on VM, we don't poll, so the q is always done here */ + if (q->siga_sync) + return 1; + if (q->hydra_gives_outbound_pcis) + return 1; + + /* + * at this point we know, that inbound first_to_check + * has (probably) not moved (see qdio_inbound_processing) + */ + if (NOW>GET_SAVED_TIMESTAMP(q)+q->timing.threshold) { +#ifdef CONFIG_QDIO_DEBUG + QDIO_DBF_TEXT4(0,trace,"inqisdon"); + QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); + sprintf(dbf_text,"pf%02xcn%02x",q->first_to_check,no_used); + QDIO_DBF_TEXT4(0,trace,dbf_text); +#endif /* CONFIG_QDIO_DEBUG */ + return 1; + } else { +#ifdef CONFIG_QDIO_DEBUG + QDIO_DBF_TEXT4(0,trace,"inqisntd"); + QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); + sprintf(dbf_text,"pf%02xcn%02x",q->first_to_check,no_used); + QDIO_DBF_TEXT4(0,trace,dbf_text); +#endif /* CONFIG_QDIO_DEBUG */ + return 0; + } +} + +inline static void +qdio_kick_inbound_handler(struct qdio_q *q) +{ + int count, start, end, real_end, i; +#ifdef CONFIG_QDIO_DEBUG + char dbf_text[15]; +#endif + + QDIO_DBF_TEXT4(0,trace,"kickinh"); + QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); + + start=q->first_element_to_kick; + real_end=q->first_to_check; + end=(real_end+QDIO_MAX_BUFFERS_PER_Q-1)&(QDIO_MAX_BUFFERS_PER_Q-1); + + i=start; + count=0; + while (1) { + count++; + if (i==end) + break; + i=(i+1)&(QDIO_MAX_BUFFERS_PER_Q-1); + } + +#ifdef CONFIG_QDIO_DEBUG + sprintf(dbf_text,"s=%2xc=%2x",start,count); + QDIO_DBF_TEXT4(0,trace,dbf_text); +#endif /* CONFIG_QDIO_DEBUG */ + + if (likely(q->state==QDIO_IRQ_STATE_ACTIVE)) + q->handler(q->cdev, + QDIO_STATUS_INBOUND_INT|q->error_status_flags, + q->qdio_error,q->siga_error,q->q_no,start,count, + q->int_parm); + + /* for the next time: */ + q->first_element_to_kick=real_end; + q->qdio_error=0; + q->siga_error=0; + q->error_status_flags=0; + +#ifdef QDIO_PERFORMANCE_STATS + perf_stats.inbound_time+=NOW-perf_stats.start_time_inbound; + perf_stats.inbound_cnt++; +#endif /* QDIO_PERFORMANCE_STATS */ +} + +static inline void +__tiqdio_inbound_processing(struct qdio_q *q, int spare_ind_was_set) +{ + struct qdio_irq *irq_ptr; + struct qdio_q *oq; + int i; + + QDIO_DBF_TEXT4(0,trace,"iqinproc"); + QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); + + /* + * we first want to reserve the q, so that we know, that we don't + * interrupt ourselves and call qdio_unmark_q, as is_in_shutdown might + * be set + */ + if (unlikely(qdio_reserve_q(q))) { + qdio_release_q(q); +#ifdef QDIO_PERFORMANCE_STATS + ii_p_c++; +#endif /* QDIO_PERFORMANCE_STATS */ + /* + * as we might just be about to stop polling, we make + * sure that we check again at least once more + */ + tiqdio_sched_tl(); + return; + } +#ifdef QDIO_PERFORMANCE_STATS + ii_p_nc++; +#endif /* QDIO_PERFORMANCE_STATS */ + if (unlikely(atomic_read(&q->is_in_shutdown))) { + qdio_unmark_q(q); + goto out; + } + + /* + * we reset spare_ind_was_set, when the queue does not use the + * spare indicator + */ + if (spare_ind_was_set) + spare_ind_was_set = (q->dev_st_chg_ind == &spare_indicator); + + if (!(*(q->dev_st_chg_ind)) && !spare_ind_was_set) + goto out; + /* + * q->dev_st_chg_ind is the indicator, be it shared or not. + * only clear it, if indicator is non-shared + */ + if (!spare_ind_was_set) + tiqdio_clear_summary_bit((__u32*)q->dev_st_chg_ind); + + if (q->hydra_gives_outbound_pcis) { + if (!q->siga_sync_done_on_thinints) { + SYNC_MEMORY_ALL; + } else if ((!q->siga_sync_done_on_outb_tis)&& + (q->hydra_gives_outbound_pcis)) { + SYNC_MEMORY_ALL_OUTB; + } + } else { + SYNC_MEMORY; + } + /* + * maybe we have to do work on our outbound queues... at least + * we have to check the outbound-int-capable thinint-capable + * queues + */ + if (q->hydra_gives_outbound_pcis) { + irq_ptr = (struct qdio_irq*)q->irq_ptr; + for (i=0;i<irq_ptr->no_output_qs;i++) { + oq = irq_ptr->output_qs[i]; +#ifdef QDIO_PERFORMANCE_STATS + perf_stats.tl_runs--; +#endif /* QDIO_PERFORMANCE_STATS */ + if (!qdio_is_outbound_q_done(oq)) + __qdio_outbound_processing(oq); + } + } + + if (!qdio_has_inbound_q_moved(q)) + goto out; + + qdio_kick_inbound_handler(q); + if (tiqdio_is_inbound_q_done(q)) + if (!qdio_stop_polling(q)) { + /* + * we set the flags to get into the stuff next time, + * see also comment in qdio_stop_polling + */ + tiqdio_set_summary_bit((__u32*)q->dev_st_chg_ind); + tiqdio_sched_tl(); + } +out: + qdio_release_q(q); +} + +static void +tiqdio_inbound_processing(struct qdio_q *q) +{ + __tiqdio_inbound_processing(q, atomic_read(&spare_indicator_usecount)); +} + +static inline void +__qdio_inbound_processing(struct qdio_q *q) +{ + int q_laps=0; + + QDIO_DBF_TEXT4(0,trace,"qinproc"); + QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); + + if (unlikely(qdio_reserve_q(q))) { + qdio_release_q(q); +#ifdef QDIO_PERFORMANCE_STATS + i_p_c++; +#endif /* QDIO_PERFORMANCE_STATS */ + /* as we're sissies, we'll check next time */ + if (likely(!atomic_read(&q->is_in_shutdown))) { + qdio_mark_q(q); + QDIO_DBF_TEXT4(0,trace,"busy,agn"); + } + return; + } +#ifdef QDIO_PERFORMANCE_STATS + i_p_nc++; + perf_stats.tl_runs++; +#endif /* QDIO_PERFORMANCE_STATS */ + +again: + if (qdio_has_inbound_q_moved(q)) { + qdio_kick_inbound_handler(q); + if (!qdio_stop_polling(q)) { + q_laps++; + if (q_laps<QDIO_Q_LAPS) + goto again; + } + qdio_mark_q(q); + } else { + if (!qdio_is_inbound_q_done(q)) + /* means poll time is not yet over */ + qdio_mark_q(q); + } + + qdio_release_q(q); +} + +static void +qdio_inbound_processing(struct qdio_q *q) +{ + __qdio_inbound_processing(q); +} + +/************************* MAIN ROUTINES *******************************/ + +#ifdef QDIO_USE_PROCESSING_STATE +static inline int +tiqdio_reset_processing_state(struct qdio_q *q, int q_laps) +{ + if (!q) { + tiqdio_sched_tl(); + return 0; + } + + /* + * under VM, we have not used the PROCESSING state, so no + * need to stop polling + */ + if (q->siga_sync) + return 2; + + if (unlikely(qdio_reserve_q(q))) { + qdio_release_q(q); +#ifdef QDIO_PERFORMANCE_STATS + ii_p_c++; +#endif /* QDIO_PERFORMANCE_STATS */ + /* + * as we might just be about to stop polling, we make + * sure that we check again at least once more + */ + + /* + * sanity -- we'd get here without setting the + * dev st chg ind + */ + tiqdio_set_summary_bit((__u32*)q->dev_st_chg_ind); + tiqdio_sched_tl(); + return 0; + } + if (qdio_stop_polling(q)) { + qdio_release_q(q); + return 2; + } + if (q_laps<QDIO_Q_LAPS-1) { + qdio_release_q(q); + return 3; + } + /* + * we set the flags to get into the stuff + * next time, see also comment in qdio_stop_polling + */ + tiqdio_set_summary_bit((__u32*)q->dev_st_chg_ind); + tiqdio_sched_tl(); + qdio_release_q(q); + return 1; + +} +#endif /* QDIO_USE_PROCESSING_STATE */ + +static inline void +tiqdio_inbound_checks(void) +{ + struct qdio_q *q; + int spare_ind_was_set=0; +#ifdef QDIO_USE_PROCESSING_STATE + int q_laps=0; +#endif /* QDIO_USE_PROCESSING_STATE */ + + QDIO_DBF_TEXT4(0,trace,"iqdinbck"); + QDIO_DBF_TEXT5(0,trace,"iqlocsum"); + +#ifdef QDIO_USE_PROCESSING_STATE +again: +#endif /* QDIO_USE_PROCESSING_STATE */ + + /* when the spare indicator is used and set, save that and clear it */ + if ((atomic_read(&spare_indicator_usecount)) && spare_indicator) { + spare_ind_was_set = 1; + tiqdio_clear_summary_bit((__u32*)&spare_indicator); + } + + q=(struct qdio_q*)tiq_list; + do { + if (!q) + break; + __tiqdio_inbound_processing(q, spare_ind_was_set); + q=(struct qdio_q*)q->list_next; + } while (q!=(struct qdio_q*)tiq_list); + +#ifdef QDIO_USE_PROCESSING_STATE + q=(struct qdio_q*)tiq_list; + do { + int ret; + + ret = tiqdio_reset_processing_state(q, q_laps); + switch (ret) { + case 0: + return; + case 1: + q_laps++; + case 2: + q = (struct qdio_q*)q->list_next; + break; + default: + q_laps++; + goto again; + } + } while (q!=(struct qdio_q*)tiq_list); +#endif /* QDIO_USE_PROCESSING_STATE */ +} + +static void +tiqdio_tl(unsigned long data) +{ + QDIO_DBF_TEXT4(0,trace,"iqdio_tl"); + +#ifdef QDIO_PERFORMANCE_STATS + perf_stats.tl_runs++; +#endif /* QDIO_PERFORMANCE_STATS */ + + tiqdio_inbound_checks(); +} + +/********************* GENERAL HELPER_ROUTINES ***********************/ + +static void +qdio_release_irq_memory(struct qdio_irq *irq_ptr) +{ + int i; + + for (i=0;i<QDIO_MAX_QUEUES_PER_IRQ;i++) { + if (!irq_ptr->input_qs[i]) + goto next; + + if (irq_ptr->input_qs[i]->slib) + kfree(irq_ptr->input_qs[i]->slib); + kfree(irq_ptr->input_qs[i]); + +next: + if (!irq_ptr->output_qs[i]) + continue; + + if (irq_ptr->output_qs[i]->slib) + kfree(irq_ptr->output_qs[i]->slib); + kfree(irq_ptr->output_qs[i]); + + } + kfree(irq_ptr->qdr); + kfree(irq_ptr); +} + +static void +qdio_set_impl_params(struct qdio_irq *irq_ptr, + unsigned int qib_param_field_format, + /* pointer to 128 bytes or NULL, if no param field */ + unsigned char *qib_param_field, + /* pointer to no_queues*128 words of data or NULL */ + unsigned int no_input_qs, + unsigned int no_output_qs, + unsigned long *input_slib_elements, + unsigned long *output_slib_elements) +{ + int i,j; + + if (!irq_ptr) + return; + + irq_ptr->qib.pfmt=qib_param_field_format; + if (qib_param_field) + memcpy(irq_ptr->qib.parm,qib_param_field, + QDIO_MAX_BUFFERS_PER_Q); + + if (input_slib_elements) + for (i=0;i<no_input_qs;i++) { + for (j=0;j<QDIO_MAX_BUFFERS_PER_Q;j++) + irq_ptr->input_qs[i]->slib->slibe[j].parms= + input_slib_elements[ + i*QDIO_MAX_BUFFERS_PER_Q+j]; + } + if (output_slib_elements) + for (i=0;i<no_output_qs;i++) { + for (j=0;j<QDIO_MAX_BUFFERS_PER_Q;j++) + irq_ptr->output_qs[i]->slib->slibe[j].parms= + output_slib_elements[ + i*QDIO_MAX_BUFFERS_PER_Q+j]; + } +} + +static int +qdio_alloc_qs(struct qdio_irq *irq_ptr, + int no_input_qs, int no_output_qs) +{ + int i; + struct qdio_q *q; + int result=-ENOMEM; + + for (i=0;i<no_input_qs;i++) { + q=kmalloc(sizeof(struct qdio_q),GFP_KERNEL); + + if (!q) { + QDIO_PRINT_ERR("kmalloc of q failed!\n"); + goto out; + } + + memset(q,0,sizeof(struct qdio_q)); + + q->slib=kmalloc(PAGE_SIZE,GFP_KERNEL); + if (!q->slib) { + QDIO_PRINT_ERR("kmalloc of slib failed!\n"); + goto out; + } + + irq_ptr->input_qs[i]=q; + } + + for (i=0;i<no_output_qs;i++) { + q=kmalloc(sizeof(struct qdio_q),GFP_KERNEL); + + if (!q) { + goto out; + } + + memset(q,0,sizeof(struct qdio_q)); + + q->slib=kmalloc(PAGE_SIZE,GFP_KERNEL); + if (!q->slib) { + QDIO_PRINT_ERR("kmalloc of slib failed!\n"); + goto out; + } + + irq_ptr->output_qs[i]=q; + } + + result=0; +out: + return result; +} + +static void +qdio_fill_qs(struct qdio_irq *irq_ptr, struct ccw_device *cdev, + int no_input_qs, int no_output_qs, + qdio_handler_t *input_handler, + qdio_handler_t *output_handler, + unsigned long int_parm,int q_format, + unsigned long flags, + void **inbound_sbals_array, + void **outbound_sbals_array) +{ + struct qdio_q *q; + int i,j; + char dbf_text[20]; /* see qdio_initialize */ + void *ptr; + int available; + + sprintf(dbf_text,"qfqs%4x",cdev->private->irq); + QDIO_DBF_TEXT0(0,setup,dbf_text); + for (i=0;i<no_input_qs;i++) { + q=irq_ptr->input_qs[i]; + + memset(q,0,((char*)&q->slib)-((char*)q)); + sprintf(dbf_text,"in-q%4x",i); + QDIO_DBF_TEXT0(0,setup,dbf_text); + QDIO_DBF_HEX0(0,setup,&q,sizeof(void*)); + + memset(q->slib,0,PAGE_SIZE); + q->sl=(struct sl*)(((char*)q->slib)+PAGE_SIZE/2); + + available=0; + + for (j=0;j<QDIO_MAX_BUFFERS_PER_Q;j++) + q->sbal[j]=*(inbound_sbals_array++); + + q->queue_type=q_format; + q->int_parm=int_parm; + q->irq=irq_ptr->irq; + q->irq_ptr = irq_ptr; + q->cdev = cdev; + q->mask=1<<(31-i); + q->q_no=i; + q->is_input_q=1; + q->first_to_check=0; + q->last_move_ftc=0; + q->handler=input_handler; + q->dev_st_chg_ind=irq_ptr->dev_st_chg_ind; + + q->tasklet.data=(unsigned long)q; + /* q->is_thinint_q isn't valid at this time, but + * irq_ptr->is_thinint_irq is */ + q->tasklet.func=(void(*)(unsigned long)) + ((irq_ptr->is_thinint_irq)?&tiqdio_inbound_processing: + &qdio_inbound_processing); + + /* actually this is not used for inbound queues. yet. */ + atomic_set(&q->busy_siga_counter,0); + q->timing.busy_start=0; + +/* for (j=0;j<QDIO_STATS_NUMBER;j++) + q->timing.last_transfer_times[j]=(qdio_get_micros()/ + QDIO_STATS_NUMBER)*j; + q->timing.last_transfer_index=QDIO_STATS_NUMBER-1; +*/ + + /* fill in slib */ + if (i>0) irq_ptr->input_qs[i-1]->slib->nsliba= + (unsigned long)(q->slib); + q->slib->sla=(unsigned long)(q->sl); + q->slib->slsba=(unsigned long)(&q->slsb.acc.val[0]); + + /* fill in sl */ + for (j=0;j<QDIO_MAX_BUFFERS_PER_Q;j++) + q->sl->element[j].sbal=(unsigned long)(q->sbal[j]); + + QDIO_DBF_TEXT2(0,setup,"sl-sb-b0"); + ptr=(void*)q->sl; + QDIO_DBF_HEX2(0,setup,&ptr,sizeof(void*)); + ptr=(void*)&q->slsb; + QDIO_DBF_HEX2(0,setup,&ptr,sizeof(void*)); + ptr=(void*)q->sbal[0]; + QDIO_DBF_HEX2(0,setup,&ptr,sizeof(void*)); + + /* fill in slsb */ + for (j=0;j<QDIO_MAX_BUFFERS_PER_Q;j++) { + set_slsb(&q->slsb.acc.val[j], + SLSB_P_INPUT_NOT_INIT); +/* q->sbal[j]->element[1].sbalf.i1.key=QDIO_STORAGE_KEY;*/ + } + } + + for (i=0;i<no_output_qs;i++) { + q=irq_ptr->output_qs[i]; + memset(q,0,((char*)&q->slib)-((char*)q)); + + sprintf(dbf_text,"outq%4x",i); + QDIO_DBF_TEXT0(0,setup,dbf_text); + QDIO_DBF_HEX0(0,setup,&q,sizeof(void*)); + + memset(q->slib,0,PAGE_SIZE); + q->sl=(struct sl*)(((char*)q->slib)+PAGE_SIZE/2); + + available=0; + + for (j=0;j<QDIO_MAX_BUFFERS_PER_Q;j++) + q->sbal[j]=*(outbound_sbals_array++); + + q->queue_type=q_format; + q->int_parm=int_parm; + q->is_input_q=0; + q->irq=irq_ptr->irq; + q->cdev = cdev; + q->irq_ptr = irq_ptr; + q->mask=1<<(31-i); + q->q_no=i; + q->first_to_check=0; + q->last_move_ftc=0; + q->handler=output_handler; + + q->tasklet.data=(unsigned long)q; + q->tasklet.func=(void(*)(unsigned long)) + &qdio_outbound_processing; + + atomic_set(&q->busy_siga_counter,0); + q->timing.busy_start=0; + + /* fill in slib */ + if (i>0) irq_ptr->output_qs[i-1]->slib->nsliba= + (unsigned long)(q->slib); + q->slib->sla=(unsigned long)(q->sl); + q->slib->slsba=(unsigned long)(&q->slsb.acc.val[0]); + + /* fill in sl */ + for (j=0;j<QDIO_MAX_BUFFERS_PER_Q;j++) + q->sl->element[j].sbal=(unsigned long)(q->sbal[j]); + + QDIO_DBF_TEXT2(0,setup,"sl-sb-b0"); + ptr=(void*)q->sl; + QDIO_DBF_HEX2(0,setup,&ptr,sizeof(void*)); + ptr=(void*)&q->slsb; + QDIO_DBF_HEX2(0,setup,&ptr,sizeof(void*)); + ptr=(void*)q->sbal[0]; + QDIO_DBF_HEX2(0,setup,&ptr,sizeof(void*)); + + /* fill in slsb */ + for (j=0;j<QDIO_MAX_BUFFERS_PER_Q;j++) { + set_slsb(&q->slsb.acc.val[j], + SLSB_P_OUTPUT_NOT_INIT); +/* q->sbal[j]->element[1].sbalf.i1.key=QDIO_STORAGE_KEY;*/ + } + } +} + +static void +qdio_fill_thresholds(struct qdio_irq *irq_ptr, + unsigned int no_input_qs, + unsigned int no_output_qs, + unsigned int min_input_threshold, + unsigned int max_input_threshold, + unsigned int min_output_threshold, + unsigned int max_output_threshold) +{ + int i; + struct qdio_q *q; + + for (i=0;i<no_input_qs;i++) { + q=irq_ptr->input_qs[i]; + q->timing.threshold=max_input_threshold; +/* for (j=0;j<QDIO_STATS_CLASSES;j++) { + q->threshold_classes[j].threshold= + min_input_threshold+ + (max_input_threshold-min_input_threshold)/ + QDIO_STATS_CLASSES; + } + qdio_use_thresholds(q,QDIO_STATS_CLASSES/2);*/ + } + for (i=0;i<no_output_qs;i++) { + q=irq_ptr->output_qs[i]; + q->timing.threshold=max_output_threshold; +/* for (j=0;j<QDIO_STATS_CLASSES;j++) { + q->threshold_classes[j].threshold= + min_output_threshold+ + (max_output_threshold-min_output_threshold)/ + QDIO_STATS_CLASSES; + } + qdio_use_thresholds(q,QDIO_STATS_CLASSES/2);*/ + } +} + +static int +tiqdio_thinint_handler(void) +{ + QDIO_DBF_TEXT4(0,trace,"thin_int"); + +#ifdef QDIO_PERFORMANCE_STATS + perf_stats.thinints++; + perf_stats.start_time_inbound=NOW; +#endif /* QDIO_PERFORMANCE_STATS */ + + /* SVS only when needed: + * issue SVS to benefit from iqdio interrupt avoidance + * (SVS clears AISOI)*/ + if (!omit_svs) + tiqdio_clear_global_summary(); + + tiqdio_inbound_checks(); + return 0; +} + +static void +qdio_set_state(struct qdio_irq *irq_ptr, enum qdio_irq_states state) +{ + int i; +#ifdef CONFIG_QDIO_DEBUG + char dbf_text[15]; + + QDIO_DBF_TEXT5(0,trace,"newstate"); + sprintf(dbf_text,"%4x%4x",irq_ptr->irq,state); + QDIO_DBF_TEXT5(0,trace,dbf_text); +#endif /* CONFIG_QDIO_DEBUG */ + + irq_ptr->state=state; + for (i=0;i<irq_ptr->no_input_qs;i++) + irq_ptr->input_qs[i]->state=state; + for (i=0;i<irq_ptr->no_output_qs;i++) + irq_ptr->output_qs[i]->state=state; + mb(); +} + +static inline void +qdio_irq_check_sense(int irq, struct irb *irb) +{ + char dbf_text[15]; + + if (irb->esw.esw0.erw.cons) { + sprintf(dbf_text,"sens%4x",irq); + QDIO_DBF_TEXT2(1,trace,dbf_text); + QDIO_DBF_HEX0(0,sense,irb,QDIO_DBF_SENSE_LEN); + + QDIO_PRINT_WARN("sense data available on qdio channel.\n"); + HEXDUMP16(WARN,"irb: ",irb); + HEXDUMP16(WARN,"sense data: ",irb->ecw); + } + +} + +static inline void +qdio_handle_pci(struct qdio_irq *irq_ptr) +{ + int i; + struct qdio_q *q; + +#ifdef QDIO_PERFORMANCE_STATS + perf_stats.pcis++; + perf_stats.start_time_inbound=NOW; +#endif /* QDIO_PERFORMANCE_STATS */ + for (i=0;i<irq_ptr->no_input_qs;i++) { + q=irq_ptr->input_qs[i]; + if (q->is_input_q&QDIO_FLAG_NO_INPUT_INTERRUPT_CONTEXT) + qdio_mark_q(q); + else { +#ifdef QDIO_PERFORMANCE_STATS + perf_stats.tl_runs--; +#endif /* QDIO_PERFORMANCE_STATS */ + __qdio_inbound_processing(q); + } + } + if (!irq_ptr->hydra_gives_outbound_pcis) + return; + for (i=0;i<irq_ptr->no_output_qs;i++) { + q=irq_ptr->output_qs[i]; +#ifdef QDIO_PERFORMANCE_STATS + perf_stats.tl_runs--; +#endif /* QDIO_PERFORMANCE_STATS */ + if (qdio_is_outbound_q_done(q)) + continue; + if (!irq_ptr->sync_done_on_outb_pcis) + SYNC_MEMORY; + __qdio_outbound_processing(q); + } +} + +static void qdio_establish_handle_irq(struct ccw_device*, int, int); + +static inline void +qdio_handle_activate_check(struct ccw_device *cdev, unsigned long intparm, + int cstat, int dstat) +{ + struct qdio_irq *irq_ptr; + struct qdio_q *q; + char dbf_text[15]; + + irq_ptr = cdev->private->qdio_data; + + QDIO_DBF_TEXT2(1, trace, "ick2"); + sprintf(dbf_text,"%s", cdev->dev.bus_id); + QDIO_DBF_TEXT2(1,trace,dbf_text); + QDIO_DBF_HEX2(0,trace,&intparm,sizeof(int)); + QDIO_DBF_HEX2(0,trace,&dstat,sizeof(int)); + QDIO_DBF_HEX2(0,trace,&cstat,sizeof(int)); + QDIO_PRINT_ERR("received check condition on activate " \ + "queues on device %s (cs=x%x, ds=x%x).\n", + cdev->dev.bus_id, cstat, dstat); + if (irq_ptr->no_input_qs) { + q=irq_ptr->input_qs[0]; + } else if (irq_ptr->no_output_qs) { + q=irq_ptr->output_qs[0]; + } else { + QDIO_PRINT_ERR("oops... no queue registered for device %s!?\n", + cdev->dev.bus_id); + goto omit_handler_call; + } + q->handler(q->cdev,QDIO_STATUS_ACTIVATE_CHECK_CONDITION| + QDIO_STATUS_LOOK_FOR_ERROR, + 0,0,0,-1,-1,q->int_parm); +omit_handler_call: + qdio_set_state(irq_ptr,QDIO_IRQ_STATE_STOPPED); + +} + +static void +qdio_call_shutdown(void *data) +{ + struct ccw_device *cdev; + + cdev = (struct ccw_device *)data; + qdio_shutdown(cdev, QDIO_FLAG_CLEANUP_USING_CLEAR); + put_device(&cdev->dev); +} + +static void +qdio_timeout_handler(struct ccw_device *cdev) +{ + struct qdio_irq *irq_ptr; + char dbf_text[15]; + + QDIO_DBF_TEXT2(0, trace, "qtoh"); + sprintf(dbf_text, "%s", cdev->dev.bus_id); + QDIO_DBF_TEXT2(0, trace, dbf_text); + + irq_ptr = cdev->private->qdio_data; + sprintf(dbf_text, "state:%d", irq_ptr->state); + QDIO_DBF_TEXT2(0, trace, dbf_text); + + switch (irq_ptr->state) { + case QDIO_IRQ_STATE_INACTIVE: + QDIO_PRINT_ERR("establish queues on irq %04x: timed out\n", + irq_ptr->irq); + QDIO_DBF_TEXT2(1,setup,"eq:timeo"); + qdio_set_state(irq_ptr, QDIO_IRQ_STATE_ERR); + break; + case QDIO_IRQ_STATE_CLEANUP: + QDIO_PRINT_INFO("Did not get interrupt on cleanup, irq=0x%x.\n", + irq_ptr->irq); + qdio_set_state(irq_ptr, QDIO_IRQ_STATE_ERR); + break; + case QDIO_IRQ_STATE_ESTABLISHED: + case QDIO_IRQ_STATE_ACTIVE: + /* I/O has been terminated by common I/O layer. */ + QDIO_PRINT_INFO("Queues on irq %04x killed by cio.\n", + irq_ptr->irq); + QDIO_DBF_TEXT2(1, trace, "cio:term"); + qdio_set_state(irq_ptr, QDIO_IRQ_STATE_STOPPED); + if (get_device(&cdev->dev)) { + /* Can't call shutdown from interrupt context. */ + PREPARE_WORK(&cdev->private->kick_work, + qdio_call_shutdown, (void *)cdev); + queue_work(ccw_device_work, &cdev->private->kick_work); + } + break; + default: + BUG(); + } + ccw_device_set_timeout(cdev, 0); + wake_up(&cdev->private->wait_q); +} + +static void +qdio_handler(struct ccw_device *cdev, unsigned long intparm, struct irb *irb) +{ + struct qdio_irq *irq_ptr; + int cstat,dstat; + char dbf_text[15]; + +#ifdef CONFIG_QDIO_DEBUG + QDIO_DBF_TEXT4(0, trace, "qint"); + sprintf(dbf_text, "%s", cdev->dev.bus_id); + QDIO_DBF_TEXT4(0, trace, dbf_text); +#endif /* CONFIG_QDIO_DEBUG */ + + if (!intparm) { + QDIO_PRINT_ERR("got unsolicited interrupt in qdio " \ + "handler, device %s\n", cdev->dev.bus_id); + return; + } + + irq_ptr = cdev->private->qdio_data; + if (!irq_ptr) { + QDIO_DBF_TEXT2(1, trace, "uint"); + sprintf(dbf_text,"%s", cdev->dev.bus_id); + QDIO_DBF_TEXT2(1,trace,dbf_text); + QDIO_PRINT_ERR("received interrupt on unused device %s!\n", + cdev->dev.bus_id); + return; + } + + if (IS_ERR(irb)) { + /* Currently running i/o is in error. */ + switch (PTR_ERR(irb)) { + case -EIO: + QDIO_PRINT_ERR("i/o error on device %s\n", + cdev->dev.bus_id); + return; + case -ETIMEDOUT: + qdio_timeout_handler(cdev); + return; + default: + QDIO_PRINT_ERR("unknown error state %ld on device %s\n", + PTR_ERR(irb), cdev->dev.bus_id); + return; + } + } + + qdio_irq_check_sense(irq_ptr->irq, irb); + +#ifdef CONFIG_QDIO_DEBUG + sprintf(dbf_text, "state:%d", irq_ptr->state); + QDIO_DBF_TEXT4(0, trace, dbf_text); +#endif /* CONFIG_QDIO_DEBUG */ + + cstat = irb->scsw.cstat; + dstat = irb->scsw.dstat; + + switch (irq_ptr->state) { + case QDIO_IRQ_STATE_INACTIVE: + qdio_establish_handle_irq(cdev, cstat, dstat); + break; + + case QDIO_IRQ_STATE_CLEANUP: + qdio_set_state(irq_ptr, QDIO_IRQ_STATE_INACTIVE); + break; + + case QDIO_IRQ_STATE_ESTABLISHED: + case QDIO_IRQ_STATE_ACTIVE: + if (cstat & SCHN_STAT_PCI) { + qdio_handle_pci(irq_ptr); + break; + } + + if ((cstat&~SCHN_STAT_PCI)||dstat) { + qdio_handle_activate_check(cdev, intparm, cstat, dstat); + break; + } + default: + QDIO_PRINT_ERR("got interrupt for queues in state %d on " \ + "device %s?!\n", + irq_ptr->state, cdev->dev.bus_id); + } + wake_up(&cdev->private->wait_q); + +} + +int +qdio_synchronize(struct ccw_device *cdev, unsigned int flags, + unsigned int queue_number) +{ + int cc; + struct qdio_q *q; + struct qdio_irq *irq_ptr; + void *ptr; +#ifdef CONFIG_QDIO_DEBUG + char dbf_text[15]="SyncXXXX"; +#endif + + irq_ptr = cdev->private->qdio_data; + if (!irq_ptr) + return -ENODEV; + +#ifdef CONFIG_QDIO_DEBUG + *((int*)(&dbf_text[4])) = irq_ptr->irq; + QDIO_DBF_HEX4(0,trace,dbf_text,QDIO_DBF_TRACE_LEN); + *((int*)(&dbf_text[0]))=flags; + *((int*)(&dbf_text[4]))=queue_number; + QDIO_DBF_HEX4(0,trace,dbf_text,QDIO_DBF_TRACE_LEN); +#endif /* CONFIG_QDIO_DEBUG */ + + if (flags&QDIO_FLAG_SYNC_INPUT) { + q=irq_ptr->input_qs[queue_number]; + if (!q) + return -EINVAL; + cc = do_siga_sync(q->irq, 0, q->mask); + } else if (flags&QDIO_FLAG_SYNC_OUTPUT) { + q=irq_ptr->output_qs[queue_number]; + if (!q) + return -EINVAL; + cc = do_siga_sync(q->irq, q->mask, 0); + } else + return -EINVAL; + + ptr=&cc; + if (cc) + QDIO_DBF_HEX3(0,trace,&ptr,sizeof(int)); + + return cc; +} + +static unsigned char +qdio_check_siga_needs(int sch) +{ + int result; + unsigned char qdioac; + + struct { + struct chsc_header request; + u16 reserved1; + u16 first_sch; + u16 reserved2; + u16 last_sch; + u32 reserved3; + struct chsc_header response; + u32 reserved4; + u8 flags; + u8 reserved5; + u16 sch; + u8 qfmt; + u8 reserved6; + u8 qdioac; + u8 sch_class; + u8 reserved7; + u8 icnt; + u8 reserved8; + u8 ocnt; + } *ssqd_area; + + ssqd_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); + if (!ssqd_area) { + QDIO_PRINT_WARN("Could not get memory for chsc. Using all " \ + "SIGAs for sch x%x.\n", sch); + return CHSC_FLAG_SIGA_INPUT_NECESSARY || + CHSC_FLAG_SIGA_OUTPUT_NECESSARY || + CHSC_FLAG_SIGA_SYNC_NECESSARY; /* all flags set */ + } + ssqd_area->request = (struct chsc_header) { + .length = 0x0010, + .code = 0x0024, + }; + + ssqd_area->first_sch = sch; + ssqd_area->last_sch = sch; + + result=chsc(ssqd_area); + + if (result) { + QDIO_PRINT_WARN("CHSC returned cc %i. Using all " \ + "SIGAs for sch x%x.\n", + result,sch); + qdioac = CHSC_FLAG_SIGA_INPUT_NECESSARY || + CHSC_FLAG_SIGA_OUTPUT_NECESSARY || + CHSC_FLAG_SIGA_SYNC_NECESSARY; /* all flags set */ + goto out; + } + + if (ssqd_area->response.code != QDIO_CHSC_RESPONSE_CODE_OK) { + QDIO_PRINT_WARN("response upon checking SIGA needs " \ + "is 0x%x. Using all SIGAs for sch x%x.\n", + ssqd_area->response.code, sch); + qdioac = CHSC_FLAG_SIGA_INPUT_NECESSARY || + CHSC_FLAG_SIGA_OUTPUT_NECESSARY || + CHSC_FLAG_SIGA_SYNC_NECESSARY; /* all flags set */ + goto out; + } + if (!(ssqd_area->flags & CHSC_FLAG_QDIO_CAPABILITY) || + !(ssqd_area->flags & CHSC_FLAG_VALIDITY) || + (ssqd_area->sch != sch)) { + QDIO_PRINT_WARN("huh? problems checking out sch x%x... " \ + "using all SIGAs.\n",sch); + qdioac = CHSC_FLAG_SIGA_INPUT_NECESSARY | + CHSC_FLAG_SIGA_OUTPUT_NECESSARY | + CHSC_FLAG_SIGA_SYNC_NECESSARY; /* worst case */ + goto out; + } + + qdioac = ssqd_area->qdioac; +out: + free_page ((unsigned long) ssqd_area); + return qdioac; +} + +static unsigned int +tiqdio_check_chsc_availability(void) +{ + char dbf_text[15]; + + if (!css_characteristics_avail) + return -EIO; + + /* Check for bit 41. */ + if (!css_general_characteristics.aif) { + QDIO_PRINT_WARN("Adapter interruption facility not " \ + "installed.\n"); + return -ENOENT; + } + + /* Check for bits 107 and 108. */ + if (!css_chsc_characteristics.scssc || + !css_chsc_characteristics.scsscf) { + QDIO_PRINT_WARN("Set Chan Subsys. Char. & Fast-CHSCs " \ + "not available.\n"); + return -ENOENT; + } + + /* Check for OSA/FCP thin interrupts (bit 67). */ + hydra_thinints = css_general_characteristics.aif_osa; + sprintf(dbf_text,"hydrati%1x", hydra_thinints); + QDIO_DBF_TEXT0(0,setup,dbf_text); + + /* Check for aif time delay disablement fac (bit 56). If installed, + * omit svs even under lpar (good point by rick again) */ + omit_svs = css_general_characteristics.aif_tdd; + sprintf(dbf_text,"omitsvs%1x", omit_svs); + QDIO_DBF_TEXT0(0,setup,dbf_text); + return 0; +} + + +static unsigned int +tiqdio_set_subchannel_ind(struct qdio_irq *irq_ptr, int reset_to_zero) +{ + unsigned long real_addr_local_summary_bit; + unsigned long real_addr_dev_st_chg_ind; + void *ptr; + char dbf_text[15]; + + unsigned int resp_code; + int result; + + struct { + struct chsc_header request; + u16 operation_code; + u16 reserved1; + u32 reserved2; + u32 reserved3; + u64 summary_indicator_addr; + u64 subchannel_indicator_addr; + u32 ks:4; + u32 kc:4; + u32 reserved4:21; + u32 isc:3; + u32 word_with_d_bit; + /* set to 0x10000000 to enable + * time delay disablement facility */ + u32 reserved5; + u32 subsystem_id; + u32 reserved6[1004]; + struct chsc_header response; + u32 reserved7; + } *scssc_area; + + if (!irq_ptr->is_thinint_irq) + return -ENODEV; + + if (reset_to_zero) { + real_addr_local_summary_bit=0; + real_addr_dev_st_chg_ind=0; + } else { + real_addr_local_summary_bit= + virt_to_phys((volatile void *)indicators); + real_addr_dev_st_chg_ind= + virt_to_phys((volatile void *)irq_ptr->dev_st_chg_ind); + } + + scssc_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); + if (!scssc_area) { + QDIO_PRINT_WARN("No memory for setting indicators on " \ + "subchannel x%x.\n", irq_ptr->irq); + return -ENOMEM; + } + scssc_area->request = (struct chsc_header) { + .length = 0x0fe0, + .code = 0x0021, + }; + scssc_area->operation_code = 0; + + scssc_area->summary_indicator_addr = real_addr_local_summary_bit; + scssc_area->subchannel_indicator_addr = real_addr_dev_st_chg_ind; + scssc_area->ks = QDIO_STORAGE_KEY; + scssc_area->kc = QDIO_STORAGE_KEY; + scssc_area->isc = TIQDIO_THININT_ISC; + scssc_area->subsystem_id = (1<<16) + irq_ptr->irq; + /* enables the time delay disablement facility. Don't care + * whether it is really there (i.e. we haven't checked for + * it) */ + if (css_general_characteristics.aif_tdd) + scssc_area->word_with_d_bit = 0x10000000; + else + QDIO_PRINT_WARN("Time delay disablement facility " \ + "not available\n"); + + + + result = chsc(scssc_area); + if (result) { + QDIO_PRINT_WARN("could not set indicators on irq x%x, " \ + "cc=%i.\n",irq_ptr->irq,result); + result = -EIO; + goto out; + } + + resp_code = scssc_area->response.code; + if (resp_code!=QDIO_CHSC_RESPONSE_CODE_OK) { + QDIO_PRINT_WARN("response upon setting indicators " \ + "is 0x%x.\n",resp_code); + sprintf(dbf_text,"sidR%4x",resp_code); + QDIO_DBF_TEXT1(0,trace,dbf_text); + QDIO_DBF_TEXT1(0,setup,dbf_text); + ptr=&scssc_area->response; + QDIO_DBF_HEX2(1,setup,&ptr,QDIO_DBF_SETUP_LEN); + result = -EIO; + goto out; + } + + QDIO_DBF_TEXT2(0,setup,"setscind"); + QDIO_DBF_HEX2(0,setup,&real_addr_local_summary_bit, + sizeof(unsigned long)); + QDIO_DBF_HEX2(0,setup,&real_addr_dev_st_chg_ind,sizeof(unsigned long)); + result = 0; +out: + free_page ((unsigned long) scssc_area); + return result; + +} + +static unsigned int +tiqdio_set_delay_target(struct qdio_irq *irq_ptr, unsigned long delay_target) +{ + unsigned int resp_code; + int result; + void *ptr; + char dbf_text[15]; + + struct { + struct chsc_header request; + u16 operation_code; + u16 reserved1; + u32 reserved2; + u32 reserved3; + u32 reserved4[2]; + u32 delay_target; + u32 reserved5[1009]; + struct chsc_header response; + u32 reserved6; + } *scsscf_area; + + if (!irq_ptr->is_thinint_irq) + return -ENODEV; + + scsscf_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); + if (!scsscf_area) { + QDIO_PRINT_WARN("No memory for setting delay target on " \ + "subchannel x%x.\n", irq_ptr->irq); + return -ENOMEM; + } + scsscf_area->request = (struct chsc_header) { + .length = 0x0fe0, + .code = 0x1027, + }; + + scsscf_area->delay_target = delay_target<<16; + + result=chsc(scsscf_area); + if (result) { + QDIO_PRINT_WARN("could not set delay target on irq x%x, " \ + "cc=%i. Continuing.\n",irq_ptr->irq,result); + result = -EIO; + goto out; + } + + resp_code = scsscf_area->response.code; + if (resp_code!=QDIO_CHSC_RESPONSE_CODE_OK) { + QDIO_PRINT_WARN("response upon setting delay target " \ + "is 0x%x. Continuing.\n",resp_code); + sprintf(dbf_text,"sdtR%4x",resp_code); + QDIO_DBF_TEXT1(0,trace,dbf_text); + QDIO_DBF_TEXT1(0,setup,dbf_text); + ptr=&scsscf_area->response; + QDIO_DBF_HEX2(1,trace,&ptr,QDIO_DBF_TRACE_LEN); + } + QDIO_DBF_TEXT2(0,trace,"delytrgt"); + QDIO_DBF_HEX2(0,trace,&delay_target,sizeof(unsigned long)); + result = 0; /* not critical */ +out: + free_page ((unsigned long) scsscf_area); + return result; +} + +int +qdio_cleanup(struct ccw_device *cdev, int how) +{ + struct qdio_irq *irq_ptr; + char dbf_text[15]; + int rc; + + irq_ptr = cdev->private->qdio_data; + if (!irq_ptr) + return -ENODEV; + + sprintf(dbf_text,"qcln%4x",irq_ptr->irq); + QDIO_DBF_TEXT1(0,trace,dbf_text); + QDIO_DBF_TEXT0(0,setup,dbf_text); + + rc = qdio_shutdown(cdev, how); + if ((rc == 0) || (rc == -EINPROGRESS)) + rc = qdio_free(cdev); + return rc; +} + +int +qdio_shutdown(struct ccw_device *cdev, int how) +{ + struct qdio_irq *irq_ptr; + int i; + int result = 0; + int rc; + unsigned long flags; + int timeout; + char dbf_text[15]; + + irq_ptr = cdev->private->qdio_data; + if (!irq_ptr) + return -ENODEV; + + down(&irq_ptr->setting_up_sema); + + sprintf(dbf_text,"qsqs%4x",irq_ptr->irq); + QDIO_DBF_TEXT1(0,trace,dbf_text); + QDIO_DBF_TEXT0(0,setup,dbf_text); + + /* mark all qs as uninteresting */ + for (i=0;i<irq_ptr->no_input_qs;i++) + atomic_set(&irq_ptr->input_qs[i]->is_in_shutdown,1); + + for (i=0;i<irq_ptr->no_output_qs;i++) + atomic_set(&irq_ptr->output_qs[i]->is_in_shutdown,1); + + tasklet_kill(&tiqdio_tasklet); + + for (i=0;i<irq_ptr->no_input_qs;i++) { + qdio_unmark_q(irq_ptr->input_qs[i]); + tasklet_kill(&irq_ptr->input_qs[i]->tasklet); + wait_event_interruptible_timeout(cdev->private->wait_q, + !atomic_read(&irq_ptr-> + input_qs[i]-> + use_count), + QDIO_NO_USE_COUNT_TIMEOUT); + if (atomic_read(&irq_ptr->input_qs[i]->use_count)) + result=-EINPROGRESS; + } + + for (i=0;i<irq_ptr->no_output_qs;i++) { + tasklet_kill(&irq_ptr->output_qs[i]->tasklet); + wait_event_interruptible_timeout(cdev->private->wait_q, + !atomic_read(&irq_ptr-> + output_qs[i]-> + use_count), + QDIO_NO_USE_COUNT_TIMEOUT); + if (atomic_read(&irq_ptr->output_qs[i]->use_count)) + result=-EINPROGRESS; + } + + /* cleanup subchannel */ + spin_lock_irqsave(get_ccwdev_lock(cdev),flags); + if (how&QDIO_FLAG_CLEANUP_USING_CLEAR) { + rc = ccw_device_clear(cdev, QDIO_DOING_CLEANUP); + timeout=QDIO_CLEANUP_CLEAR_TIMEOUT; + } else if (how&QDIO_FLAG_CLEANUP_USING_HALT) { + rc = ccw_device_halt(cdev, QDIO_DOING_CLEANUP); + timeout=QDIO_CLEANUP_HALT_TIMEOUT; + } else { /* default behaviour */ + rc = ccw_device_halt(cdev, QDIO_DOING_CLEANUP); + timeout=QDIO_CLEANUP_HALT_TIMEOUT; + } + if (rc == -ENODEV) { + /* No need to wait for device no longer present. */ + qdio_set_state(irq_ptr, QDIO_IRQ_STATE_INACTIVE); + spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags); + } else if (((void *)cdev->handler != (void *)qdio_handler) && rc == 0) { + /* + * Whoever put another handler there, has to cope with the + * interrupt theirself. Might happen if qdio_shutdown was + * called on already shutdown queues, but this shouldn't have + * bad side effects. + */ + qdio_set_state(irq_ptr, QDIO_IRQ_STATE_INACTIVE); + spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags); + } else if (rc == 0) { + qdio_set_state(irq_ptr, QDIO_IRQ_STATE_CLEANUP); + ccw_device_set_timeout(cdev, timeout); + spin_unlock_irqrestore(get_ccwdev_lock(cdev),flags); + + wait_event(cdev->private->wait_q, + irq_ptr->state == QDIO_IRQ_STATE_INACTIVE || + irq_ptr->state == QDIO_IRQ_STATE_ERR); + } else { + QDIO_PRINT_INFO("ccw_device_{halt,clear} returned %d for " + "device %s\n", result, cdev->dev.bus_id); + spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags); + result = rc; + goto out; + } + if (irq_ptr->is_thinint_irq) { + qdio_put_indicator((__u32*)irq_ptr->dev_st_chg_ind); + tiqdio_set_subchannel_ind(irq_ptr,1); + /* reset adapter interrupt indicators */ + } + + /* exchange int handlers, if necessary */ + if ((void*)cdev->handler == (void*)qdio_handler) + cdev->handler=irq_ptr->original_int_handler; + + /* Ignore errors. */ + qdio_set_state(irq_ptr, QDIO_IRQ_STATE_INACTIVE); + ccw_device_set_timeout(cdev, 0); +out: + up(&irq_ptr->setting_up_sema); + return result; +} + +int +qdio_free(struct ccw_device *cdev) +{ + struct qdio_irq *irq_ptr; + char dbf_text[15]; + + irq_ptr = cdev->private->qdio_data; + if (!irq_ptr) + return -ENODEV; + + down(&irq_ptr->setting_up_sema); + + sprintf(dbf_text,"qfqs%4x",irq_ptr->irq); + QDIO_DBF_TEXT1(0,trace,dbf_text); + QDIO_DBF_TEXT0(0,setup,dbf_text); + + cdev->private->qdio_data = 0; + + up(&irq_ptr->setting_up_sema); + + qdio_release_irq_memory(irq_ptr); + module_put(THIS_MODULE); + return 0; +} + +static inline void +qdio_allocate_do_dbf(struct qdio_initialize *init_data) +{ + char dbf_text[20]; /* if a printf printed out more than 8 chars */ + + sprintf(dbf_text,"qfmt:%x",init_data->q_format); + QDIO_DBF_TEXT0(0,setup,dbf_text); + QDIO_DBF_HEX0(0,setup,init_data->adapter_name,8); + sprintf(dbf_text,"qpff%4x",init_data->qib_param_field_format); + QDIO_DBF_TEXT0(0,setup,dbf_text); + QDIO_DBF_HEX0(0,setup,&init_data->qib_param_field,sizeof(char*)); + QDIO_DBF_HEX0(0,setup,&init_data->input_slib_elements,sizeof(long*)); + QDIO_DBF_HEX0(0,setup,&init_data->output_slib_elements,sizeof(long*)); + sprintf(dbf_text,"miit%4x",init_data->min_input_threshold); + QDIO_DBF_TEXT0(0,setup,dbf_text); + sprintf(dbf_text,"mait%4x",init_data->max_input_threshold); + QDIO_DBF_TEXT0(0,setup,dbf_text); + sprintf(dbf_text,"miot%4x",init_data->min_output_threshold); + QDIO_DBF_TEXT0(0,setup,dbf_text); + sprintf(dbf_text,"maot%4x",init_data->max_output_threshold); + QDIO_DBF_TEXT0(0,setup,dbf_text); + sprintf(dbf_text,"niq:%4x",init_data->no_input_qs); + QDIO_DBF_TEXT0(0,setup,dbf_text); + sprintf(dbf_text,"noq:%4x",init_data->no_output_qs); + QDIO_DBF_TEXT0(0,setup,dbf_text); + QDIO_DBF_HEX0(0,setup,&init_data->input_handler,sizeof(void*)); + QDIO_DBF_HEX0(0,setup,&init_data->output_handler,sizeof(void*)); + QDIO_DBF_HEX0(0,setup,&init_data->int_parm,sizeof(long)); + QDIO_DBF_HEX0(0,setup,&init_data->flags,sizeof(long)); + QDIO_DBF_HEX0(0,setup,&init_data->input_sbal_addr_array,sizeof(void*)); + QDIO_DBF_HEX0(0,setup,&init_data->output_sbal_addr_array,sizeof(void*)); +} + +static inline void +qdio_allocate_fill_input_desc(struct qdio_irq *irq_ptr, int i, int iqfmt) +{ + irq_ptr->input_qs[i]->is_iqdio_q = iqfmt; + irq_ptr->input_qs[i]->is_thinint_q = irq_ptr->is_thinint_irq; + + irq_ptr->qdr->qdf0[i].sliba=(unsigned long)(irq_ptr->input_qs[i]->slib); + + irq_ptr->qdr->qdf0[i].sla=(unsigned long)(irq_ptr->input_qs[i]->sl); + + irq_ptr->qdr->qdf0[i].slsba= + (unsigned long)(&irq_ptr->input_qs[i]->slsb.acc.val[0]); + + irq_ptr->qdr->qdf0[i].akey=QDIO_STORAGE_KEY; + irq_ptr->qdr->qdf0[i].bkey=QDIO_STORAGE_KEY; + irq_ptr->qdr->qdf0[i].ckey=QDIO_STORAGE_KEY; + irq_ptr->qdr->qdf0[i].dkey=QDIO_STORAGE_KEY; +} + +static inline void +qdio_allocate_fill_output_desc(struct qdio_irq *irq_ptr, int i, + int j, int iqfmt) +{ + irq_ptr->output_qs[i]->is_iqdio_q = iqfmt; + irq_ptr->output_qs[i]->is_thinint_q = irq_ptr->is_thinint_irq; + + irq_ptr->qdr->qdf0[i+j].sliba=(unsigned long)(irq_ptr->output_qs[i]->slib); + + irq_ptr->qdr->qdf0[i+j].sla=(unsigned long)(irq_ptr->output_qs[i]->sl); + + irq_ptr->qdr->qdf0[i+j].slsba= + (unsigned long)(&irq_ptr->output_qs[i]->slsb.acc.val[0]); + + irq_ptr->qdr->qdf0[i+j].akey=QDIO_STORAGE_KEY; + irq_ptr->qdr->qdf0[i+j].bkey=QDIO_STORAGE_KEY; + irq_ptr->qdr->qdf0[i+j].ckey=QDIO_STORAGE_KEY; + irq_ptr->qdr->qdf0[i+j].dkey=QDIO_STORAGE_KEY; +} + + +static inline void +qdio_initialize_set_siga_flags_input(struct qdio_irq *irq_ptr) +{ + int i; + + for (i=0;i<irq_ptr->no_input_qs;i++) { + irq_ptr->input_qs[i]->siga_sync= + irq_ptr->qdioac&CHSC_FLAG_SIGA_SYNC_NECESSARY; + irq_ptr->input_qs[i]->siga_in= + irq_ptr->qdioac&CHSC_FLAG_SIGA_INPUT_NECESSARY; + irq_ptr->input_qs[i]->siga_out= + irq_ptr->qdioac&CHSC_FLAG_SIGA_OUTPUT_NECESSARY; + irq_ptr->input_qs[i]->siga_sync_done_on_thinints= + irq_ptr->qdioac&CHSC_FLAG_SIGA_SYNC_DONE_ON_THININTS; + irq_ptr->input_qs[i]->hydra_gives_outbound_pcis= + irq_ptr->hydra_gives_outbound_pcis; + irq_ptr->input_qs[i]->siga_sync_done_on_outb_tis= + ((irq_ptr->qdioac& + (CHSC_FLAG_SIGA_SYNC_DONE_ON_OUTB_PCIS| + CHSC_FLAG_SIGA_SYNC_DONE_ON_THININTS))== + (CHSC_FLAG_SIGA_SYNC_DONE_ON_OUTB_PCIS| + CHSC_FLAG_SIGA_SYNC_DONE_ON_THININTS)); + + } +} + +static inline void +qdio_initialize_set_siga_flags_output(struct qdio_irq *irq_ptr) +{ + int i; + + for (i=0;i<irq_ptr->no_output_qs;i++) { + irq_ptr->output_qs[i]->siga_sync= + irq_ptr->qdioac&CHSC_FLAG_SIGA_SYNC_NECESSARY; + irq_ptr->output_qs[i]->siga_in= + irq_ptr->qdioac&CHSC_FLAG_SIGA_INPUT_NECESSARY; + irq_ptr->output_qs[i]->siga_out= + irq_ptr->qdioac&CHSC_FLAG_SIGA_OUTPUT_NECESSARY; + irq_ptr->output_qs[i]->siga_sync_done_on_thinints= + irq_ptr->qdioac&CHSC_FLAG_SIGA_SYNC_DONE_ON_THININTS; + irq_ptr->output_qs[i]->hydra_gives_outbound_pcis= + irq_ptr->hydra_gives_outbound_pcis; + irq_ptr->output_qs[i]->siga_sync_done_on_outb_tis= + ((irq_ptr->qdioac& + (CHSC_FLAG_SIGA_SYNC_DONE_ON_OUTB_PCIS| + CHSC_FLAG_SIGA_SYNC_DONE_ON_THININTS))== + (CHSC_FLAG_SIGA_SYNC_DONE_ON_OUTB_PCIS| + CHSC_FLAG_SIGA_SYNC_DONE_ON_THININTS)); + + } +} + +static inline int +qdio_establish_irq_check_for_errors(struct ccw_device *cdev, int cstat, + int dstat) +{ + char dbf_text[15]; + struct qdio_irq *irq_ptr; + + irq_ptr = cdev->private->qdio_data; + + if (cstat || (dstat & ~(DEV_STAT_CHN_END|DEV_STAT_DEV_END))) { + sprintf(dbf_text,"ick1%4x",irq_ptr->irq); + QDIO_DBF_TEXT2(1,trace,dbf_text); + QDIO_DBF_HEX2(0,trace,&dstat,sizeof(int)); + QDIO_DBF_HEX2(0,trace,&cstat,sizeof(int)); + QDIO_PRINT_ERR("received check condition on establish " \ + "queues on irq 0x%x (cs=x%x, ds=x%x).\n", + irq_ptr->irq,cstat,dstat); + qdio_set_state(irq_ptr,QDIO_IRQ_STATE_ERR); + } + + if (!(dstat & DEV_STAT_DEV_END)) { + QDIO_DBF_TEXT2(1,setup,"eq:no de"); + QDIO_DBF_HEX2(0,setup,&dstat, sizeof(dstat)); + QDIO_DBF_HEX2(0,setup,&cstat, sizeof(cstat)); + QDIO_PRINT_ERR("establish queues on irq %04x: didn't get " + "device end: dstat=%02x, cstat=%02x\n", + irq_ptr->irq, dstat, cstat); + qdio_set_state(irq_ptr, QDIO_IRQ_STATE_ERR); + return 1; + } + + if (dstat & ~(DEV_STAT_CHN_END|DEV_STAT_DEV_END)) { + QDIO_DBF_TEXT2(1,setup,"eq:badio"); + QDIO_DBF_HEX2(0,setup,&dstat, sizeof(dstat)); + QDIO_DBF_HEX2(0,setup,&cstat, sizeof(cstat)); + QDIO_PRINT_ERR("establish queues on irq %04x: got " + "the following devstat: dstat=%02x, " + "cstat=%02x\n", + irq_ptr->irq, dstat, cstat); + qdio_set_state(irq_ptr, QDIO_IRQ_STATE_ERR); + return 1; + } + return 0; +} + +static void +qdio_establish_handle_irq(struct ccw_device *cdev, int cstat, int dstat) +{ + struct qdio_irq *irq_ptr; + char dbf_text[15]; + + irq_ptr = cdev->private->qdio_data; + + sprintf(dbf_text,"qehi%4x",cdev->private->irq); + QDIO_DBF_TEXT0(0,setup,dbf_text); + QDIO_DBF_TEXT0(0,trace,dbf_text); + + if (qdio_establish_irq_check_for_errors(cdev, cstat, dstat)) { + ccw_device_set_timeout(cdev, 0); + return; + } + + qdio_set_state(irq_ptr,QDIO_IRQ_STATE_ESTABLISHED); + ccw_device_set_timeout(cdev, 0); +} + +int +qdio_initialize(struct qdio_initialize *init_data) +{ + int rc; + char dbf_text[15]; + + sprintf(dbf_text,"qini%4x",init_data->cdev->private->irq); + QDIO_DBF_TEXT0(0,setup,dbf_text); + QDIO_DBF_TEXT0(0,trace,dbf_text); + + rc = qdio_allocate(init_data); + if (rc == 0) { + rc = qdio_establish(init_data); + if (rc != 0) + qdio_free(init_data->cdev); + } + + return rc; +} + + +int +qdio_allocate(struct qdio_initialize *init_data) +{ + struct qdio_irq *irq_ptr; + char dbf_text[15]; + + sprintf(dbf_text,"qalc%4x",init_data->cdev->private->irq); + QDIO_DBF_TEXT0(0,setup,dbf_text); + QDIO_DBF_TEXT0(0,trace,dbf_text); + if ( (init_data->no_input_qs>QDIO_MAX_QUEUES_PER_IRQ) || + (init_data->no_output_qs>QDIO_MAX_QUEUES_PER_IRQ) || + ((init_data->no_input_qs) && (!init_data->input_handler)) || + ((init_data->no_output_qs) && (!init_data->output_handler)) ) + return -EINVAL; + + if (!init_data->input_sbal_addr_array) + return -EINVAL; + + if (!init_data->output_sbal_addr_array) + return -EINVAL; + + qdio_allocate_do_dbf(init_data); + + /* create irq */ + irq_ptr=kmalloc(sizeof(struct qdio_irq), GFP_KERNEL | GFP_DMA); + + QDIO_DBF_TEXT0(0,setup,"irq_ptr:"); + QDIO_DBF_HEX0(0,setup,&irq_ptr,sizeof(void*)); + + if (!irq_ptr) { + QDIO_PRINT_ERR("kmalloc of irq_ptr failed!\n"); + return -ENOMEM; + } + + memset(irq_ptr,0,sizeof(struct qdio_irq)); + + init_MUTEX(&irq_ptr->setting_up_sema); + + /* QDR must be in DMA area since CCW data address is only 32 bit */ + irq_ptr->qdr=kmalloc(sizeof(struct qdr), GFP_KERNEL | GFP_DMA); + if (!(irq_ptr->qdr)) { + kfree(irq_ptr); + QDIO_PRINT_ERR("kmalloc of irq_ptr->qdr failed!\n"); + return -ENOMEM; + } + QDIO_DBF_TEXT0(0,setup,"qdr:"); + QDIO_DBF_HEX0(0,setup,&irq_ptr->qdr,sizeof(void*)); + + if (qdio_alloc_qs(irq_ptr, + init_data->no_input_qs, + init_data->no_output_qs)) { + qdio_release_irq_memory(irq_ptr); + return -ENOMEM; + } + + init_data->cdev->private->qdio_data = irq_ptr; + + qdio_set_state(irq_ptr,QDIO_IRQ_STATE_INACTIVE); + + return 0; +} + +int qdio_fill_irq(struct qdio_initialize *init_data) +{ + int i; + char dbf_text[15]; + struct ciw *ciw; + int is_iqdio; + struct qdio_irq *irq_ptr; + + irq_ptr = init_data->cdev->private->qdio_data; + + memset(irq_ptr,0,((char*)&irq_ptr->qdr)-((char*)irq_ptr)); + + /* wipes qib.ac, required by ar7063 */ + memset(irq_ptr->qdr,0,sizeof(struct qdr)); + + irq_ptr->int_parm=init_data->int_parm; + + irq_ptr->irq = init_data->cdev->private->irq; + irq_ptr->no_input_qs=init_data->no_input_qs; + irq_ptr->no_output_qs=init_data->no_output_qs; + + if (init_data->q_format==QDIO_IQDIO_QFMT) { + irq_ptr->is_iqdio_irq=1; + irq_ptr->is_thinint_irq=1; + } else { + irq_ptr->is_iqdio_irq=0; + irq_ptr->is_thinint_irq=hydra_thinints; + } + sprintf(dbf_text,"is_i_t%1x%1x", + irq_ptr->is_iqdio_irq,irq_ptr->is_thinint_irq); + QDIO_DBF_TEXT2(0,setup,dbf_text); + + if (irq_ptr->is_thinint_irq) { + irq_ptr->dev_st_chg_ind=qdio_get_indicator(); + QDIO_DBF_HEX1(0,setup,&irq_ptr->dev_st_chg_ind,sizeof(void*)); + if (!irq_ptr->dev_st_chg_ind) { + QDIO_PRINT_WARN("no indicator location available " \ + "for irq 0x%x\n",irq_ptr->irq); + qdio_release_irq_memory(irq_ptr); + return -ENOBUFS; + } + } + + /* defaults */ + irq_ptr->equeue.cmd=DEFAULT_ESTABLISH_QS_CMD; + irq_ptr->equeue.count=DEFAULT_ESTABLISH_QS_COUNT; + irq_ptr->aqueue.cmd=DEFAULT_ACTIVATE_QS_CMD; + irq_ptr->aqueue.count=DEFAULT_ACTIVATE_QS_COUNT; + + qdio_fill_qs(irq_ptr, init_data->cdev, + init_data->no_input_qs, + init_data->no_output_qs, + init_data->input_handler, + init_data->output_handler,init_data->int_parm, + init_data->q_format,init_data->flags, + init_data->input_sbal_addr_array, + init_data->output_sbal_addr_array); + + if (!try_module_get(THIS_MODULE)) { + QDIO_PRINT_CRIT("try_module_get() failed!\n"); + qdio_release_irq_memory(irq_ptr); + return -EINVAL; + } + + qdio_fill_thresholds(irq_ptr,init_data->no_input_qs, + init_data->no_output_qs, + init_data->min_input_threshold, + init_data->max_input_threshold, + init_data->min_output_threshold, + init_data->max_output_threshold); + + /* fill in qdr */ + irq_ptr->qdr->qfmt=init_data->q_format; + irq_ptr->qdr->iqdcnt=init_data->no_input_qs; + irq_ptr->qdr->oqdcnt=init_data->no_output_qs; + irq_ptr->qdr->iqdsz=sizeof(struct qdesfmt0)/4; /* size in words */ + irq_ptr->qdr->oqdsz=sizeof(struct qdesfmt0)/4; + + irq_ptr->qdr->qiba=(unsigned long)&irq_ptr->qib; + irq_ptr->qdr->qkey=QDIO_STORAGE_KEY; + + /* fill in qib */ + irq_ptr->qib.qfmt=init_data->q_format; + if (init_data->no_input_qs) + irq_ptr->qib.isliba=(unsigned long)(irq_ptr->input_qs[0]->slib); + if (init_data->no_output_qs) + irq_ptr->qib.osliba=(unsigned long)(irq_ptr->output_qs[0]->slib); + memcpy(irq_ptr->qib.ebcnam,init_data->adapter_name,8); + + qdio_set_impl_params(irq_ptr,init_data->qib_param_field_format, + init_data->qib_param_field, + init_data->no_input_qs, + init_data->no_output_qs, + init_data->input_slib_elements, + init_data->output_slib_elements); + + /* first input descriptors, then output descriptors */ + is_iqdio = (init_data->q_format == QDIO_IQDIO_QFMT) ? 1 : 0; + for (i=0;i<init_data->no_input_qs;i++) + qdio_allocate_fill_input_desc(irq_ptr, i, is_iqdio); + + for (i=0;i<init_data->no_output_qs;i++) + qdio_allocate_fill_output_desc(irq_ptr, i, + init_data->no_input_qs, + is_iqdio); + + /* qdr, qib, sls, slsbs, slibs, sbales filled. */ + + /* get qdio commands */ + ciw = ccw_device_get_ciw(init_data->cdev, CIW_TYPE_EQUEUE); + if (!ciw) { + QDIO_DBF_TEXT2(1,setup,"no eq"); + QDIO_PRINT_INFO("No equeue CIW found for QDIO commands. " + "Trying to use default.\n"); + } else + irq_ptr->equeue = *ciw; + ciw = ccw_device_get_ciw(init_data->cdev, CIW_TYPE_AQUEUE); + if (!ciw) { + QDIO_DBF_TEXT2(1,setup,"no aq"); + QDIO_PRINT_INFO("No aqueue CIW found for QDIO commands. " + "Trying to use default.\n"); + } else + irq_ptr->aqueue = *ciw; + + /* Set new interrupt handler. */ + irq_ptr->original_int_handler = init_data->cdev->handler; + init_data->cdev->handler = qdio_handler; + + return 0; +} + +int +qdio_establish(struct qdio_initialize *init_data) +{ + struct qdio_irq *irq_ptr; + unsigned long saveflags; + int result, result2; + struct ccw_device *cdev; + char dbf_text[20]; + + cdev=init_data->cdev; + irq_ptr = cdev->private->qdio_data; + if (!irq_ptr) + return -EINVAL; + + if (cdev->private->state != DEV_STATE_ONLINE) + return -EINVAL; + + down(&irq_ptr->setting_up_sema); + + qdio_fill_irq(init_data); + + /* the thinint CHSC stuff */ + if (irq_ptr->is_thinint_irq) { + + result = tiqdio_set_subchannel_ind(irq_ptr,0); + if (result) { + up(&irq_ptr->setting_up_sema); + qdio_shutdown(cdev, QDIO_FLAG_CLEANUP_USING_CLEAR); + return result; + } + tiqdio_set_delay_target(irq_ptr,TIQDIO_DELAY_TARGET); + } + + sprintf(dbf_text,"qest%4x",cdev->private->irq); + QDIO_DBF_TEXT0(0,setup,dbf_text); + QDIO_DBF_TEXT0(0,trace,dbf_text); + + /* establish q */ + irq_ptr->ccw.cmd_code=irq_ptr->equeue.cmd; + irq_ptr->ccw.flags=CCW_FLAG_SLI; + irq_ptr->ccw.count=irq_ptr->equeue.count; + irq_ptr->ccw.cda=QDIO_GET_ADDR(irq_ptr->qdr); + + spin_lock_irqsave(get_ccwdev_lock(cdev),saveflags); + + ccw_device_set_options(cdev, 0); + result=ccw_device_start_timeout(cdev,&irq_ptr->ccw, + QDIO_DOING_ESTABLISH,0, 0, + QDIO_ESTABLISH_TIMEOUT); + if (result) { + result2=ccw_device_start_timeout(cdev,&irq_ptr->ccw, + QDIO_DOING_ESTABLISH,0,0, + QDIO_ESTABLISH_TIMEOUT); + sprintf(dbf_text,"eq:io%4x",result); + QDIO_DBF_TEXT2(1,setup,dbf_text); + if (result2) { + sprintf(dbf_text,"eq:io%4x",result); + QDIO_DBF_TEXT2(1,setup,dbf_text); + } + QDIO_PRINT_WARN("establish queues on irq %04x: do_IO " \ + "returned %i, next try returned %i\n", + irq_ptr->irq,result,result2); + result=result2; + if (result) + ccw_device_set_timeout(cdev, 0); + } + + spin_unlock_irqrestore(get_ccwdev_lock(cdev),saveflags); + + if (result) { + up(&irq_ptr->setting_up_sema); + qdio_shutdown(cdev,QDIO_FLAG_CLEANUP_USING_CLEAR); + return result; + } + + wait_event_interruptible_timeout(cdev->private->wait_q, + irq_ptr->state == QDIO_IRQ_STATE_ESTABLISHED || + irq_ptr->state == QDIO_IRQ_STATE_ERR, + QDIO_ESTABLISH_TIMEOUT); + + if (irq_ptr->state == QDIO_IRQ_STATE_ESTABLISHED) + result = 0; + else { + up(&irq_ptr->setting_up_sema); + qdio_shutdown(cdev, QDIO_FLAG_CLEANUP_USING_CLEAR); + return -EIO; + } + + irq_ptr->qdioac=qdio_check_siga_needs(irq_ptr->irq); + /* if this gets set once, we're running under VM and can omit SVSes */ + if (irq_ptr->qdioac&CHSC_FLAG_SIGA_SYNC_NECESSARY) + omit_svs=1; + + sprintf(dbf_text,"qdioac%2x",irq_ptr->qdioac); + QDIO_DBF_TEXT2(0,setup,dbf_text); + + sprintf(dbf_text,"qib ac%2x",irq_ptr->qib.ac); + QDIO_DBF_TEXT2(0,setup,dbf_text); + + irq_ptr->hydra_gives_outbound_pcis= + irq_ptr->qib.ac&QIB_AC_OUTBOUND_PCI_SUPPORTED; + irq_ptr->sync_done_on_outb_pcis= + irq_ptr->qdioac&CHSC_FLAG_SIGA_SYNC_DONE_ON_OUTB_PCIS; + + qdio_initialize_set_siga_flags_input(irq_ptr); + qdio_initialize_set_siga_flags_output(irq_ptr); + + up(&irq_ptr->setting_up_sema); + + return result; + +} + +int +qdio_activate(struct ccw_device *cdev, int flags) +{ + struct qdio_irq *irq_ptr; + int i,result=0,result2; + unsigned long saveflags; + char dbf_text[20]; /* see qdio_initialize */ + + irq_ptr = cdev->private->qdio_data; + if (!irq_ptr) + return -ENODEV; + + if (cdev->private->state != DEV_STATE_ONLINE) + return -EINVAL; + + down(&irq_ptr->setting_up_sema); + if (irq_ptr->state==QDIO_IRQ_STATE_INACTIVE) { + result=-EBUSY; + goto out; + } + + sprintf(dbf_text,"qact%4x", irq_ptr->irq); + QDIO_DBF_TEXT2(0,setup,dbf_text); + QDIO_DBF_TEXT2(0,trace,dbf_text); + + /* activate q */ + irq_ptr->ccw.cmd_code=irq_ptr->aqueue.cmd; + irq_ptr->ccw.flags=CCW_FLAG_SLI; + irq_ptr->ccw.count=irq_ptr->aqueue.count; + irq_ptr->ccw.cda=QDIO_GET_ADDR(0); + + spin_lock_irqsave(get_ccwdev_lock(cdev),saveflags); + + ccw_device_set_timeout(cdev, 0); + ccw_device_set_options(cdev, CCWDEV_REPORT_ALL); + result=ccw_device_start(cdev,&irq_ptr->ccw,QDIO_DOING_ACTIVATE, + 0, DOIO_DENY_PREFETCH); + if (result) { + result2=ccw_device_start(cdev,&irq_ptr->ccw, + QDIO_DOING_ACTIVATE,0,0); + sprintf(dbf_text,"aq:io%4x",result); + QDIO_DBF_TEXT2(1,setup,dbf_text); + if (result2) { + sprintf(dbf_text,"aq:io%4x",result); + QDIO_DBF_TEXT2(1,setup,dbf_text); + } + QDIO_PRINT_WARN("activate queues on irq %04x: do_IO " \ + "returned %i, next try returned %i\n", + irq_ptr->irq,result,result2); + result=result2; + } + + spin_unlock_irqrestore(get_ccwdev_lock(cdev),saveflags); + if (result) + goto out; + + for (i=0;i<irq_ptr->no_input_qs;i++) { + if (irq_ptr->is_thinint_irq) { + /* + * that way we know, that, if we will get interrupted + * by tiqdio_inbound_processing, qdio_unmark_q will + * not be called + */ + qdio_reserve_q(irq_ptr->input_qs[i]); + qdio_mark_tiq(irq_ptr->input_qs[i]); + qdio_release_q(irq_ptr->input_qs[i]); + } + } + + if (flags&QDIO_FLAG_NO_INPUT_INTERRUPT_CONTEXT) { + for (i=0;i<irq_ptr->no_input_qs;i++) { + irq_ptr->input_qs[i]->is_input_q|= + QDIO_FLAG_NO_INPUT_INTERRUPT_CONTEXT; + } + } + + wait_event_interruptible_timeout(cdev->private->wait_q, + ((irq_ptr->state == + QDIO_IRQ_STATE_STOPPED) || + (irq_ptr->state == + QDIO_IRQ_STATE_ERR)), + QDIO_ACTIVATE_TIMEOUT); + + switch (irq_ptr->state) { + case QDIO_IRQ_STATE_STOPPED: + case QDIO_IRQ_STATE_ERR: + up(&irq_ptr->setting_up_sema); + qdio_shutdown(cdev, QDIO_FLAG_CLEANUP_USING_CLEAR); + down(&irq_ptr->setting_up_sema); + result = -EIO; + break; + default: + qdio_set_state(irq_ptr, QDIO_IRQ_STATE_ACTIVE); + result = 0; + } + out: + up(&irq_ptr->setting_up_sema); + + return result; +} + +/* buffers filled forwards again to make Rick happy */ +static inline void +qdio_do_qdio_fill_input(struct qdio_q *q, unsigned int qidx, + unsigned int count, struct qdio_buffer *buffers) +{ + for (;;) { + set_slsb(&q->slsb.acc.val[qidx],SLSB_CU_INPUT_EMPTY); + count--; + if (!count) break; + qidx=(qidx+1)&(QDIO_MAX_BUFFERS_PER_Q-1); + } + + /* not necessary, as the queues are synced during the SIGA read */ + /*SYNC_MEMORY;*/ +} + +static inline void +qdio_do_qdio_fill_output(struct qdio_q *q, unsigned int qidx, + unsigned int count, struct qdio_buffer *buffers) +{ + for (;;) { + set_slsb(&q->slsb.acc.val[qidx],SLSB_CU_OUTPUT_PRIMED); + count--; + if (!count) break; + qidx=(qidx+1)&(QDIO_MAX_BUFFERS_PER_Q-1); + } + + /* SIGA write will sync the queues */ + /*SYNC_MEMORY;*/ +} + +static inline void +do_qdio_handle_inbound(struct qdio_q *q, unsigned int callflags, + unsigned int qidx, unsigned int count, + struct qdio_buffer *buffers) +{ + int used_elements; + + /* This is the inbound handling of queues */ + used_elements=atomic_add_return(count, &q->number_of_buffers_used) - count; + + qdio_do_qdio_fill_input(q,qidx,count,buffers); + + if ((used_elements+count==QDIO_MAX_BUFFERS_PER_Q)&& + (callflags&QDIO_FLAG_UNDER_INTERRUPT)) + atomic_swap(&q->polling,0); + + if (used_elements) + return; + if (callflags&QDIO_FLAG_DONT_SIGA) + return; + if (q->siga_in) { + int result; + + result=qdio_siga_input(q); + if (result) { + if (q->siga_error) + q->error_status_flags|= + QDIO_STATUS_MORE_THAN_ONE_SIGA_ERROR; + q->error_status_flags|=QDIO_STATUS_LOOK_FOR_ERROR; + q->siga_error=result; + } + } + + qdio_mark_q(q); +} + +static inline void +do_qdio_handle_outbound(struct qdio_q *q, unsigned int callflags, + unsigned int qidx, unsigned int count, + struct qdio_buffer *buffers) +{ + int used_elements; + + /* This is the outbound handling of queues */ +#ifdef QDIO_PERFORMANCE_STATS + perf_stats.start_time_outbound=NOW; +#endif /* QDIO_PERFORMANCE_STATS */ + + qdio_do_qdio_fill_output(q,qidx,count,buffers); + + used_elements=atomic_add_return(count, &q->number_of_buffers_used) - count; + + if (callflags&QDIO_FLAG_DONT_SIGA) { +#ifdef QDIO_PERFORMANCE_STATS + perf_stats.outbound_time+=NOW-perf_stats.start_time_outbound; + perf_stats.outbound_cnt++; +#endif /* QDIO_PERFORMANCE_STATS */ + return; + } + if (q->is_iqdio_q) { + /* one siga for every sbal */ + while (count--) + qdio_kick_outbound_q(q); + + __qdio_outbound_processing(q); + } else { + /* under VM, we do a SIGA sync unconditionally */ + SYNC_MEMORY; + else { + /* + * w/o shadow queues (else branch of + * SYNC_MEMORY :-/ ), we try to + * fast-requeue buffers + */ + if (q->slsb.acc.val[(qidx+QDIO_MAX_BUFFERS_PER_Q-1) + &(QDIO_MAX_BUFFERS_PER_Q-1)]!= + SLSB_CU_OUTPUT_PRIMED) { + qdio_kick_outbound_q(q); + } else { + QDIO_DBF_TEXT3(0,trace, "fast-req"); +#ifdef QDIO_PERFORMANCE_STATS + perf_stats.fast_reqs++; +#endif /* QDIO_PERFORMANCE_STATS */ + } + } + /* + * only marking the q could take too long, + * the upper layer module could do a lot of + * traffic in that time + */ + __qdio_outbound_processing(q); + } + +#ifdef QDIO_PERFORMANCE_STATS + perf_stats.outbound_time+=NOW-perf_stats.start_time_outbound; + perf_stats.outbound_cnt++; +#endif /* QDIO_PERFORMANCE_STATS */ +} + +/* count must be 1 in iqdio */ +int +do_QDIO(struct ccw_device *cdev,unsigned int callflags, + unsigned int queue_number, unsigned int qidx, + unsigned int count,struct qdio_buffer *buffers) +{ + struct qdio_irq *irq_ptr; +#ifdef CONFIG_QDIO_DEBUG + char dbf_text[20]; + + sprintf(dbf_text,"doQD%04x",cdev->private->irq); + QDIO_DBF_TEXT3(0,trace,dbf_text); +#endif /* CONFIG_QDIO_DEBUG */ + + if ( (qidx>QDIO_MAX_BUFFERS_PER_Q) || + (count>QDIO_MAX_BUFFERS_PER_Q) || + (queue_number>QDIO_MAX_QUEUES_PER_IRQ) ) + return -EINVAL; + + if (count==0) + return 0; + + irq_ptr = cdev->private->qdio_data; + if (!irq_ptr) + return -ENODEV; + +#ifdef CONFIG_QDIO_DEBUG + if (callflags&QDIO_FLAG_SYNC_INPUT) + QDIO_DBF_HEX3(0,trace,&irq_ptr->input_qs[queue_number], + sizeof(void*)); + else + QDIO_DBF_HEX3(0,trace,&irq_ptr->output_qs[queue_number], + sizeof(void*)); + sprintf(dbf_text,"flag%04x",callflags); + QDIO_DBF_TEXT3(0,trace,dbf_text); + sprintf(dbf_text,"qi%02xct%02x",qidx,count); + QDIO_DBF_TEXT3(0,trace,dbf_text); +#endif /* CONFIG_QDIO_DEBUG */ + + if (irq_ptr->state!=QDIO_IRQ_STATE_ACTIVE) + return -EBUSY; + + if (callflags&QDIO_FLAG_SYNC_INPUT) + do_qdio_handle_inbound(irq_ptr->input_qs[queue_number], + callflags, qidx, count, buffers); + else if (callflags&QDIO_FLAG_SYNC_OUTPUT) + do_qdio_handle_outbound(irq_ptr->output_qs[queue_number], + callflags, qidx, count, buffers); + else { + QDIO_DBF_TEXT3(1,trace,"doQD:inv"); + return -EINVAL; + } + return 0; +} + +#ifdef QDIO_PERFORMANCE_STATS +static int +qdio_perf_procfile_read(char *buffer, char **buffer_location, off_t offset, + int buffer_length, int *eof, void *data) +{ + int c=0; + + /* we are always called with buffer_length=4k, so we all + deliver on the first read */ + if (offset>0) + return 0; + +#define _OUTP_IT(x...) c+=sprintf(buffer+c,x) + _OUTP_IT("i_p_nc/c=%lu/%lu\n",i_p_nc,i_p_c); + _OUTP_IT("ii_p_nc/c=%lu/%lu\n",ii_p_nc,ii_p_c); + _OUTP_IT("o_p_nc/c=%lu/%lu\n",o_p_nc,o_p_c); + _OUTP_IT("Number of tasklet runs (total) : %u\n", + perf_stats.tl_runs); + _OUTP_IT("\n"); + _OUTP_IT("Number of SIGA sync's issued : %u\n", + perf_stats.siga_syncs); + _OUTP_IT("Number of SIGA in's issued : %u\n", + perf_stats.siga_ins); + _OUTP_IT("Number of SIGA out's issued : %u\n", + perf_stats.siga_outs); + _OUTP_IT("Number of PCIs caught : %u\n", + perf_stats.pcis); + _OUTP_IT("Number of adapter interrupts caught : %u\n", + perf_stats.thinints); + _OUTP_IT("Number of fast requeues (outg. SBALs w/o SIGA) : %u\n", + perf_stats.fast_reqs); + _OUTP_IT("\n"); + _OUTP_IT("Total time of all inbound actions (us) incl. UL : %u\n", + perf_stats.inbound_time); + _OUTP_IT("Number of inbound transfers : %u\n", + perf_stats.inbound_cnt); + _OUTP_IT("Total time of all outbound do_QDIOs (us) : %u\n", + perf_stats.outbound_time); + _OUTP_IT("Number of do_QDIOs outbound : %u\n", + perf_stats.outbound_cnt); + _OUTP_IT("\n"); + + return c; +} + +static struct proc_dir_entry *qdio_perf_proc_file; +#endif /* QDIO_PERFORMANCE_STATS */ + +static void +qdio_add_procfs_entry(void) +{ +#ifdef QDIO_PERFORMANCE_STATS + proc_perf_file_registration=0; + qdio_perf_proc_file=create_proc_entry(QDIO_PERF, + S_IFREG|0444,&proc_root); + if (qdio_perf_proc_file) { + qdio_perf_proc_file->read_proc=&qdio_perf_procfile_read; + } else proc_perf_file_registration=-1; + + if (proc_perf_file_registration) + QDIO_PRINT_WARN("was not able to register perf. " \ + "proc-file (%i).\n", + proc_perf_file_registration); +#endif /* QDIO_PERFORMANCE_STATS */ +} + +static void +qdio_remove_procfs_entry(void) +{ +#ifdef QDIO_PERFORMANCE_STATS + perf_stats.tl_runs=0; + + if (!proc_perf_file_registration) /* means if it went ok earlier */ + remove_proc_entry(QDIO_PERF,&proc_root); +#endif /* QDIO_PERFORMANCE_STATS */ +} + +static void +tiqdio_register_thinints(void) +{ + char dbf_text[20]; + register_thinint_result= + s390_register_adapter_interrupt(&tiqdio_thinint_handler); + if (register_thinint_result) { + sprintf(dbf_text,"regthn%x",(register_thinint_result&0xff)); + QDIO_DBF_TEXT0(0,setup,dbf_text); + QDIO_PRINT_ERR("failed to register adapter handler " \ + "(rc=%i).\nAdapter interrupts might " \ + "not work. Continuing.\n", + register_thinint_result); + } +} + +static void +tiqdio_unregister_thinints(void) +{ + if (!register_thinint_result) + s390_unregister_adapter_interrupt(&tiqdio_thinint_handler); +} + +static int +qdio_get_qdio_memory(void) +{ + int i; + indicator_used[0]=1; + + for (i=1;i<INDICATORS_PER_CACHELINE;i++) + indicator_used[i]=0; + indicators=(__u32*)kmalloc(sizeof(__u32)*(INDICATORS_PER_CACHELINE), + GFP_KERNEL); + if (!indicators) return -ENOMEM; + memset(indicators,0,sizeof(__u32)*(INDICATORS_PER_CACHELINE)); + return 0; +} + +static void +qdio_release_qdio_memory(void) +{ + if (indicators) + kfree(indicators); +} + +static void +qdio_unregister_dbf_views(void) +{ + if (qdio_dbf_setup) + debug_unregister(qdio_dbf_setup); + if (qdio_dbf_sbal) + debug_unregister(qdio_dbf_sbal); + if (qdio_dbf_sense) + debug_unregister(qdio_dbf_sense); + if (qdio_dbf_trace) + debug_unregister(qdio_dbf_trace); +#ifdef CONFIG_QDIO_DEBUG + if (qdio_dbf_slsb_out) + debug_unregister(qdio_dbf_slsb_out); + if (qdio_dbf_slsb_in) + debug_unregister(qdio_dbf_slsb_in); +#endif /* CONFIG_QDIO_DEBUG */ +} + +static int +qdio_register_dbf_views(void) +{ + qdio_dbf_setup=debug_register(QDIO_DBF_SETUP_NAME, + QDIO_DBF_SETUP_INDEX, + QDIO_DBF_SETUP_NR_AREAS, + QDIO_DBF_SETUP_LEN); + if (!qdio_dbf_setup) + goto oom; + debug_register_view(qdio_dbf_setup,&debug_hex_ascii_view); + debug_set_level(qdio_dbf_setup,QDIO_DBF_SETUP_LEVEL); + + qdio_dbf_sbal=debug_register(QDIO_DBF_SBAL_NAME, + QDIO_DBF_SBAL_INDEX, + QDIO_DBF_SBAL_NR_AREAS, + QDIO_DBF_SBAL_LEN); + if (!qdio_dbf_sbal) + goto oom; + + debug_register_view(qdio_dbf_sbal,&debug_hex_ascii_view); + debug_set_level(qdio_dbf_sbal,QDIO_DBF_SBAL_LEVEL); + + qdio_dbf_sense=debug_register(QDIO_DBF_SENSE_NAME, + QDIO_DBF_SENSE_INDEX, + QDIO_DBF_SENSE_NR_AREAS, + QDIO_DBF_SENSE_LEN); + if (!qdio_dbf_sense) + goto oom; + + debug_register_view(qdio_dbf_sense,&debug_hex_ascii_view); + debug_set_level(qdio_dbf_sense,QDIO_DBF_SENSE_LEVEL); + + qdio_dbf_trace=debug_register(QDIO_DBF_TRACE_NAME, + QDIO_DBF_TRACE_INDEX, + QDIO_DBF_TRACE_NR_AREAS, + QDIO_DBF_TRACE_LEN); + if (!qdio_dbf_trace) + goto oom; + + debug_register_view(qdio_dbf_trace,&debug_hex_ascii_view); + debug_set_level(qdio_dbf_trace,QDIO_DBF_TRACE_LEVEL); + +#ifdef CONFIG_QDIO_DEBUG + qdio_dbf_slsb_out=debug_register(QDIO_DBF_SLSB_OUT_NAME, + QDIO_DBF_SLSB_OUT_INDEX, + QDIO_DBF_SLSB_OUT_NR_AREAS, + QDIO_DBF_SLSB_OUT_LEN); + if (!qdio_dbf_slsb_out) + goto oom; + debug_register_view(qdio_dbf_slsb_out,&debug_hex_ascii_view); + debug_set_level(qdio_dbf_slsb_out,QDIO_DBF_SLSB_OUT_LEVEL); + + qdio_dbf_slsb_in=debug_register(QDIO_DBF_SLSB_IN_NAME, + QDIO_DBF_SLSB_IN_INDEX, + QDIO_DBF_SLSB_IN_NR_AREAS, + QDIO_DBF_SLSB_IN_LEN); + if (!qdio_dbf_slsb_in) + goto oom; + debug_register_view(qdio_dbf_slsb_in,&debug_hex_ascii_view); + debug_set_level(qdio_dbf_slsb_in,QDIO_DBF_SLSB_IN_LEVEL); +#endif /* CONFIG_QDIO_DEBUG */ + return 0; +oom: + QDIO_PRINT_ERR("not enough memory for dbf.\n"); + qdio_unregister_dbf_views(); + return -ENOMEM; +} + +static int __init +init_QDIO(void) +{ + int res; +#ifdef QDIO_PERFORMANCE_STATS + void *ptr; +#endif /* QDIO_PERFORMANCE_STATS */ + + printk("qdio: loading %s\n",version); + + res=qdio_get_qdio_memory(); + if (res) + return res; + + res = qdio_register_dbf_views(); + if (res) + return res; + + QDIO_DBF_TEXT0(0,setup,"initQDIO"); + +#ifdef QDIO_PERFORMANCE_STATS + memset((void*)&perf_stats,0,sizeof(perf_stats)); + QDIO_DBF_TEXT0(0,setup,"perfstat"); + ptr=&perf_stats; + QDIO_DBF_HEX0(0,setup,&ptr,sizeof(void*)); +#endif /* QDIO_PERFORMANCE_STATS */ + + qdio_add_procfs_entry(); + + if (tiqdio_check_chsc_availability()) + QDIO_PRINT_ERR("Not all CHSCs supported. Continuing.\n"); + + tiqdio_register_thinints(); + + return 0; + } + +static void __exit +cleanup_QDIO(void) +{ + tiqdio_unregister_thinints(); + qdio_remove_procfs_entry(); + qdio_release_qdio_memory(); + qdio_unregister_dbf_views(); + + printk("qdio: %s: module removed\n",version); +} + +module_init(init_QDIO); +module_exit(cleanup_QDIO); + +EXPORT_SYMBOL(qdio_allocate); +EXPORT_SYMBOL(qdio_establish); +EXPORT_SYMBOL(qdio_initialize); +EXPORT_SYMBOL(qdio_activate); +EXPORT_SYMBOL(do_QDIO); +EXPORT_SYMBOL(qdio_shutdown); +EXPORT_SYMBOL(qdio_free); +EXPORT_SYMBOL(qdio_cleanup); +EXPORT_SYMBOL(qdio_synchronize); diff --git a/drivers/s390/cio/qdio.h b/drivers/s390/cio/qdio.h new file mode 100644 index 000000000000..9ad14db24143 --- /dev/null +++ b/drivers/s390/cio/qdio.h @@ -0,0 +1,648 @@ +#ifndef _CIO_QDIO_H +#define _CIO_QDIO_H + +#define VERSION_CIO_QDIO_H "$Revision: 1.26 $" + +#ifdef CONFIG_QDIO_DEBUG +#define QDIO_VERBOSE_LEVEL 9 +#else /* CONFIG_QDIO_DEBUG */ +#define QDIO_VERBOSE_LEVEL 5 +#endif /* CONFIG_QDIO_DEBUG */ + +#define QDIO_USE_PROCESSING_STATE + +#ifdef CONFIG_QDIO_PERF_STATS +#define QDIO_PERFORMANCE_STATS +#endif /* CONFIG_QDIO_PERF_STATS */ + +#define QDIO_MINIMAL_BH_RELIEF_TIME 16 +#define QDIO_TIMER_POLL_VALUE 1 +#define IQDIO_TIMER_POLL_VALUE 1 + +/* + * unfortunately this can't be (QDIO_MAX_BUFFERS_PER_Q*4/3) or so -- as + * we never know, whether we'll get initiative again, e.g. to give the + * transmit skb's back to the stack, however the stack may be waiting for + * them... therefore we define 4 as threshold to start polling (which + * will stop as soon as the asynchronous queue catches up) + * btw, this only applies to the asynchronous HiperSockets queue + */ +#define IQDIO_FILL_LEVEL_TO_POLL 4 + +#define TIQDIO_THININT_ISC 3 +#define TIQDIO_DELAY_TARGET 0 +#define QDIO_BUSY_BIT_PATIENCE 100 /* in microsecs */ +#define QDIO_BUSY_BIT_GIVE_UP 10000000 /* 10 seconds */ +#define IQDIO_GLOBAL_LAPS 2 /* GLOBAL_LAPS are not used as we */ +#define IQDIO_GLOBAL_LAPS_INT 1 /* don't global summary */ +#define IQDIO_LOCAL_LAPS 4 +#define IQDIO_LOCAL_LAPS_INT 1 +#define IQDIO_GLOBAL_SUMMARY_CC_MASK 2 +/*#define IQDIO_IQDC_INT_PARM 0x1234*/ + +#define QDIO_Q_LAPS 5 + +#define QDIO_STORAGE_KEY 0 + +#define L2_CACHELINE_SIZE 256 +#define INDICATORS_PER_CACHELINE (L2_CACHELINE_SIZE/sizeof(__u32)) + +#define QDIO_PERF "qdio_perf" + +/* must be a power of 2 */ +/*#define QDIO_STATS_NUMBER 4 + +#define QDIO_STATS_CLASSES 2 +#define QDIO_STATS_COUNT_NEEDED 2*/ + +#define QDIO_NO_USE_COUNT_TIMEOUT (1*HZ) /* wait for 1 sec on each q before + exiting without having use_count + of the queue to 0 */ + +#define QDIO_ESTABLISH_TIMEOUT (1*HZ) +#define QDIO_ACTIVATE_TIMEOUT ((5*HZ)>>10) +#define QDIO_CLEANUP_CLEAR_TIMEOUT (20*HZ) +#define QDIO_CLEANUP_HALT_TIMEOUT (10*HZ) + +enum qdio_irq_states { + QDIO_IRQ_STATE_INACTIVE, + QDIO_IRQ_STATE_ESTABLISHED, + QDIO_IRQ_STATE_ACTIVE, + QDIO_IRQ_STATE_STOPPED, + QDIO_IRQ_STATE_CLEANUP, + QDIO_IRQ_STATE_ERR, + NR_QDIO_IRQ_STATES, +}; + +/* used as intparm in do_IO: */ +#define QDIO_DOING_SENSEID 0 +#define QDIO_DOING_ESTABLISH 1 +#define QDIO_DOING_ACTIVATE 2 +#define QDIO_DOING_CLEANUP 3 + +/************************* DEBUG FACILITY STUFF *********************/ + +#define QDIO_DBF_HEX(ex,name,level,addr,len) \ + do { \ + if (ex) \ + debug_exception(qdio_dbf_##name,level,(void*)(addr),len); \ + else \ + debug_event(qdio_dbf_##name,level,(void*)(addr),len); \ + } while (0) +#define QDIO_DBF_TEXT(ex,name,level,text) \ + do { \ + if (ex) \ + debug_text_exception(qdio_dbf_##name,level,text); \ + else \ + debug_text_event(qdio_dbf_##name,level,text); \ + } while (0) + + +#define QDIO_DBF_HEX0(ex,name,addr,len) QDIO_DBF_HEX(ex,name,0,addr,len) +#define QDIO_DBF_HEX1(ex,name,addr,len) QDIO_DBF_HEX(ex,name,1,addr,len) +#define QDIO_DBF_HEX2(ex,name,addr,len) QDIO_DBF_HEX(ex,name,2,addr,len) +#ifdef CONFIG_QDIO_DEBUG +#define QDIO_DBF_HEX3(ex,name,addr,len) QDIO_DBF_HEX(ex,name,3,addr,len) +#define QDIO_DBF_HEX4(ex,name,addr,len) QDIO_DBF_HEX(ex,name,4,addr,len) +#define QDIO_DBF_HEX5(ex,name,addr,len) QDIO_DBF_HEX(ex,name,5,addr,len) +#define QDIO_DBF_HEX6(ex,name,addr,len) QDIO_DBF_HEX(ex,name,6,addr,len) +#else /* CONFIG_QDIO_DEBUG */ +#define QDIO_DBF_HEX3(ex,name,addr,len) do {} while (0) +#define QDIO_DBF_HEX4(ex,name,addr,len) do {} while (0) +#define QDIO_DBF_HEX5(ex,name,addr,len) do {} while (0) +#define QDIO_DBF_HEX6(ex,name,addr,len) do {} while (0) +#endif /* CONFIG_QDIO_DEBUG */ + +#define QDIO_DBF_TEXT0(ex,name,text) QDIO_DBF_TEXT(ex,name,0,text) +#define QDIO_DBF_TEXT1(ex,name,text) QDIO_DBF_TEXT(ex,name,1,text) +#define QDIO_DBF_TEXT2(ex,name,text) QDIO_DBF_TEXT(ex,name,2,text) +#ifdef CONFIG_QDIO_DEBUG +#define QDIO_DBF_TEXT3(ex,name,text) QDIO_DBF_TEXT(ex,name,3,text) +#define QDIO_DBF_TEXT4(ex,name,text) QDIO_DBF_TEXT(ex,name,4,text) +#define QDIO_DBF_TEXT5(ex,name,text) QDIO_DBF_TEXT(ex,name,5,text) +#define QDIO_DBF_TEXT6(ex,name,text) QDIO_DBF_TEXT(ex,name,6,text) +#else /* CONFIG_QDIO_DEBUG */ +#define QDIO_DBF_TEXT3(ex,name,text) do {} while (0) +#define QDIO_DBF_TEXT4(ex,name,text) do {} while (0) +#define QDIO_DBF_TEXT5(ex,name,text) do {} while (0) +#define QDIO_DBF_TEXT6(ex,name,text) do {} while (0) +#endif /* CONFIG_QDIO_DEBUG */ + +#define QDIO_DBF_SETUP_NAME "qdio_setup" +#define QDIO_DBF_SETUP_LEN 8 +#define QDIO_DBF_SETUP_INDEX 2 +#define QDIO_DBF_SETUP_NR_AREAS 1 +#ifdef CONFIG_QDIO_DEBUG +#define QDIO_DBF_SETUP_LEVEL 6 +#else /* CONFIG_QDIO_DEBUG */ +#define QDIO_DBF_SETUP_LEVEL 2 +#endif /* CONFIG_QDIO_DEBUG */ + +#define QDIO_DBF_SBAL_NAME "qdio_labs" /* sbal */ +#define QDIO_DBF_SBAL_LEN 256 +#define QDIO_DBF_SBAL_INDEX 2 +#define QDIO_DBF_SBAL_NR_AREAS 2 +#ifdef CONFIG_QDIO_DEBUG +#define QDIO_DBF_SBAL_LEVEL 6 +#else /* CONFIG_QDIO_DEBUG */ +#define QDIO_DBF_SBAL_LEVEL 2 +#endif /* CONFIG_QDIO_DEBUG */ + +#define QDIO_DBF_TRACE_NAME "qdio_trace" +#define QDIO_DBF_TRACE_LEN 8 +#define QDIO_DBF_TRACE_NR_AREAS 2 +#ifdef CONFIG_QDIO_DEBUG +#define QDIO_DBF_TRACE_INDEX 4 +#define QDIO_DBF_TRACE_LEVEL 4 /* -------- could be even more verbose here */ +#else /* CONFIG_QDIO_DEBUG */ +#define QDIO_DBF_TRACE_INDEX 2 +#define QDIO_DBF_TRACE_LEVEL 2 +#endif /* CONFIG_QDIO_DEBUG */ + +#define QDIO_DBF_SENSE_NAME "qdio_sense" +#define QDIO_DBF_SENSE_LEN 64 +#define QDIO_DBF_SENSE_INDEX 1 +#define QDIO_DBF_SENSE_NR_AREAS 1 +#ifdef CONFIG_QDIO_DEBUG +#define QDIO_DBF_SENSE_LEVEL 6 +#else /* CONFIG_QDIO_DEBUG */ +#define QDIO_DBF_SENSE_LEVEL 2 +#endif /* CONFIG_QDIO_DEBUG */ + +#ifdef CONFIG_QDIO_DEBUG +#define QDIO_TRACE_QTYPE QDIO_ZFCP_QFMT + +#define QDIO_DBF_SLSB_OUT_NAME "qdio_slsb_out" +#define QDIO_DBF_SLSB_OUT_LEN QDIO_MAX_BUFFERS_PER_Q +#define QDIO_DBF_SLSB_OUT_INDEX 8 +#define QDIO_DBF_SLSB_OUT_NR_AREAS 1 +#define QDIO_DBF_SLSB_OUT_LEVEL 6 + +#define QDIO_DBF_SLSB_IN_NAME "qdio_slsb_in" +#define QDIO_DBF_SLSB_IN_LEN QDIO_MAX_BUFFERS_PER_Q +#define QDIO_DBF_SLSB_IN_INDEX 8 +#define QDIO_DBF_SLSB_IN_NR_AREAS 1 +#define QDIO_DBF_SLSB_IN_LEVEL 6 +#endif /* CONFIG_QDIO_DEBUG */ + +#define QDIO_PRINTK_HEADER QDIO_NAME ": " + +#if QDIO_VERBOSE_LEVEL>8 +#define QDIO_PRINT_STUPID(x...) printk( KERN_DEBUG QDIO_PRINTK_HEADER x) +#else +#define QDIO_PRINT_STUPID(x...) +#endif + +#if QDIO_VERBOSE_LEVEL>7 +#define QDIO_PRINT_ALL(x...) printk( QDIO_PRINTK_HEADER x) +#else +#define QDIO_PRINT_ALL(x...) +#endif + +#if QDIO_VERBOSE_LEVEL>6 +#define QDIO_PRINT_INFO(x...) printk( QDIO_PRINTK_HEADER x) +#else +#define QDIO_PRINT_INFO(x...) +#endif + +#if QDIO_VERBOSE_LEVEL>5 +#define QDIO_PRINT_WARN(x...) printk( QDIO_PRINTK_HEADER x) +#else +#define QDIO_PRINT_WARN(x...) +#endif + +#if QDIO_VERBOSE_LEVEL>4 +#define QDIO_PRINT_ERR(x...) printk( QDIO_PRINTK_HEADER x) +#else +#define QDIO_PRINT_ERR(x...) +#endif + +#if QDIO_VERBOSE_LEVEL>3 +#define QDIO_PRINT_CRIT(x...) printk( QDIO_PRINTK_HEADER x) +#else +#define QDIO_PRINT_CRIT(x...) +#endif + +#if QDIO_VERBOSE_LEVEL>2 +#define QDIO_PRINT_ALERT(x...) printk( QDIO_PRINTK_HEADER x) +#else +#define QDIO_PRINT_ALERT(x...) +#endif + +#if QDIO_VERBOSE_LEVEL>1 +#define QDIO_PRINT_EMERG(x...) printk( QDIO_PRINTK_HEADER x) +#else +#define QDIO_PRINT_EMERG(x...) +#endif + +#define HEXDUMP16(importance,header,ptr) \ +QDIO_PRINT_##importance(header "%02x %02x %02x %02x " \ + "%02x %02x %02x %02x %02x %02x %02x %02x " \ + "%02x %02x %02x %02x\n",*(((char*)ptr)), \ + *(((char*)ptr)+1),*(((char*)ptr)+2), \ + *(((char*)ptr)+3),*(((char*)ptr)+4), \ + *(((char*)ptr)+5),*(((char*)ptr)+6), \ + *(((char*)ptr)+7),*(((char*)ptr)+8), \ + *(((char*)ptr)+9),*(((char*)ptr)+10), \ + *(((char*)ptr)+11),*(((char*)ptr)+12), \ + *(((char*)ptr)+13),*(((char*)ptr)+14), \ + *(((char*)ptr)+15)); \ +QDIO_PRINT_##importance(header "%02x %02x %02x %02x %02x %02x %02x %02x " \ + "%02x %02x %02x %02x %02x %02x %02x %02x\n", \ + *(((char*)ptr)+16),*(((char*)ptr)+17), \ + *(((char*)ptr)+18),*(((char*)ptr)+19), \ + *(((char*)ptr)+20),*(((char*)ptr)+21), \ + *(((char*)ptr)+22),*(((char*)ptr)+23), \ + *(((char*)ptr)+24),*(((char*)ptr)+25), \ + *(((char*)ptr)+26),*(((char*)ptr)+27), \ + *(((char*)ptr)+28),*(((char*)ptr)+29), \ + *(((char*)ptr)+30),*(((char*)ptr)+31)); + +/****************** END OF DEBUG FACILITY STUFF *********************/ + +/* + * Some instructions as assembly + */ +extern __inline__ int +do_siga_sync(unsigned int irq, unsigned int mask1, unsigned int mask2) +{ + int cc; + +#ifndef CONFIG_ARCH_S390X + asm volatile ( + "lhi 0,2 \n\t" + "lr 1,%1 \n\t" + "lr 2,%2 \n\t" + "lr 3,%3 \n\t" + "siga 0 \n\t" + "ipm %0 \n\t" + "srl %0,28 \n\t" + : "=d" (cc) + : "d" (0x10000|irq), "d" (mask1), "d" (mask2) + : "cc", "0", "1", "2", "3" + ); +#else /* CONFIG_ARCH_S390X */ + asm volatile ( + "lghi 0,2 \n\t" + "llgfr 1,%1 \n\t" + "llgfr 2,%2 \n\t" + "llgfr 3,%3 \n\t" + "siga 0 \n\t" + "ipm %0 \n\t" + "srl %0,28 \n\t" + : "=d" (cc) + : "d" (0x10000|irq), "d" (mask1), "d" (mask2) + : "cc", "0", "1", "2", "3" + ); +#endif /* CONFIG_ARCH_S390X */ + return cc; +} + +extern __inline__ int +do_siga_input(unsigned int irq, unsigned int mask) +{ + int cc; + +#ifndef CONFIG_ARCH_S390X + asm volatile ( + "lhi 0,1 \n\t" + "lr 1,%1 \n\t" + "lr 2,%2 \n\t" + "siga 0 \n\t" + "ipm %0 \n\t" + "srl %0,28 \n\t" + : "=d" (cc) + : "d" (0x10000|irq), "d" (mask) + : "cc", "0", "1", "2", "memory" + ); +#else /* CONFIG_ARCH_S390X */ + asm volatile ( + "lghi 0,1 \n\t" + "llgfr 1,%1 \n\t" + "llgfr 2,%2 \n\t" + "siga 0 \n\t" + "ipm %0 \n\t" + "srl %0,28 \n\t" + : "=d" (cc) + : "d" (0x10000|irq), "d" (mask) + : "cc", "0", "1", "2", "memory" + ); +#endif /* CONFIG_ARCH_S390X */ + + return cc; +} + +extern __inline__ int +do_siga_output(unsigned long irq, unsigned long mask, __u32 *bb) +{ + int cc; + __u32 busy_bit; + +#ifndef CONFIG_ARCH_S390X + asm volatile ( + "lhi 0,0 \n\t" + "lr 1,%2 \n\t" + "lr 2,%3 \n\t" + "siga 0 \n\t" + "0:" + "ipm %0 \n\t" + "srl %0,28 \n\t" + "srl 0,31 \n\t" + "lr %1,0 \n\t" + "1: \n\t" + ".section .fixup,\"ax\"\n\t" + "2: \n\t" + "lhi %0,%4 \n\t" + "bras 1,3f \n\t" + ".long 1b \n\t" + "3: \n\t" + "l 1,0(1) \n\t" + "br 1 \n\t" + ".previous \n\t" + ".section __ex_table,\"a\"\n\t" + ".align 4 \n\t" + ".long 0b,2b \n\t" + ".previous \n\t" + : "=d" (cc), "=d" (busy_bit) + : "d" (0x10000|irq), "d" (mask), + "i" (QDIO_SIGA_ERROR_ACCESS_EXCEPTION) + : "cc", "0", "1", "2", "memory" + ); +#else /* CONFIG_ARCH_S390X */ + asm volatile ( + "lghi 0,0 \n\t" + "llgfr 1,%2 \n\t" + "llgfr 2,%3 \n\t" + "siga 0 \n\t" + "0:" + "ipm %0 \n\t" + "srl %0,28 \n\t" + "srl 0,31 \n\t" + "llgfr %1,0 \n\t" + "1: \n\t" + ".section .fixup,\"ax\"\n\t" + "lghi %0,%4 \n\t" + "jg 1b \n\t" + ".previous\n\t" + ".section __ex_table,\"a\"\n\t" + ".align 8 \n\t" + ".quad 0b,1b \n\t" + ".previous \n\t" + : "=d" (cc), "=d" (busy_bit) + : "d" (0x10000|irq), "d" (mask), + "i" (QDIO_SIGA_ERROR_ACCESS_EXCEPTION) + : "cc", "0", "1", "2", "memory" + ); +#endif /* CONFIG_ARCH_S390X */ + + (*bb) = busy_bit; + return cc; +} + +extern __inline__ unsigned long +do_clear_global_summary(void) +{ + + unsigned long time; + +#ifndef CONFIG_ARCH_S390X + asm volatile ( + "lhi 1,3 \n\t" + ".insn rre,0xb2650000,2,0 \n\t" + "lr %0,3 \n\t" + : "=d" (time) : : "cc", "1", "2", "3" + ); +#else /* CONFIG_ARCH_S390X */ + asm volatile ( + "lghi 1,3 \n\t" + ".insn rre,0xb2650000,2,0 \n\t" + "lgr %0,3 \n\t" + : "=d" (time) : : "cc", "1", "2", "3" + ); +#endif /* CONFIG_ARCH_S390X */ + + return time; +} + +/* + * QDIO device commands returned by extended Sense-ID + */ +#define DEFAULT_ESTABLISH_QS_CMD 0x1b +#define DEFAULT_ESTABLISH_QS_COUNT 0x1000 +#define DEFAULT_ACTIVATE_QS_CMD 0x1f +#define DEFAULT_ACTIVATE_QS_COUNT 0 + +/* + * additional CIWs returned by extended Sense-ID + */ +#define CIW_TYPE_EQUEUE 0x3 /* establish QDIO queues */ +#define CIW_TYPE_AQUEUE 0x4 /* activate QDIO queues */ + +#define QDIO_CHSC_RESPONSE_CODE_OK 1 +/* flags for st qdio sch data */ +#define CHSC_FLAG_QDIO_CAPABILITY 0x80 +#define CHSC_FLAG_VALIDITY 0x40 + +#define CHSC_FLAG_SIGA_INPUT_NECESSARY 0x40 +#define CHSC_FLAG_SIGA_OUTPUT_NECESSARY 0x20 +#define CHSC_FLAG_SIGA_SYNC_NECESSARY 0x10 +#define CHSC_FLAG_SIGA_SYNC_DONE_ON_THININTS 0x08 +#define CHSC_FLAG_SIGA_SYNC_DONE_ON_OUTB_PCIS 0x04 + +#ifdef QDIO_PERFORMANCE_STATS +struct qdio_perf_stats { + unsigned int tl_runs; + + unsigned int siga_outs; + unsigned int siga_ins; + unsigned int siga_syncs; + unsigned int pcis; + unsigned int thinints; + unsigned int fast_reqs; + + __u64 start_time_outbound; + unsigned int outbound_cnt; + unsigned int outbound_time; + __u64 start_time_inbound; + unsigned int inbound_cnt; + unsigned int inbound_time; +}; +#endif /* QDIO_PERFORMANCE_STATS */ + +#define atomic_swap(a,b) xchg((int*)a.counter,b) + +/* unlikely as the later the better */ +#define SYNC_MEMORY if (unlikely(q->siga_sync)) qdio_siga_sync_q(q) +#define SYNC_MEMORY_ALL if (unlikely(q->siga_sync)) \ + qdio_siga_sync(q,~0U,~0U) +#define SYNC_MEMORY_ALL_OUTB if (unlikely(q->siga_sync)) \ + qdio_siga_sync(q,~0U,0) + +#define NOW qdio_get_micros() +#define SAVE_TIMESTAMP(q) q->timing.last_transfer_time=NOW +#define GET_SAVED_TIMESTAMP(q) (q->timing.last_transfer_time) +#define SAVE_FRONTIER(q,val) q->last_move_ftc=val +#define GET_SAVED_FRONTIER(q) (q->last_move_ftc) + +#define MY_MODULE_STRING(x) #x + +#ifdef CONFIG_ARCH_S390X +#define QDIO_GET_ADDR(x) ((__u32)(unsigned long)x) +#else /* CONFIG_ARCH_S390X */ +#define QDIO_GET_ADDR(x) ((__u32)(long)x) +#endif /* CONFIG_ARCH_S390X */ + +#ifdef CONFIG_QDIO_DEBUG +#define set_slsb(x,y) \ + if(q->queue_type==QDIO_TRACE_QTYPE) { \ + if(q->is_input_q) { \ + QDIO_DBF_HEX2(0,slsb_in,&q->slsb,QDIO_MAX_BUFFERS_PER_Q); \ + } else { \ + QDIO_DBF_HEX2(0,slsb_out,&q->slsb,QDIO_MAX_BUFFERS_PER_Q); \ + } \ + } \ + qdio_set_slsb(x,y); \ + if(q->queue_type==QDIO_TRACE_QTYPE) { \ + if(q->is_input_q) { \ + QDIO_DBF_HEX2(0,slsb_in,&q->slsb,QDIO_MAX_BUFFERS_PER_Q); \ + } else { \ + QDIO_DBF_HEX2(0,slsb_out,&q->slsb,QDIO_MAX_BUFFERS_PER_Q); \ + } \ + } +#else /* CONFIG_QDIO_DEBUG */ +#define set_slsb(x,y) qdio_set_slsb(x,y) +#endif /* CONFIG_QDIO_DEBUG */ + +struct qdio_q { + volatile struct slsb slsb; + + char unused[QDIO_MAX_BUFFERS_PER_Q]; + + __u32 * volatile dev_st_chg_ind; + + int is_input_q; + int irq; + struct ccw_device *cdev; + + unsigned int is_iqdio_q; + unsigned int is_thinint_q; + + /* bit 0 means queue 0, bit 1 means queue 1, ... */ + unsigned int mask; + unsigned int q_no; + + qdio_handler_t (*handler); + + /* points to the next buffer to be checked for having + * been processed by the card (outbound) + * or to the next buffer the program should check for (inbound) */ + volatile int first_to_check; + /* and the last time it was: */ + volatile int last_move_ftc; + + atomic_t number_of_buffers_used; + atomic_t polling; + + unsigned int siga_in; + unsigned int siga_out; + unsigned int siga_sync; + unsigned int siga_sync_done_on_thinints; + unsigned int siga_sync_done_on_outb_tis; + unsigned int hydra_gives_outbound_pcis; + + /* used to save beginning position when calling dd_handlers */ + int first_element_to_kick; + + atomic_t use_count; + atomic_t is_in_shutdown; + + void *irq_ptr; + +#ifdef QDIO_USE_TIMERS_FOR_POLLING + struct timer_list timer; + atomic_t timer_already_set; + spinlock_t timer_lock; +#else /* QDIO_USE_TIMERS_FOR_POLLING */ + struct tasklet_struct tasklet; +#endif /* QDIO_USE_TIMERS_FOR_POLLING */ + + enum qdio_irq_states state; + + /* used to store the error condition during a data transfer */ + unsigned int qdio_error; + unsigned int siga_error; + unsigned int error_status_flags; + + /* list of interesting queues */ + volatile struct qdio_q *list_next; + volatile struct qdio_q *list_prev; + + struct sl *sl; + volatile struct sbal *sbal[QDIO_MAX_BUFFERS_PER_Q]; + + struct qdio_buffer *qdio_buffers[QDIO_MAX_BUFFERS_PER_Q]; + + unsigned long int_parm; + + /*struct { + int in_bh_check_limit; + int threshold; + } threshold_classes[QDIO_STATS_CLASSES];*/ + + struct { + /* inbound: the time to stop polling + outbound: the time to kick peer */ + int threshold; /* the real value */ + + /* outbound: last time of do_QDIO + inbound: last time of noticing incoming data */ + /*__u64 last_transfer_times[QDIO_STATS_NUMBER]; + int last_transfer_index; */ + + __u64 last_transfer_time; + __u64 busy_start; + } timing; + atomic_t busy_siga_counter; + unsigned int queue_type; + + /* leave this member at the end. won't be cleared in qdio_fill_qs */ + struct slib *slib; /* a page is allocated under this pointer, + sl points into this page, offset PAGE_SIZE/2 + (after slib) */ +} __attribute__ ((aligned(256))); + +struct qdio_irq { + __u32 * volatile dev_st_chg_ind; + + unsigned long int_parm; + int irq; + + unsigned int is_iqdio_irq; + unsigned int is_thinint_irq; + unsigned int hydra_gives_outbound_pcis; + unsigned int sync_done_on_outb_pcis; + + enum qdio_irq_states state; + + unsigned int no_input_qs; + unsigned int no_output_qs; + + unsigned char qdioac; + + struct ccw1 ccw; + + struct ciw equeue; + struct ciw aqueue; + + struct qib qib; + + void (*original_int_handler) (struct ccw_device *, + unsigned long, struct irb *); + + /* leave these four members together at the end. won't be cleared in qdio_fill_irq */ + struct qdr *qdr; + struct qdio_q *input_qs[QDIO_MAX_QUEUES_PER_IRQ]; + struct qdio_q *output_qs[QDIO_MAX_QUEUES_PER_IRQ]; + struct semaphore setting_up_sema; +}; +#endif |