summaryrefslogtreecommitdiffstats
path: root/drivers/s390/cio
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /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/Makefile10
-rw-r--r--drivers/s390/cio/airq.c87
-rw-r--r--drivers/s390/cio/airq.h10
-rw-r--r--drivers/s390/cio/blacklist.c351
-rw-r--r--drivers/s390/cio/blacklist.h6
-rw-r--r--drivers/s390/cio/ccwgroup.c482
-rw-r--r--drivers/s390/cio/chsc.c1114
-rw-r--r--drivers/s390/cio/chsc.h66
-rw-r--r--drivers/s390/cio/cio.c860
-rw-r--r--drivers/s390/cio/cio.h143
-rw-r--r--drivers/s390/cio/cio_debug.h32
-rw-r--r--drivers/s390/cio/cmf.c1042
-rw-r--r--drivers/s390/cio/css.c575
-rw-r--r--drivers/s390/cio/css.h155
-rw-r--r--drivers/s390/cio/device.c1135
-rw-r--r--drivers/s390/cio/device.h115
-rw-r--r--drivers/s390/cio/device_fsm.c1250
-rw-r--r--drivers/s390/cio/device_id.c355
-rw-r--r--drivers/s390/cio/device_ops.c603
-rw-r--r--drivers/s390/cio/device_pgid.c448
-rw-r--r--drivers/s390/cio/device_status.c385
-rw-r--r--drivers/s390/cio/ioasm.h228
-rw-r--r--drivers/s390/cio/qdio.c3468
-rw-r--r--drivers/s390/cio/qdio.h648
24 files changed, 13568 insertions, 0 deletions
diff --git a/drivers/s390/cio/Makefile b/drivers/s390/cio/Makefile
new file mode 100644
index 00000000000..c490c2a1c2f
--- /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 00000000000..3720e77b465
--- /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 00000000000..7d6be3fdcd6
--- /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 00000000000..4a06c7d0e5e
--- /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 00000000000..fb42cafbe57
--- /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 00000000000..21a75ee28b8
--- /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 00000000000..b35fe12e6bf
--- /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 00000000000..be20da49d14
--- /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 00000000000..99ce5a56798
--- /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 00000000000..c50a9da420a
--- /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 00000000000..6af8b27d366
--- /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 00000000000..49def26ba38
--- /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 00000000000..87bd70eeabe
--- /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 00000000000..2004a6c4938
--- /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 00000000000..df0325505e4
--- /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 00000000000..a3aa056d724
--- /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 00000000000..9b7f6f548b1
--- /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 00000000000..0e68fb511dc
--- /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 00000000000..11e260e0b9c
--- /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 00000000000..0adac8a6733
--- /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 00000000000..4ab2e0d9500
--- /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 00000000000..c874607d9a8
--- /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 00000000000..bbe9f45d143
--- /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 00000000000..9ad14db2414
--- /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