diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/s390/cio |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/s390/cio')
-rw-r--r-- | drivers/s390/cio/Makefile | 10 | ||||
-rw-r--r-- | drivers/s390/cio/airq.c | 87 | ||||
-rw-r--r-- | drivers/s390/cio/airq.h | 10 | ||||
-rw-r--r-- | drivers/s390/cio/blacklist.c | 351 | ||||
-rw-r--r-- | drivers/s390/cio/blacklist.h | 6 | ||||
-rw-r--r-- | drivers/s390/cio/ccwgroup.c | 482 | ||||
-rw-r--r-- | drivers/s390/cio/chsc.c | 1114 | ||||
-rw-r--r-- | drivers/s390/cio/chsc.h | 66 | ||||
-rw-r--r-- | drivers/s390/cio/cio.c | 860 | ||||
-rw-r--r-- | drivers/s390/cio/cio.h | 143 | ||||
-rw-r--r-- | drivers/s390/cio/cio_debug.h | 32 | ||||
-rw-r--r-- | drivers/s390/cio/cmf.c | 1042 | ||||
-rw-r--r-- | drivers/s390/cio/css.c | 575 | ||||
-rw-r--r-- | drivers/s390/cio/css.h | 155 | ||||
-rw-r--r-- | drivers/s390/cio/device.c | 1135 | ||||
-rw-r--r-- | drivers/s390/cio/device.h | 115 | ||||
-rw-r--r-- | drivers/s390/cio/device_fsm.c | 1250 | ||||
-rw-r--r-- | drivers/s390/cio/device_id.c | 355 | ||||
-rw-r--r-- | drivers/s390/cio/device_ops.c | 603 | ||||
-rw-r--r-- | drivers/s390/cio/device_pgid.c | 448 | ||||
-rw-r--r-- | drivers/s390/cio/device_status.c | 385 | ||||
-rw-r--r-- | drivers/s390/cio/ioasm.h | 228 | ||||
-rw-r--r-- | drivers/s390/cio/qdio.c | 3468 | ||||
-rw-r--r-- | drivers/s390/cio/qdio.h | 648 |
24 files changed, 13568 insertions, 0 deletions
diff --git a/drivers/s390/cio/Makefile b/drivers/s390/cio/Makefile new file mode 100644 index 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; + } |