diff options
Diffstat (limited to 'drivers/s390/cio')
-rw-r--r-- | drivers/s390/cio/airq.c | 171 | ||||
-rw-r--r-- | drivers/s390/cio/airq.h | 10 | ||||
-rw-r--r-- | drivers/s390/cio/blacklist.c | 2 | ||||
-rw-r--r-- | drivers/s390/cio/ccwgroup.c | 37 | ||||
-rw-r--r-- | drivers/s390/cio/chsc.c | 266 | ||||
-rw-r--r-- | drivers/s390/cio/cio.c | 106 | ||||
-rw-r--r-- | drivers/s390/cio/cio.h | 89 | ||||
-rw-r--r-- | drivers/s390/cio/cio_debug.h | 22 | ||||
-rw-r--r-- | drivers/s390/cio/css.c | 198 | ||||
-rw-r--r-- | drivers/s390/cio/css.h | 78 | ||||
-rw-r--r-- | drivers/s390/cio/device.c | 157 | ||||
-rw-r--r-- | drivers/s390/cio/device.h | 5 | ||||
-rw-r--r-- | drivers/s390/cio/device_fsm.c | 124 | ||||
-rw-r--r-- | drivers/s390/cio/device_id.c | 116 | ||||
-rw-r--r-- | drivers/s390/cio/device_ops.c | 2 | ||||
-rw-r--r-- | drivers/s390/cio/device_pgid.c | 6 | ||||
-rw-r--r-- | drivers/s390/cio/device_status.c | 13 | ||||
-rw-r--r-- | drivers/s390/cio/io_sch.h | 163 | ||||
-rw-r--r-- | drivers/s390/cio/ioasm.h | 66 | ||||
-rw-r--r-- | drivers/s390/cio/qdio.c | 38 | ||||
-rw-r--r-- | drivers/s390/cio/qdio.h | 2 |
21 files changed, 996 insertions, 675 deletions
diff --git a/drivers/s390/cio/airq.c b/drivers/s390/cio/airq.c index 5287631fbfc..b7a07a86629 100644 --- a/drivers/s390/cio/airq.c +++ b/drivers/s390/cio/airq.c @@ -1,12 +1,12 @@ /* * drivers/s390/cio/airq.c - * S/390 common I/O routines -- support for adapter interruptions + * Support for adapter interruptions * - * Copyright (C) 1999-2002 IBM Deutschland Entwicklung GmbH, - * IBM Corporation - * Author(s): Ingo Adlung (adlung@de.ibm.com) - * Cornelia Huck (cornelia.huck@de.ibm.com) - * Arnd Bergmann (arndb@de.ibm.com) + * Copyright IBM Corp. 1999,2007 + * Author(s): Ingo Adlung <adlung@de.ibm.com> + * Cornelia Huck <cornelia.huck@de.ibm.com> + * Arnd Bergmann <arndb@de.ibm.com> + * Peter Oberparleiter <peter.oberparleiter@de.ibm.com> */ #include <linux/init.h> @@ -14,72 +14,131 @@ #include <linux/slab.h> #include <linux/rcupdate.h> +#include <asm/airq.h> + +#include "cio.h" #include "cio_debug.h" -#include "airq.h" -static adapter_int_handler_t adapter_handler; +#define NR_AIRQS 32 +#define NR_AIRQS_PER_WORD sizeof(unsigned long) +#define NR_AIRQ_WORDS (NR_AIRQS / NR_AIRQS_PER_WORD) -/* - * 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]; +union indicator_t { + unsigned long word[NR_AIRQ_WORDS]; + unsigned char byte[NR_AIRQS]; +} __attribute__((packed)); - CIO_TRACE_EVENT (4, "rgaint"); +struct airq_t { + adapter_int_handler_t handler; + void *drv_data; +}; - if (handler == NULL) - ret = -EINVAL; - else - ret = (cmpxchg(&adapter_handler, NULL, handler) ? -EBUSY : 0); - if (!ret) - synchronize_sched(); /* Allow interrupts to complete. */ +static union indicator_t indicators; +static struct airq_t *airqs[NR_AIRQS]; - sprintf (dbf_txt, "ret:%d", ret); - CIO_TRACE_EVENT (4, dbf_txt); +static int register_airq(struct airq_t *airq) +{ + int i; - return ret; + for (i = 0; i < NR_AIRQS; i++) + if (!cmpxchg(&airqs[i], NULL, airq)) + return i; + return -ENOMEM; } -int -s390_unregister_adapter_interrupt (adapter_int_handler_t handler) +/** + * s390_register_adapter_interrupt() - register adapter interrupt handler + * @handler: adapter handler to be registered + * @drv_data: driver data passed with each call to the handler + * + * Returns: + * Pointer to the indicator to be used on success + * ERR_PTR() if registration failed + */ +void *s390_register_adapter_interrupt(adapter_int_handler_t handler, + void *drv_data) { + struct airq_t *airq; + char dbf_txt[16]; int ret; - char dbf_txt[15]; - CIO_TRACE_EVENT (4, "urgaint"); - - if (handler == NULL) - ret = -EINVAL; - else { - adapter_handler = NULL; - synchronize_sched(); /* Allow interrupts to complete. */ - ret = 0; + airq = kmalloc(sizeof(struct airq_t), GFP_KERNEL); + if (!airq) { + ret = -ENOMEM; + goto out; } - sprintf (dbf_txt, "ret:%d", ret); - CIO_TRACE_EVENT (4, dbf_txt); - - return ret; + airq->handler = handler; + airq->drv_data = drv_data; + ret = register_airq(airq); + if (ret < 0) + kfree(airq); +out: + snprintf(dbf_txt, sizeof(dbf_txt), "rairq:%d", ret); + CIO_TRACE_EVENT(4, dbf_txt); + if (ret < 0) + return ERR_PTR(ret); + else + return &indicators.byte[ret]; } +EXPORT_SYMBOL(s390_register_adapter_interrupt); -void -do_adapter_IO (void) +/** + * s390_unregister_adapter_interrupt - unregister adapter interrupt handler + * @ind: indicator for which the handler is to be unregistered + */ +void s390_unregister_adapter_interrupt(void *ind) { - CIO_TRACE_EVENT (6, "doaio"); + struct airq_t *airq; + char dbf_txt[16]; + int i; - if (adapter_handler) - (*adapter_handler) (); + i = (int) ((addr_t) ind) - ((addr_t) &indicators.byte[0]); + snprintf(dbf_txt, sizeof(dbf_txt), "urairq:%d", i); + CIO_TRACE_EVENT(4, dbf_txt); + indicators.byte[i] = 0; + airq = xchg(&airqs[i], NULL); + /* + * Allow interrupts to complete. This will ensure that the airq handle + * is no longer referenced by any interrupt handler. + */ + synchronize_sched(); + kfree(airq); } +EXPORT_SYMBOL(s390_unregister_adapter_interrupt); + +#define INDICATOR_MASK (0xffUL << ((NR_AIRQS_PER_WORD - 1) * 8)) -EXPORT_SYMBOL (s390_register_adapter_interrupt); -EXPORT_SYMBOL (s390_unregister_adapter_interrupt); +void do_adapter_IO(void) +{ + int w; + int i; + unsigned long word; + struct airq_t *airq; + + /* + * Access indicator array in word-sized chunks to minimize storage + * fetch operations. + */ + for (w = 0; w < NR_AIRQ_WORDS; w++) { + word = indicators.word[w]; + i = w * NR_AIRQS_PER_WORD; + /* + * Check bytes within word for active indicators. + */ + while (word) { + if (word & INDICATOR_MASK) { + airq = airqs[i]; + if (likely(airq)) + airq->handler(&indicators.byte[i], + airq->drv_data); + else + /* + * Reset ill-behaved indicator. + */ + indicators.byte[i] = 0; + } + word <<= 8; + i++; + } + } +} diff --git a/drivers/s390/cio/airq.h b/drivers/s390/cio/airq.h deleted file mode 100644 index 7d6be3fdcd6..00000000000 --- a/drivers/s390/cio/airq.h +++ /dev/null @@ -1,10 +0,0 @@ -#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 index bd5f16f80bf..e8597ec9224 100644 --- a/drivers/s390/cio/blacklist.c +++ b/drivers/s390/cio/blacklist.c @@ -348,7 +348,7 @@ cio_ignore_write(struct file *file, const char __user *user_buf, return user_len; } -static struct seq_operations cio_ignore_proc_seq_ops = { +static const struct seq_operations cio_ignore_proc_seq_ops = { .start = cio_ignore_proc_seq_start, .stop = cio_ignore_proc_seq_stop, .next = cio_ignore_proc_seq_next, diff --git a/drivers/s390/cio/ccwgroup.c b/drivers/s390/cio/ccwgroup.c index 5baa517c3b6..03914fa8117 100644 --- a/drivers/s390/cio/ccwgroup.c +++ b/drivers/s390/cio/ccwgroup.c @@ -35,8 +35,8 @@ 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); + gdev = to_ccwgroupdev(dev); + gdrv = to_ccwgroupdrv(drv); if (gdev->creator_id == gdrv->driver_id) return 1; @@ -75,8 +75,10 @@ static void ccwgroup_ungroup_callback(struct device *dev) struct ccwgroup_device *gdev = to_ccwgroupdev(dev); mutex_lock(&gdev->reg_mutex); - __ccwgroup_remove_symlinks(gdev); - device_unregister(dev); + if (device_is_registered(&gdev->dev)) { + __ccwgroup_remove_symlinks(gdev); + device_unregister(dev); + } mutex_unlock(&gdev->reg_mutex); } @@ -111,7 +113,7 @@ ccwgroup_release (struct device *dev) gdev = to_ccwgroupdev(dev); for (i = 0; i < gdev->count; i++) { - gdev->cdev[i]->dev.driver_data = NULL; + dev_set_drvdata(&gdev->cdev[i]->dev, NULL); put_device(&gdev->cdev[i]->dev); } kfree(gdev); @@ -196,11 +198,11 @@ int ccwgroup_create(struct device *root, unsigned int creator_id, goto error; } /* Don't allow a device to belong to more than one group. */ - if (gdev->cdev[i]->dev.driver_data) { + if (dev_get_drvdata(&gdev->cdev[i]->dev)) { rc = -EINVAL; goto error; } - gdev->cdev[i]->dev.driver_data = gdev; + dev_set_drvdata(&gdev->cdev[i]->dev, gdev); } gdev->creator_id = creator_id; @@ -234,8 +236,8 @@ int ccwgroup_create(struct device *root, unsigned int creator_id, error: for (i = 0; i < argc; i++) if (gdev->cdev[i]) { - if (gdev->cdev[i]->dev.driver_data == gdev) - gdev->cdev[i]->dev.driver_data = NULL; + if (dev_get_drvdata(&gdev->cdev[i]->dev) == gdev) + dev_set_drvdata(&gdev->cdev[i]->dev, NULL); put_device(&gdev->cdev[i]->dev); } mutex_unlock(&gdev->reg_mutex); @@ -389,12 +391,24 @@ ccwgroup_remove (struct device *dev) return 0; } +static void ccwgroup_shutdown(struct device *dev) +{ + struct ccwgroup_device *gdev; + struct ccwgroup_driver *gdrv; + + gdev = to_ccwgroupdev(dev); + gdrv = to_ccwgroupdrv(dev->driver); + if (gdrv && gdrv->shutdown) + gdrv->shutdown(gdev); +} + static struct bus_type ccwgroup_bus_type = { .name = "ccwgroup", .match = ccwgroup_bus_match, .uevent = ccwgroup_uevent, .probe = ccwgroup_probe, .remove = ccwgroup_remove, + .shutdown = ccwgroup_shutdown, }; /** @@ -408,6 +422,7 @@ int ccwgroup_driver_register(struct ccwgroup_driver *cdriver) /* register our new driver with the core */ cdriver->driver.bus = &ccwgroup_bus_type; cdriver->driver.name = cdriver->name; + cdriver->driver.owner = cdriver->owner; return driver_register(&cdriver->driver); } @@ -463,8 +478,8 @@ __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; + gdev = dev_get_drvdata(&cdev->dev); + if (gdev) { if (get_device(&gdev->dev)) { mutex_lock(&gdev->reg_mutex); if (device_is_registered(&gdev->dev)) diff --git a/drivers/s390/cio/chsc.c b/drivers/s390/cio/chsc.c index 597c0c76a2a..007aaeb4f53 100644 --- a/drivers/s390/cio/chsc.c +++ b/drivers/s390/cio/chsc.c @@ -26,6 +26,25 @@ static void *sei_page; +static int chsc_error_from_response(int response) +{ + switch (response) { + case 0x0001: + return 0; + case 0x0002: + case 0x0003: + case 0x0006: + case 0x0007: + case 0x0008: + case 0x000a: + return -EINVAL; + case 0x0004: + return -EOPNOTSUPP; + default: + return -EIO; + } +} + struct chsc_ssd_area { struct chsc_header request; u16 :10; @@ -75,11 +94,11 @@ int chsc_get_ssd_info(struct subchannel_id schid, struct chsc_ssd_info *ssd) ret = (ccode == 3) ? -ENODEV : -EBUSY; goto out_free; } - if (ssd_area->response.code != 0x0001) { + ret = chsc_error_from_response(ssd_area->response.code); + if (ret != 0) { CIO_MSG_EVENT(2, "chsc: ssd failed for 0.%x.%04x (rc=%04x)\n", schid.ssid, schid.sch_no, ssd_area->response.code); - ret = -EIO; goto out_free; } if (!ssd_area->sch_valid) { @@ -89,7 +108,8 @@ int chsc_get_ssd_info(struct subchannel_id schid, struct chsc_ssd_info *ssd) /* Copy data */ ret = 0; memset(ssd, 0, sizeof(struct chsc_ssd_info)); - if ((ssd_area->st != 0) && (ssd_area->st != 2)) + if ((ssd_area->st != SUBCHANNEL_TYPE_IO) && + (ssd_area->st != SUBCHANNEL_TYPE_MSG)) goto out_free; ssd->path_mask = ssd_area->path_mask; ssd->fla_valid_mask = ssd_area->fla_valid_mask; @@ -132,20 +152,16 @@ static void terminate_internal_io(struct subchannel *sch) device_set_intretry(sch); /* Call handler. */ if (sch->driver && sch->driver->termination) - sch->driver->termination(&sch->dev); + sch->driver->termination(sch); } -static int -s390_subchannel_remove_chpid(struct device *dev, void *data) +static int s390_subchannel_remove_chpid(struct subchannel *sch, void *data) { int j; int mask; - struct subchannel *sch; - struct chp_id *chpid; + struct chp_id *chpid = data; struct schib schib; - sch = to_subchannel(dev); - chpid = data; for (j = 0; j < 8; j++) { mask = 0x80 >> j; if ((sch->schib.pmcw.pim & mask) && @@ -158,7 +174,7 @@ s390_subchannel_remove_chpid(struct device *dev, void *data) spin_lock_irq(sch->lock); stsch(sch->schid, &schib); - if (!schib.pmcw.dnv) + if (!css_sch_is_valid(&schib)) goto out_unreg; memcpy(&sch->schib, &schib, sizeof(struct schib)); /* Check for single path devices. */ @@ -172,12 +188,12 @@ s390_subchannel_remove_chpid(struct device *dev, void *data) terminate_internal_io(sch); /* Re-start path verification. */ if (sch->driver && sch->driver->verify) - sch->driver->verify(&sch->dev); + sch->driver->verify(sch); } } else { /* trigger path verification. */ if (sch->driver && sch->driver->verify) - sch->driver->verify(&sch->dev); + sch->driver->verify(sch); else if (sch->lpm == mask) goto out_unreg; } @@ -201,12 +217,10 @@ void chsc_chp_offline(struct chp_id chpid) if (chp_get_status(chpid) <= 0) return; - bus_for_each_dev(&css_bus_type, NULL, &chpid, - s390_subchannel_remove_chpid); + for_each_subchannel_staged(s390_subchannel_remove_chpid, NULL, &chpid); } -static int -s390_process_res_acc_new_sch(struct subchannel_id schid) +static int s390_process_res_acc_new_sch(struct subchannel_id schid, void *data) { struct schib schib; /* @@ -252,18 +266,10 @@ static int get_res_chpid_mask(struct chsc_ssd_info *ssd, return 0; } -static int -__s390_process_res_acc(struct subchannel_id schid, void *data) +static int __s390_process_res_acc(struct subchannel *sch, void *data) { int chp_mask, old_lpm; - struct res_acc_data *res_data; - struct subchannel *sch; - - res_data = data; - sch = get_subchannel_by_schid(schid); - if (!sch) - /* Check if a subchannel is newly available. */ - return s390_process_res_acc_new_sch(schid); + struct res_acc_data *res_data = data; spin_lock_irq(sch->lock); chp_mask = get_res_chpid_mask(&sch->ssd_info, res_data); @@ -279,10 +285,10 @@ __s390_process_res_acc(struct subchannel_id schid, void *data) if (!old_lpm && sch->lpm) device_trigger_reprobe(sch); else if (sch->driver && sch->driver->verify) - sch->driver->verify(&sch->dev); + sch->driver->verify(sch); out: spin_unlock_irq(sch->lock); - put_device(&sch->dev); + return 0; } @@ -305,7 +311,8 @@ static void s390_process_res_acc (struct res_acc_data *res_data) * The more information we have (info), the less scanning * will we have to do. */ - for_each_subchannel(__s390_process_res_acc, res_data); + for_each_subchannel_staged(__s390_process_res_acc, + s390_process_res_acc_new_sch, res_data); } static int @@ -499,8 +506,7 @@ void chsc_process_crw(void) } while (sei_area->flags & 0x80); } -static int -__chp_add_new_sch(struct subchannel_id schid) +static int __chp_add_new_sch(struct subchannel_id schid, void *data) { struct schib schib; @@ -514,45 +520,37 @@ __chp_add_new_sch(struct subchannel_id schid) } -static int -__chp_add(struct subchannel_id schid, void *data) +static int __chp_add(struct subchannel *sch, void *data) { int i, mask; - struct chp_id *chpid; - struct subchannel *sch; - - chpid = data; - sch = get_subchannel_by_schid(schid); - if (!sch) - /* Check if the subchannel is now available. */ - return __chp_add_new_sch(schid); + struct chp_id *chpid = data; + spin_lock_irq(sch->lock); for (i=0; i<8; i++) { mask = 0x80 >> i; if ((sch->schib.pmcw.pim & mask) && - (sch->schib.pmcw.chpid[i] == chpid->id)) { - if (stsch(sch->schid, &sch->schib) != 0) { - /* Endgame. */ - spin_unlock_irq(sch->lock); - return -ENXIO; - } + (sch->schib.pmcw.chpid[i] == chpid->id)) break; - } } if (i==8) { spin_unlock_irq(sch->lock); return 0; } + if (stsch(sch->schid, &sch->schib)) { + spin_unlock_irq(sch->lock); + css_schedule_eval(sch->schid); + return 0; + } sch->lpm = ((sch->schib.pmcw.pim & sch->schib.pmcw.pam & sch->schib.pmcw.pom) | mask) & sch->opm; if (sch->driver && sch->driver->verify) - sch->driver->verify(&sch->dev); + sch->driver->verify(sch); spin_unlock_irq(sch->lock); - put_device(&sch->dev); + return 0; } @@ -564,7 +562,8 @@ void chsc_chp_online(struct chp_id chpid) CIO_TRACE_EVENT(2, dbf_txt); if (chp_get_status(chpid) != 0) - for_each_subchannel(__chp_add, &chpid); + for_each_subchannel_staged(__chp_add, __chp_add_new_sch, + &chpid); } static void __s390_subchannel_vary_chpid(struct subchannel *sch, @@ -589,7 +588,7 @@ static void __s390_subchannel_vary_chpid(struct subchannel *sch, if (!old_lpm) device_trigger_reprobe(sch); else if (sch->driver && sch->driver->verify) - sch->driver->verify(&sch->dev); + sch->driver->verify(sch); break; } sch->opm &= ~mask; @@ -603,37 +602,29 @@ static void __s390_subchannel_vary_chpid(struct subchannel *sch, terminate_internal_io(sch); /* Re-start path verification. */ if (sch->driver && sch->driver->verify) - sch->driver->verify(&sch->dev); + sch->driver->verify(sch); } } else if (!sch->lpm) { if (device_trigger_verify(sch) != 0) css_schedule_eval(sch->schid); } else if (sch->driver && sch->driver->verify) - sch->driver->verify(&sch->dev); + sch->driver->verify(sch); break; } spin_unlock_irqrestore(sch->lock, flags); } -static int s390_subchannel_vary_chpid_off(struct device *dev, void *data) +static int s390_subchannel_vary_chpid_off(struct subchannel *sch, void *data) { - struct subchannel *sch; - struct chp_id *chpid; - - sch = to_subchannel(dev); - chpid = data; + struct chp_id *chpid = data; __s390_subchannel_vary_chpid(sch, *chpid, 0); return 0; } -static int s390_subchannel_vary_chpid_on(struct device *dev, void *data) +static int s390_subchannel_vary_chpid_on(struct subchannel *sch, void *data) { - struct subchannel *sch; - struct chp_id *chpid; - - sch = to_subchannel(dev); - chpid = data; + struct chp_id *chpid = data; __s390_subchannel_vary_chpid(sch, *chpid, 1); return 0; @@ -643,13 +634,7 @@ static int __s390_vary_chpid_on(struct subchannel_id schid, void *data) { struct schib schib; - struct subchannel *sch; - sch = get_subchannel_by_schid(schid); - if (sch) { - put_device(&sch->dev); - return 0; - } if (stsch_err(schid, &schib)) /* We're through */ return -ENXIO; @@ -669,12 +654,13 @@ int chsc_chp_vary(struct chp_id chpid, int 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) - /* Scan for new devices on varied on path. */ - for_each_subchannel(__s390_vary_chpid_on, NULL); + for_each_subchannel_staged(s390_subchannel_vary_chpid_on, + __s390_vary_chpid_on, &chpid); + else + for_each_subchannel_staged(s390_subchannel_vary_chpid_off, + NULL, &chpid); + return 0; } @@ -750,36 +736,15 @@ __chsc_do_secm(struct channel_subsystem *css, int enable, void *page) return (ccode == 3) ? -ENODEV : -EBUSY; switch (secm_area->response.code) { - case 0x0001: /* Success. */ - 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"); + case 0x0102: + case 0x0103: ret = -EINVAL; - break; - case 0x0004: /* Command not provided in model. */ - CIO_CRW_EVENT(2, "Model does not provide secm\n"); - ret = -EOPNOTSUPP; - break; - case 0x0102: /* cub adresses incorrect */ - CIO_CRW_EVENT(2, "Invalid addresses in chsc request block\n"); - ret = -EINVAL; - break; - case 0x0103: /* key error */ - CIO_CRW_EVENT(2, "Access key error in secm\n"); - ret = -EINVAL; - break; - case 0x0105: /* error while starting */ - CIO_CRW_EVENT(2, "Error while starting channel measurement\n"); - ret = -EIO; - break; default: - CIO_CRW_EVENT(2, "Unknown CHSC response %d\n", - secm_area->response.code); - ret = -EIO; + ret = chsc_error_from_response(secm_area->response.code); } + if (ret != 0) + CIO_CRW_EVENT(2, "chsc: secm failed (rc=%04x)\n", + secm_area->response.code); return ret; } @@ -860,27 +825,14 @@ int chsc_determine_channel_path_description(struct chp_id chpid, goto out; } - switch (scpd_area->response.code) { - case 0x0001: /* Success. */ + ret = chsc_error_from_response(scpd_area->response.code); + if (ret == 0) + /* 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", + else + CIO_CRW_EVENT(2, "chsc: scpd failed (rc=%04x)\n", scpd_area->response.code); - ret = -EIO; - } out: free_page((unsigned long)scpd_area); return ret; @@ -956,8 +908,9 @@ int chsc_get_channel_measurement_chars(struct channel_path *chp) goto out; } - switch (scmc_area->response.code) { - case 0x0001: /* Success. */ + ret = chsc_error_from_response(scmc_area->response.code); + if (ret == 0) { + /* Success. */ if (!scmc_area->not_valid) { chp->cmg = scmc_area->cmg; chp->shared = scmc_area->shared; @@ -968,22 +921,9 @@ int chsc_get_channel_measurement_chars(struct channel_path *chp) chp->cmg = -1; chp->shared = -1; } - ret = 0; - break; - case 0x0003: /* Invalid block. */ - case 0x0007: /* Invalid format. */ - case 0x0008: /* Invalid bit combination. */ - CIO_CRW_EVENT(2, "Error in chsc request block!\n"); - ret = -EINVAL; - break; - case 0x0004: /* Command not provided. */ - CIO_CRW_EVENT(2, "Model does not provide scmc\n"); - ret = -EOPNOTSUPP; - break; - default: - CIO_CRW_EVENT(2, "Unknown CHSC response %d\n", + } else { + CIO_CRW_EVENT(2, "chsc: scmc failed (rc=%04x)\n", scmc_area->response.code); - ret = -EIO; } out: free_page((unsigned long)scmc_area); @@ -1035,21 +975,17 @@ chsc_enable_facility(int operation_code) ret = (ret == 3) ? -ENODEV : -EBUSY; goto out; } + switch (sda_area->response.code) { - case 0x0001: /* everything ok */ - ret = 0; - break; - case 0x0003: /* invalid request block */ - case 0x0007: - ret = -EINVAL; - break; - case 0x0004: /* command not provided */ - case 0x0101: /* facility not provided */ + case 0x0101: ret = -EOPNOTSUPP; break; - default: /* something went wrong */ - ret = -EIO; + default: + ret = chsc_error_from_response(sda_area->response.code); } + if (ret != 0) + CIO_CRW_EVENT(2, "chsc: sda (oc=%x) failed (rc=%04x)\n", + operation_code, sda_area->response.code); out: free_page((unsigned long)sda_area); return ret; @@ -1074,33 +1010,27 @@ chsc_determine_css_characteristics(void) } __attribute__ ((packed)) *scsc_area; scsc_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); - if (!scsc_area) { - CIO_MSG_EVENT(0, "Was not able to determine available" - "CHSCs due to no memory.\n"); + if (!scsc_area) return -ENOMEM; - } scsc_area->request.length = 0x0010; scsc_area->request.code = 0x0010; result = chsc(scsc_area); if (result) { - CIO_MSG_EVENT(0, "Was not able to determine available CHSCs, " - "cc=%i.\n", result); - result = -EIO; + result = (result == 3) ? -ENODEV : -EBUSY; goto exit; } - if (scsc_area->response.code != 1) { - CIO_MSG_EVENT(0, "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)); + result = chsc_error_from_response(scsc_area->response.code); + if (result == 0) { + memcpy(&css_general_characteristics, scsc_area->general_char, + sizeof(css_general_characteristics)); + memcpy(&css_chsc_characteristics, scsc_area->chsc_char, + sizeof(css_chsc_characteristics)); + } else + CIO_CRW_EVENT(2, "chsc: scsc failed (rc=%04x)\n", + scsc_area->response.code); exit: free_page ((unsigned long) scsc_area); return result; diff --git a/drivers/s390/cio/cio.c b/drivers/s390/cio/cio.c index 46905345159..60590a12d52 100644 --- a/drivers/s390/cio/cio.c +++ b/drivers/s390/cio/cio.c @@ -23,11 +23,12 @@ #include <asm/reset.h> #include <asm/ipl.h> #include <asm/chpid.h> -#include "airq.h" +#include <asm/airq.h> #include "cio.h" #include "css.h" #include "chsc.h" #include "ioasm.h" +#include "io_sch.h" #include "blacklist.h" #include "cio_debug.h" #include "chp.h" @@ -56,39 +57,37 @@ __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 + * Initializes three debug logs for common I/O: + * - cio_msg logs generic cio messages * - 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 + * - cio_crw logs machine check related cio messages */ -static int __init -cio_debug_init (void) +static int __init cio_debug_init(void) { - cio_debug_msg_id = debug_register ("cio_msg", 16, 4, 16*sizeof (long)); + cio_debug_msg_id = debug_register("cio_msg", 16, 1, 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", 16, 4, 16); + 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", 16, 1, 16); 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", 4, 4, 16*sizeof (long)); + 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", 16, 1, 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); + debug_register_view(cio_debug_crw_id, &debug_sprintf_view); + debug_set_level(cio_debug_crw_id, 4); return 0; out_unregister: if (cio_debug_msg_id) - debug_unregister (cio_debug_msg_id); + debug_unregister(cio_debug_msg_id); if (cio_debug_trace_id) - debug_unregister (cio_debug_trace_id); + debug_unregister(cio_debug_trace_id); if (cio_debug_crw_id) - debug_unregister (cio_debug_crw_id); + debug_unregister(cio_debug_crw_id); printk(KERN_WARNING"cio: could not initialize debugging\n"); return -1; } @@ -147,7 +146,7 @@ cio_tpi(void) spin_lock(sch->lock); memcpy (&sch->schib.scsw, &irb->scsw, sizeof (struct scsw)); if (sch->driver && sch->driver->irq) - sch->driver->irq(&sch->dev); + sch->driver->irq(sch); spin_unlock(sch->lock); irq_exit (); _local_bh_enable(); @@ -184,33 +183,35 @@ cio_start_key (struct subchannel *sch, /* subchannel structure */ { char dbf_txt[15]; int ccode; + struct orb *orb; - CIO_TRACE_EVENT (4, "stIO"); - CIO_TRACE_EVENT (4, sch->dev.bus_id); + CIO_TRACE_EVENT(4, "stIO"); + CIO_TRACE_EVENT(4, sch->dev.bus_id); + orb = &to_io_private(sch)->orb; /* sch is always under 2G. */ - sch->orb.intparm = (__u32)(unsigned long)sch; - sch->orb.fmt = 1; + orb->intparm = (u32)(addr_t)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->lpm; + orb->pfch = sch->options.prefetch == 0; + orb->spnd = sch->options.suspend; + orb->ssic = sch->options.suspend && sch->options.inter; + orb->lpm = (lpm != 0) ? lpm : sch->lpm; #ifdef CONFIG_64BIT /* * for 64 bit we always support 64 bit IDAWs with 4k page size only */ - sch->orb.c64 = 1; - sch->orb.i2k = 0; + orb->c64 = 1; + orb->i2k = 0; #endif - sch->orb.key = key >> 4; + orb->key = key >> 4; /* issue "Start Subchannel" */ - sch->orb.cpa = (__u32) __pa (cpa); - ccode = ssch (sch->schid, &sch->orb); + orb->cpa = (__u32) __pa(cpa); + ccode = ssch(sch->schid, orb); /* process condition code */ - sprintf (dbf_txt, "ccode:%d", ccode); - CIO_TRACE_EVENT (4, dbf_txt); + sprintf(dbf_txt, "ccode:%d", ccode); + CIO_TRACE_EVENT(4, dbf_txt); switch (ccode) { case 0: @@ -405,8 +406,8 @@ cio_modify (struct subchannel *sch) /* * Enable subchannel. */ -int -cio_enable_subchannel (struct subchannel *sch, unsigned int isc) +int cio_enable_subchannel(struct subchannel *sch, unsigned int isc, + u32 intparm) { char dbf_txt[15]; int ccode; @@ -425,7 +426,7 @@ cio_enable_subchannel (struct subchannel *sch, unsigned int isc) 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; + sch->schib.pmcw.intparm = intparm; ret = cio_modify(sch); if (ret == -ENODEV) break; @@ -567,7 +568,7 @@ cio_validate_subchannel (struct subchannel *sch, struct subchannel_id schid) */ if (sch->st != 0) { CIO_DEBUG(KERN_INFO, 0, - "cio: Subchannel 0.%x.%04x reports " + "Subchannel 0.%x.%04x reports " "non-I/O subchannel type %04X\n", sch->schid.ssid, sch->schid.sch_no, sch->st); /* We stop here for non-io subchannels. */ @@ -576,11 +577,11 @@ cio_validate_subchannel (struct subchannel *sch, struct subchannel_id schid) } /* Initialization for io subchannels. */ - if (!sch->schib.pmcw.dnv) { - /* io subchannel but device number is invalid. */ + if (!css_sch_is_valid(&sch->schib)) { err = -ENODEV; goto out; } + /* Devno is valid. */ if (is_blacklisted (sch->schid.ssid, sch->schib.pmcw.dev)) { /* @@ -600,7 +601,7 @@ cio_validate_subchannel (struct subchannel *sch, struct subchannel_id schid) sch->lpm = sch->schib.pmcw.pam & sch->opm; CIO_DEBUG(KERN_INFO, 0, - "cio: Detected device %04x on subchannel 0.%x.%04X" + "Detected device %04x on subchannel 0.%x.%04X" " - PIM = %02X, PAM = %02X, POM = %02X\n", sch->schib.pmcw.dev, sch->schid.ssid, sch->schid.sch_no, sch->schib.pmcw.pim, @@ -680,7 +681,7 @@ do_IRQ (struct pt_regs *regs) sizeof (irb->scsw)); /* Call interrupt handler if there is one. */ if (sch->driver && sch->driver->irq) - sch->driver->irq(&sch->dev); + sch->driver->irq(sch); } if (sch) spin_unlock(sch->lock); @@ -698,8 +699,14 @@ do_IRQ (struct pt_regs *regs) #ifdef CONFIG_CCW_CONSOLE static struct subchannel console_subchannel; +static struct io_subchannel_private console_priv; static int console_subchannel_in_use; +void *cio_get_console_priv(void) +{ + return &console_priv; +} + /* * busy wait for the next interrupt on the console */ @@ -738,9 +745,9 @@ cio_test_for_console(struct subchannel_id schid, void *data) { if (stsch_err(schid, &console_subchannel.schib) != 0) return -ENXIO; - if (console_subchannel.schib.pmcw.dnv && - console_subchannel.schib.pmcw.dev == - console_devno) { + if ((console_subchannel.schib.pmcw.st == SUBCHANNEL_TYPE_IO) && + console_subchannel.schib.pmcw.dnv && + (console_subchannel.schib.pmcw.dev == console_devno)) { console_irq = schid.sch_no; return 1; /* found */ } @@ -758,6 +765,7 @@ cio_get_console_sch_no(void) /* VM provided us with the irq number of the console. */ schid.sch_no = console_irq; if (stsch(schid, &console_subchannel.schib) != 0 || + (console_subchannel.schib.pmcw.st != SUBCHANNEL_TYPE_IO) || !console_subchannel.schib.pmcw.dnv) return -1; console_devno = console_subchannel.schib.pmcw.dev; @@ -804,7 +812,7 @@ cio_probe_console(void) ctl_set_bit(6, 24); console_subchannel.schib.pmcw.isc = 7; console_subchannel.schib.pmcw.intparm = - (__u32)(unsigned long)&console_subchannel; + (u32)(addr_t)&console_subchannel; ret = cio_modify(&console_subchannel); if (ret) { console_subchannel_in_use = 0; @@ -1022,7 +1030,7 @@ static int __reipl_subchannel_match(struct subchannel_id schid, void *data) if (stsch_reset(schid, &schib)) return -ENXIO; - if (schib.pmcw.dnv && + if ((schib.pmcw.st == SUBCHANNEL_TYPE_IO) && schib.pmcw.dnv && (schib.pmcw.dev == match_id->devid.devno) && (schid.ssid == match_id->devid.ssid)) { match_id->schid = schid; @@ -1068,6 +1076,8 @@ int __init cio_get_iplinfo(struct cio_iplinfo *iplinfo) return -ENODEV; if (stsch(schid, &schib)) return -ENODEV; + if (schib.pmcw.st != SUBCHANNEL_TYPE_IO) + return -ENODEV; if (!schib.pmcw.dnv) return -ENODEV; iplinfo->devno = schib.pmcw.dev; diff --git a/drivers/s390/cio/cio.h b/drivers/s390/cio/cio.h index 7446c39951a..52afa4c784d 100644 --- a/drivers/s390/cio/cio.h +++ b/drivers/s390/cio/cio.h @@ -11,32 +11,32 @@ * 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 ...*/ + 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. */ @@ -52,31 +52,6 @@ struct schib { __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 { struct subchannel_id schid; @@ -85,7 +60,7 @@ struct subchannel { enum { SUBCHANNEL_TYPE_IO = 0, SUBCHANNEL_TYPE_CHSC = 1, - SUBCHANNEL_TYPE_MESSAGE = 2, + SUBCHANNEL_TYPE_MSG = 2, SUBCHANNEL_TYPE_ADM = 3, } st; /* subchannel type */ @@ -99,11 +74,10 @@ struct subchannel { __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 chsc_ssd_info ssd_info; /* subchannel description */ struct device dev; /* entry in device tree */ struct css_driver *driver; + void *private; /* private per subchannel type data */ } __attribute__ ((aligned(8))); #define IO_INTERRUPT_TYPE 0 /* I/O interrupt type */ @@ -111,7 +85,7 @@ struct subchannel { #define to_subchannel(n) container_of(n, struct subchannel, dev) extern int cio_validate_subchannel (struct subchannel *, struct subchannel_id); -extern int cio_enable_subchannel (struct subchannel *, unsigned int); +extern int cio_enable_subchannel(struct subchannel *, unsigned int, u32); extern int cio_disable_subchannel (struct subchannel *); extern int cio_cancel (struct subchannel *); extern int cio_clear (struct subchannel *); @@ -125,6 +99,7 @@ extern int cio_get_options (struct subchannel *); extern int cio_modify (struct subchannel *); int cio_create_sch_lock(struct subchannel *); +void do_adapter_IO(void); /* Use with care. */ #ifdef CONFIG_CCW_CONSOLE @@ -133,10 +108,12 @@ extern void cio_release_console(void); extern int cio_is_console(struct subchannel_id); extern struct subchannel *cio_get_console_subchannel(void); extern spinlock_t * cio_get_console_lock(void); +extern void *cio_get_console_priv(void); #else #define cio_is_console(schid) 0 #define cio_get_console_subchannel() NULL -#define cio_get_console_lock() NULL; +#define cio_get_console_lock() NULL +#define cio_get_console_priv() NULL #endif extern int cio_show_msg; diff --git a/drivers/s390/cio/cio_debug.h b/drivers/s390/cio/cio_debug.h index c9bf8989930..d7429ef6c66 100644 --- a/drivers/s390/cio/cio_debug.h +++ b/drivers/s390/cio/cio_debug.h @@ -8,20 +8,19 @@ extern debug_info_t *cio_debug_msg_id; extern debug_info_t *cio_debug_trace_id; extern debug_info_t *cio_debug_crw_id; -#define CIO_TRACE_EVENT(imp, txt) do { \ +#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); \ +#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); \ +#define CIO_CRW_EVENT(imp, args...) do { \ + debug_sprintf_event(cio_debug_crw_id, imp , ##args); \ } while (0) -static inline void -CIO_HEX_EVENT(int level, void *data, int length) +static inline void CIO_HEX_EVENT(int level, void *data, int length) { if (unlikely(!cio_debug_trace_id)) return; @@ -32,9 +31,10 @@ CIO_HEX_EVENT(int level, void *data, int length) } } -#define CIO_DEBUG(printk_level,event_level,msg...) ({ \ - if (cio_show_msg) printk(printk_level msg); \ - CIO_MSG_EVENT (event_level, msg); \ -}) +#define CIO_DEBUG(printk_level, event_level, msg...) do { \ + if (cio_show_msg) \ + printk(printk_level "cio: " msg); \ + CIO_MSG_EVENT(event_level, msg); \ + } while (0) #endif diff --git a/drivers/s390/cio/css.c b/drivers/s390/cio/css.c index c3df2cd009a..3b45bbe6cce 100644 --- a/drivers/s390/cio/css.c +++ b/drivers/s390/cio/css.c @@ -51,6 +51,62 @@ for_each_subchannel(int(*fn)(struct subchannel_id, void *), void *data) return ret; } +struct cb_data { + void *data; + struct idset *set; + int (*fn_known_sch)(struct subchannel *, void *); + int (*fn_unknown_sch)(struct subchannel_id, void *); +}; + +static int call_fn_known_sch(struct device *dev, void *data) +{ + struct subchannel *sch = to_subchannel(dev); + struct cb_data *cb = data; + int rc = 0; + + idset_sch_del(cb->set, sch->schid); + if (cb->fn_known_sch) + rc = cb->fn_known_sch(sch, cb->data); + return rc; +} + +static int call_fn_unknown_sch(struct subchannel_id schid, void *data) +{ + struct cb_data *cb = data; + int rc = 0; + + if (idset_sch_contains(cb->set, schid)) + rc = cb->fn_unknown_sch(schid, cb->data); + return rc; +} + +int for_each_subchannel_staged(int (*fn_known)(struct subchannel *, void *), + int (*fn_unknown)(struct subchannel_id, + void *), void *data) +{ + struct cb_data cb; + int rc; + + cb.set = idset_sch_new(); + if (!cb.set) + return -ENOMEM; + idset_fill(cb.set); + cb.data = data; + cb.fn_known_sch = fn_known; + cb.fn_unknown_sch = fn_unknown; + /* Process registered subchannels. */ + rc = bus_for_each_dev(&css_bus_type, NULL, &cb, call_fn_known_sch); + if (rc) + goto out; + /* Process unregistered subchannels. */ + if (fn_unknown) + rc = for_each_subchannel(call_fn_unknown_sch, &cb); +out: + idset_free(cb.set); + + return rc; +} + static struct subchannel * css_alloc_subchannel(struct subchannel_id schid) { @@ -77,7 +133,7 @@ css_alloc_subchannel(struct subchannel_id schid) * This is fine even on 64bit since the subchannel is always located * under 2G. */ - sch->schib.pmcw.intparm = (__u32)(unsigned long)sch; + sch->schib.pmcw.intparm = (u32)(addr_t)sch; ret = cio_modify(sch); if (ret) { kfree(sch->lock); @@ -237,11 +293,25 @@ get_subchannel_by_schid(struct subchannel_id schid) return dev ? to_subchannel(dev) : NULL; } +/** + * css_sch_is_valid() - check if a subchannel is valid + * @schib: subchannel information block for the subchannel + */ +int css_sch_is_valid(struct schib *schib) +{ + if ((schib->pmcw.st == SUBCHANNEL_TYPE_IO) && !schib->pmcw.dnv) + return 0; + return 1; +} +EXPORT_SYMBOL_GPL(css_sch_is_valid); + static int css_get_subchannel_status(struct subchannel *sch) { struct schib schib; - if (stsch(sch->schid, &schib) || !schib.pmcw.dnv) + if (stsch(sch->schid, &schib)) + return CIO_GONE; + if (!css_sch_is_valid(&schib)) return CIO_GONE; if (sch->schib.pmcw.dnv && (schib.pmcw.dev != sch->schib.pmcw.dev)) return CIO_REVALIDATE; @@ -293,7 +363,7 @@ static int css_evaluate_known_subchannel(struct subchannel *sch, int slow) action = UNREGISTER; if (sch->driver && sch->driver->notify) { spin_unlock_irqrestore(sch->lock, flags); - ret = sch->driver->notify(&sch->dev, event); + ret = sch->driver->notify(sch, event); spin_lock_irqsave(sch->lock, flags); if (ret) action = NONE; @@ -349,7 +419,7 @@ static int css_evaluate_new_subchannel(struct subchannel_id schid, int slow) /* Will be done on the slow path. */ return -EAGAIN; } - if (stsch_err(schid, &schib) || !schib.pmcw.dnv) { + if (stsch_err(schid, &schib) || !css_sch_is_valid(&schib)) { /* Unusable - ignore. */ return 0; } @@ -388,20 +458,56 @@ static int __init slow_subchannel_init(void) return 0; } -static void css_slow_path_func(struct work_struct *unused) +static int slow_eval_known_fn(struct subchannel *sch, void *data) { - struct subchannel_id schid; + int eval; + int rc; - CIO_TRACE_EVENT(4, "slowpath"); spin_lock_irq(&slow_subchannel_lock); - init_subchannel_id(&schid); - while (idset_sch_get_first(slow_subchannel_set, &schid)) { - idset_sch_del(slow_subchannel_set, schid); - spin_unlock_irq(&slow_subchannel_lock); - css_evaluate_subchannel(schid, 1); - spin_lock_irq(&slow_subchannel_lock); + eval = idset_sch_contains(slow_subchannel_set, sch->schid); + idset_sch_del(slow_subchannel_set, sch->schid); + spin_unlock_irq(&slow_subchannel_lock); + if (eval) { + rc = css_evaluate_known_subchannel(sch, 1); + if (rc == -EAGAIN) + css_schedule_eval(sch->schid); } + return 0; +} + +static int slow_eval_unknown_fn(struct subchannel_id schid, void *data) +{ + int eval; + int rc = 0; + + spin_lock_irq(&slow_subchannel_lock); + eval = idset_sch_contains(slow_subchannel_set, schid); + idset_sch_del(slow_subchannel_set, schid); spin_unlock_irq(&slow_subchannel_lock); + if (eval) { + rc = css_evaluate_new_subchannel(schid, 1); + switch (rc) { + case -EAGAIN: + css_schedule_eval(schid); + rc = 0; + break; + case -ENXIO: + case -ENOMEM: + case -EIO: + /* These should abort looping */ + break; + default: + rc = 0; + } + } + return rc; +} + +static void css_slow_path_func(struct work_struct *unused) +{ + CIO_TRACE_EVENT(4, "slowpath"); + for_each_subchannel_staged(slow_eval_known_fn, slow_eval_unknown_fn, + NULL); } static DECLARE_WORK(slow_path_work, css_slow_path_func); @@ -430,7 +536,6 @@ void css_schedule_eval_all(void) /* Reprobe subchannel if unregistered. */ static int reprobe_subchannel(struct subchannel_id schid, void *data) { - struct subchannel *sch; int ret; CIO_MSG_EVENT(6, "cio: reprobe 0.%x.%04x\n", @@ -438,13 +543,6 @@ static int reprobe_subchannel(struct subchannel_id schid, void *data) if (need_reprobe) return -EAGAIN; - sch = get_subchannel_by_schid(schid); - if (sch) { - /* Already known. */ - put_device(&sch->dev); - return 0; - } - ret = css_probe_device(schid); switch (ret) { case 0: @@ -472,7 +570,7 @@ static void reprobe_all(struct work_struct *unused) /* Make sure initial subchannel scan is done. */ wait_event(ccw_device_init_wq, atomic_read(&ccw_device_init_count) == 0); - ret = for_each_subchannel(reprobe_subchannel, NULL); + ret = for_each_subchannel_staged(NULL, reprobe_subchannel, NULL); CIO_MSG_EVENT(2, "reprobe done (rc=%d, need_reprobe=%d)\n", ret, need_reprobe); @@ -787,8 +885,8 @@ int sch_is_pseudo_sch(struct subchannel *sch) 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); + struct subchannel *sch = to_subchannel(dev); + struct css_driver *driver = to_cssdriver(drv); if (sch->st == driver->subchannel_type) return 1; @@ -796,32 +894,36 @@ css_bus_match (struct device *dev, struct device_driver *drv) return 0; } -static int -css_probe (struct device *dev) +static int css_probe(struct device *dev) { struct subchannel *sch; + int ret; sch = to_subchannel(dev); - sch->driver = container_of (dev->driver, struct css_driver, drv); - return (sch->driver->probe ? sch->driver->probe(sch) : 0); + sch->driver = to_cssdriver(dev->driver); + ret = sch->driver->probe ? sch->driver->probe(sch) : 0; + if (ret) + sch->driver = NULL; + return ret; } -static int -css_remove (struct device *dev) +static int css_remove(struct device *dev) { struct subchannel *sch; + int ret; sch = to_subchannel(dev); - return (sch->driver->remove ? sch->driver->remove(sch) : 0); + ret = sch->driver->remove ? sch->driver->remove(sch) : 0; + sch->driver = NULL; + return ret; } -static void -css_shutdown (struct device *dev) +static void css_shutdown(struct device *dev) { struct subchannel *sch; sch = to_subchannel(dev); - if (sch->driver->shutdown) + if (sch->driver && sch->driver->shutdown) sch->driver->shutdown(sch); } @@ -833,6 +935,34 @@ struct bus_type css_bus_type = { .shutdown = css_shutdown, }; +/** + * css_driver_register - register a css driver + * @cdrv: css driver to register + * + * This is mainly a wrapper around driver_register that sets name + * and bus_type in the embedded struct device_driver correctly. + */ +int css_driver_register(struct css_driver *cdrv) +{ + cdrv->drv.name = cdrv->name; + cdrv->drv.bus = &css_bus_type; + cdrv->drv.owner = cdrv->owner; + return driver_register(&cdrv->drv); +} +EXPORT_SYMBOL_GPL(css_driver_register); + +/** + * css_driver_unregister - unregister a css driver + * @cdrv: css driver to unregister + * + * This is a wrapper around driver_unregister. + */ +void css_driver_unregister(struct css_driver *cdrv) +{ + driver_unregister(&cdrv->drv); +} +EXPORT_SYMBOL_GPL(css_driver_unregister); + subsys_initcall(init_channel_subsystem); MODULE_LICENSE("GPL"); diff --git a/drivers/s390/cio/css.h b/drivers/s390/cio/css.h index 81215ef3243..b7055452355 100644 --- a/drivers/s390/cio/css.h +++ b/drivers/s390/cio/css.h @@ -58,64 +58,6 @@ struct pgid { __u32 tod_high; /* high word TOD clock */ } __attribute__ ((packed)); -#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 { - struct ccw_device *cdev; - struct subchannel *sch; - int state; /* device state */ - atomic_t onoff; - unsigned long registered; - struct ccw_dev_id dev_id; /* device id */ - struct subchannel_id schid; /* 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 */ - unsigned int intretry:1; /* retry internal operation */ - } __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[8]; /* path group IDs per chpid*/ - 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 @@ -123,25 +65,35 @@ struct ccw_device_private { */ struct subchannel; struct css_driver { + struct module *owner; 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 *); + void (*irq)(struct subchannel *); + int (*notify)(struct subchannel *, int); + void (*verify)(struct subchannel *); + void (*termination)(struct subchannel *); int (*probe)(struct subchannel *); int (*remove)(struct subchannel *); void (*shutdown)(struct subchannel *); + const char *name; }; +#define to_cssdriver(n) container_of(n, struct css_driver, drv) + /* * all css_drivers have the css_bus_type */ extern struct bus_type css_bus_type; +extern int css_driver_register(struct css_driver *); +extern void css_driver_unregister(struct css_driver *); + extern void css_sch_device_unregister(struct subchannel *); extern struct subchannel * get_subchannel_by_schid(struct subchannel_id); extern int css_init_done; +int for_each_subchannel_staged(int (*fn_known)(struct subchannel *, void *), + int (*fn_unknown)(struct subchannel_id, + void *), void *data); extern int for_each_subchannel(int(*fn)(struct subchannel_id, void *), void *); extern void css_process_crw(int, int); extern void css_reiterate_subchannels(void); @@ -188,6 +140,8 @@ void css_schedule_eval(struct subchannel_id schid); void css_schedule_eval_all(void); int sch_is_pseudo_sch(struct subchannel *); +struct schib; +int css_sch_is_valid(struct schib *); extern struct workqueue_struct *slow_path_wq; diff --git a/drivers/s390/cio/device.c b/drivers/s390/cio/device.c index 74f6b539974..d35dc3f25d0 100644 --- a/drivers/s390/cio/device.c +++ b/drivers/s390/cio/device.c @@ -17,6 +17,7 @@ #include <linux/list.h> #include <linux/device.h> #include <linux/workqueue.h> +#include <linux/timer.h> #include <asm/ccwdev.h> #include <asm/cio.h> @@ -28,6 +29,12 @@ #include "css.h" #include "device.h" #include "ioasm.h" +#include "io_sch.h" + +static struct timer_list recovery_timer; +static spinlock_t recovery_lock; +static int recovery_phase; +static const unsigned long recovery_delay[] = { 3, 30, 300 }; /******************* bus type handling ***********************/ @@ -115,19 +122,18 @@ static int ccw_uevent(struct device *dev, struct kobj_uevent_env *env) struct bus_type ccw_bus_type; -static int io_subchannel_probe (struct subchannel *); -static int io_subchannel_remove (struct subchannel *); -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_irq(struct subchannel *); +static int io_subchannel_probe(struct subchannel *); +static int io_subchannel_remove(struct subchannel *); +static int io_subchannel_notify(struct subchannel *, int); +static void io_subchannel_verify(struct subchannel *); +static void io_subchannel_ioterm(struct subchannel *); static void io_subchannel_shutdown(struct subchannel *); static struct css_driver io_subchannel_driver = { + .owner = THIS_MODULE, .subchannel_type = SUBCHANNEL_TYPE_IO, - .drv = { - .name = "io_subchannel", - .bus = &css_bus_type, - }, + .name = "io_subchannel", .irq = io_subchannel_irq, .notify = io_subchannel_notify, .verify = io_subchannel_verify, @@ -142,6 +148,8 @@ struct workqueue_struct *ccw_device_notify_work; wait_queue_head_t ccw_device_init_wq; atomic_t ccw_device_init_count; +static void recovery_func(unsigned long data); + static int __init init_ccw_bus_type (void) { @@ -149,6 +157,7 @@ init_ccw_bus_type (void) init_waitqueue_head(&ccw_device_init_wq); atomic_set(&ccw_device_init_count, 0); + setup_timer(&recovery_timer, recovery_func, 0); ccw_device_work = create_singlethread_workqueue("cio"); if (!ccw_device_work) @@ -166,7 +175,8 @@ init_ccw_bus_type (void) if ((ret = bus_register (&ccw_bus_type))) goto out_err; - if ((ret = driver_register(&io_subchannel_driver.drv))) + ret = css_driver_register(&io_subchannel_driver); + if (ret) goto out_err; wait_event(ccw_device_init_wq, @@ -186,7 +196,7 @@ out_err: static void __exit cleanup_ccw_bus_type (void) { - driver_unregister(&io_subchannel_driver.drv); + css_driver_unregister(&io_subchannel_driver); bus_unregister(&ccw_bus_type); destroy_workqueue(ccw_device_notify_work); destroy_workqueue(ccw_device_work); @@ -773,7 +783,7 @@ static void sch_attach_device(struct subchannel *sch, { css_update_ssd_info(sch); spin_lock_irq(sch->lock); - sch->dev.driver_data = cdev; + sch_set_cdev(sch, cdev); cdev->private->schid = sch->schid; cdev->ccwlock = sch->lock; device_trigger_reprobe(sch); @@ -795,7 +805,7 @@ static void sch_attach_disconnected_device(struct subchannel *sch, put_device(&other_sch->dev); return; } - other_sch->dev.driver_data = NULL; + sch_set_cdev(other_sch, NULL); /* No need to keep a subchannel without ccw device around. */ css_sch_device_unregister(other_sch); put_device(&other_sch->dev); @@ -831,12 +841,12 @@ static void sch_create_and_recog_new_device(struct subchannel *sch) return; } spin_lock_irq(sch->lock); - sch->dev.driver_data = cdev; + sch_set_cdev(sch, cdev); spin_unlock_irq(sch->lock); /* Start recognition for the new ccw device. */ if (io_subchannel_recog(cdev, sch)) { spin_lock_irq(sch->lock); - sch->dev.driver_data = NULL; + sch_set_cdev(sch, NULL); spin_unlock_irq(sch->lock); if (cdev->dev.release) cdev->dev.release(&cdev->dev); @@ -940,7 +950,7 @@ io_subchannel_register(struct work_struct *work) cdev->private->dev_id.devno, ret); put_device(&cdev->dev); spin_lock_irqsave(sch->lock, flags); - sch->dev.driver_data = NULL; + sch_set_cdev(sch, NULL); spin_unlock_irqrestore(sch->lock, flags); kfree (cdev->private); kfree (cdev); @@ -1022,7 +1032,7 @@ io_subchannel_recog(struct ccw_device *cdev, struct subchannel *sch) int rc; struct ccw_device_private *priv; - sch->dev.driver_data = cdev; + sch_set_cdev(sch, cdev); sch->driver = &io_subchannel_driver; cdev->ccwlock = sch->lock; @@ -1082,7 +1092,7 @@ static void ccw_device_move_to_sch(struct work_struct *work) } if (former_parent) { spin_lock_irq(former_parent->lock); - former_parent->dev.driver_data = NULL; + sch_set_cdev(former_parent, NULL); spin_unlock_irq(former_parent->lock); css_sch_device_unregister(former_parent); /* Reset intparm to zeroes. */ @@ -1096,6 +1106,18 @@ out: put_device(&cdev->dev); } +static void io_subchannel_irq(struct subchannel *sch) +{ + struct ccw_device *cdev; + + cdev = sch_get_cdev(sch); + + CIO_TRACE_EVENT(3, "IRQ"); + CIO_TRACE_EVENT(3, sch->dev.bus_id); + if (cdev) + dev_fsm_event(cdev, DEV_EVENT_INTERRUPT); +} + static int io_subchannel_probe (struct subchannel *sch) { @@ -1104,13 +1126,13 @@ io_subchannel_probe (struct subchannel *sch) unsigned long flags; struct ccw_dev_id dev_id; - if (sch->dev.driver_data) { + cdev = sch_get_cdev(sch); + if (cdev) { /* * 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; cdev->dev.groups = ccwdev_attr_groups; device_initialize(&cdev->dev); ccw_device_register(cdev); @@ -1132,6 +1154,11 @@ io_subchannel_probe (struct subchannel *sch) */ dev_id.devno = sch->schib.pmcw.dev; dev_id.ssid = sch->schid.ssid; + /* Allocate I/O subchannel private data. */ + sch->private = kzalloc(sizeof(struct io_subchannel_private), + GFP_KERNEL | GFP_DMA); + if (!sch->private) + return -ENOMEM; cdev = get_disc_ccwdev_by_dev_id(&dev_id, NULL); if (!cdev) cdev = get_orphaned_ccwdev_by_dev_id(to_css(sch->dev.parent), @@ -1149,16 +1176,18 @@ io_subchannel_probe (struct subchannel *sch) return 0; } cdev = io_subchannel_create_ccwdev(sch); - if (IS_ERR(cdev)) + if (IS_ERR(cdev)) { + kfree(sch->private); return PTR_ERR(cdev); - + } rc = io_subchannel_recog(cdev, sch); if (rc) { spin_lock_irqsave(sch->lock, flags); - sch->dev.driver_data = NULL; + sch_set_cdev(sch, NULL); spin_unlock_irqrestore(sch->lock, flags); if (cdev->dev.release) cdev->dev.release(&cdev->dev); + kfree(sch->private); } return rc; @@ -1170,25 +1199,25 @@ io_subchannel_remove (struct subchannel *sch) struct ccw_device *cdev; unsigned long flags; - if (!sch->dev.driver_data) + cdev = sch_get_cdev(sch); + if (!cdev) return 0; - cdev = sch->dev.driver_data; /* Set ccw device to not operational and drop reference. */ spin_lock_irqsave(cdev->ccwlock, flags); - sch->dev.driver_data = NULL; + sch_set_cdev(sch, NULL); cdev->private->state = DEV_STATE_NOT_OPER; spin_unlock_irqrestore(cdev->ccwlock, flags); ccw_device_unregister(cdev); put_device(&cdev->dev); + kfree(sch->private); return 0; } -static int -io_subchannel_notify(struct device *dev, int event) +static int io_subchannel_notify(struct subchannel *sch, int event) { struct ccw_device *cdev; - cdev = dev->driver_data; + cdev = sch_get_cdev(sch); if (!cdev) return 0; if (!cdev->drv) @@ -1198,22 +1227,20 @@ io_subchannel_notify(struct device *dev, int event) return cdev->drv->notify ? cdev->drv->notify(cdev, event) : 0; } -static void -io_subchannel_verify(struct device *dev) +static void io_subchannel_verify(struct subchannel *sch) { struct ccw_device *cdev; - cdev = dev->driver_data; + cdev = sch_get_cdev(sch); if (cdev) dev_fsm_event(cdev, DEV_EVENT_VERIFY); } -static void -io_subchannel_ioterm(struct device *dev) +static void io_subchannel_ioterm(struct subchannel *sch) { struct ccw_device *cdev; - cdev = dev->driver_data; + cdev = sch_get_cdev(sch); if (!cdev) return; /* Internal I/O will be retried by the interrupt handler. */ @@ -1231,7 +1258,7 @@ io_subchannel_shutdown(struct subchannel *sch) struct ccw_device *cdev; int ret; - cdev = sch->dev.driver_data; + cdev = sch_get_cdev(sch); if (cio_is_console(sch->schid)) return; @@ -1271,6 +1298,9 @@ ccw_device_console_enable (struct ccw_device *cdev, struct subchannel *sch) { int rc; + /* Attach subchannel private data. */ + sch->private = cio_get_console_priv(); + memset(sch->private, 0, sizeof(struct io_subchannel_private)); /* Initialize the ccw_device structure. */ cdev->dev.parent= &sch->dev; rc = io_subchannel_recog(cdev, sch); @@ -1456,6 +1486,7 @@ int ccw_driver_register(struct ccw_driver *cdriver) drv->bus = &ccw_bus_type; drv->name = cdriver->name; + drv->owner = cdriver->owner; return driver_register(drv); } @@ -1481,6 +1512,60 @@ ccw_device_get_subchannel_id(struct ccw_device *cdev) return sch->schid; } +static int recovery_check(struct device *dev, void *data) +{ + struct ccw_device *cdev = to_ccwdev(dev); + int *redo = data; + + spin_lock_irq(cdev->ccwlock); + switch (cdev->private->state) { + case DEV_STATE_DISCONNECTED: + CIO_MSG_EVENT(3, "recovery: trigger 0.%x.%04x\n", + cdev->private->dev_id.ssid, + cdev->private->dev_id.devno); + dev_fsm_event(cdev, DEV_EVENT_VERIFY); + *redo = 1; + break; + case DEV_STATE_DISCONNECTED_SENSE_ID: + *redo = 1; + break; + } + spin_unlock_irq(cdev->ccwlock); + + return 0; +} + +static void recovery_func(unsigned long data) +{ + int redo = 0; + + bus_for_each_dev(&ccw_bus_type, NULL, &redo, recovery_check); + if (redo) { + spin_lock_irq(&recovery_lock); + if (!timer_pending(&recovery_timer)) { + if (recovery_phase < ARRAY_SIZE(recovery_delay) - 1) + recovery_phase++; + mod_timer(&recovery_timer, jiffies + + recovery_delay[recovery_phase] * HZ); + } + spin_unlock_irq(&recovery_lock); + } else + CIO_MSG_EVENT(2, "recovery: end\n"); +} + +void ccw_device_schedule_recovery(void) +{ + unsigned long flags; + + CIO_MSG_EVENT(2, "recovery: schedule\n"); + spin_lock_irqsave(&recovery_lock, flags); + if (!timer_pending(&recovery_timer) || (recovery_phase != 0)) { + recovery_phase = 0; + mod_timer(&recovery_timer, jiffies + recovery_delay[0] * HZ); + } + spin_unlock_irqrestore(&recovery_lock, flags); +} + MODULE_LICENSE("GPL"); EXPORT_SYMBOL(ccw_device_set_online); EXPORT_SYMBOL(ccw_device_set_offline); diff --git a/drivers/s390/cio/device.h b/drivers/s390/cio/device.h index 0d408960043..d40a2ffaa00 100644 --- a/drivers/s390/cio/device.h +++ b/drivers/s390/cio/device.h @@ -5,6 +5,8 @@ #include <asm/atomic.h> #include <linux/wait.h> +#include "io_sch.h" + /* * states of the device statemachine */ @@ -74,7 +76,6 @@ extern struct workqueue_struct *ccw_device_notify_work; extern wait_queue_head_t ccw_device_init_wq; extern atomic_t ccw_device_init_count; -void io_subchannel_irq (struct device *pdev); void io_subchannel_recog_done(struct ccw_device *cdev); int ccw_device_cancel_halt_clear(struct ccw_device *); @@ -87,6 +88,8 @@ int ccw_device_recognition(struct ccw_device *); int ccw_device_online(struct ccw_device *); int ccw_device_offline(struct ccw_device *); +void ccw_device_schedule_recovery(void); + /* 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 *); diff --git a/drivers/s390/cio/device_fsm.c b/drivers/s390/cio/device_fsm.c index bfad421cda6..4b92c84fb43 100644 --- a/drivers/s390/cio/device_fsm.c +++ b/drivers/s390/cio/device_fsm.c @@ -25,14 +25,16 @@ #include "ioasm.h" #include "chp.h" +static int timeout_log_enabled; + int device_is_online(struct subchannel *sch) { struct ccw_device *cdev; - if (!sch->dev.driver_data) + cdev = sch_get_cdev(sch); + if (!cdev) return 0; - cdev = sch->dev.driver_data; return (cdev->private->state == DEV_STATE_ONLINE); } @@ -41,9 +43,9 @@ device_is_disconnected(struct subchannel *sch) { struct ccw_device *cdev; - if (!sch->dev.driver_data) + cdev = sch_get_cdev(sch); + if (!cdev) return 0; - cdev = sch->dev.driver_data; return (cdev->private->state == DEV_STATE_DISCONNECTED || cdev->private->state == DEV_STATE_DISCONNECTED_SENSE_ID); } @@ -53,19 +55,21 @@ device_set_disconnected(struct subchannel *sch) { struct ccw_device *cdev; - if (!sch->dev.driver_data) + cdev = sch_get_cdev(sch); + if (!cdev) return; - cdev = sch->dev.driver_data; ccw_device_set_timeout(cdev, 0); cdev->private->flags.fake_irb = 0; cdev->private->state = DEV_STATE_DISCONNECTED; + if (cdev->online) + ccw_device_schedule_recovery(); } void device_set_intretry(struct subchannel *sch) { struct ccw_device *cdev; - cdev = sch->dev.driver_data; + cdev = sch_get_cdev(sch); if (!cdev) return; cdev->private->flags.intretry = 1; @@ -75,13 +79,62 @@ int device_trigger_verify(struct subchannel *sch) { struct ccw_device *cdev; - cdev = sch->dev.driver_data; + cdev = sch_get_cdev(sch); if (!cdev || !cdev->online) return -EINVAL; dev_fsm_event(cdev, DEV_EVENT_VERIFY); return 0; } +static int __init ccw_timeout_log_setup(char *unused) +{ + timeout_log_enabled = 1; + return 1; +} + +__setup("ccw_timeout_log", ccw_timeout_log_setup); + +static void ccw_timeout_log(struct ccw_device *cdev) +{ + struct schib schib; + struct subchannel *sch; + struct io_subchannel_private *private; + int cc; + + sch = to_subchannel(cdev->dev.parent); + private = to_io_private(sch); + cc = stsch(sch->schid, &schib); + + printk(KERN_WARNING "cio: ccw device timeout occurred at %llx, " + "device information:\n", get_clock()); + printk(KERN_WARNING "cio: orb:\n"); + print_hex_dump(KERN_WARNING, "cio: ", DUMP_PREFIX_NONE, 16, 1, + &private->orb, sizeof(private->orb), 0); + printk(KERN_WARNING "cio: ccw device bus id: %s\n", cdev->dev.bus_id); + printk(KERN_WARNING "cio: subchannel bus id: %s\n", sch->dev.bus_id); + printk(KERN_WARNING "cio: subchannel lpm: %02x, opm: %02x, " + "vpm: %02x\n", sch->lpm, sch->opm, sch->vpm); + + if ((void *)(addr_t)private->orb.cpa == &private->sense_ccw || + (void *)(addr_t)private->orb.cpa == cdev->private->iccws) + printk(KERN_WARNING "cio: last channel program (intern):\n"); + else + printk(KERN_WARNING "cio: last channel program:\n"); + + print_hex_dump(KERN_WARNING, "cio: ", DUMP_PREFIX_NONE, 16, 1, + (void *)(addr_t)private->orb.cpa, + sizeof(struct ccw1), 0); + printk(KERN_WARNING "cio: ccw device state: %d\n", + cdev->private->state); + printk(KERN_WARNING "cio: store subchannel returned: cc=%d\n", cc); + printk(KERN_WARNING "cio: schib:\n"); + print_hex_dump(KERN_WARNING, "cio: ", DUMP_PREFIX_NONE, 16, 1, + &schib, sizeof(schib), 0); + printk(KERN_WARNING "cio: ccw device flags:\n"); + print_hex_dump(KERN_WARNING, "cio: ", DUMP_PREFIX_NONE, 16, 1, + &cdev->private->flags, sizeof(cdev->private->flags), 0); +} + /* * Timeout function. It just triggers a DEV_EVENT_TIMEOUT. */ @@ -92,6 +145,8 @@ ccw_device_timeout(unsigned long data) cdev = (struct ccw_device *) data; spin_lock_irq(cdev->ccwlock); + if (timeout_log_enabled) + ccw_timeout_log(cdev); dev_fsm_event(cdev, DEV_EVENT_TIMEOUT); spin_unlock_irq(cdev->ccwlock); } @@ -122,9 +177,9 @@ device_kill_pending_timer(struct subchannel *sch) { struct ccw_device *cdev; - if (!sch->dev.driver_data) + cdev = sch_get_cdev(sch); + if (!cdev) return; - cdev = sch->dev.driver_data; ccw_device_set_timeout(cdev, 0); } @@ -268,7 +323,7 @@ ccw_device_recog_done(struct ccw_device *cdev, int state) switch (state) { case DEV_STATE_NOT_OPER: CIO_DEBUG(KERN_WARNING, 2, - "cio: SenseID : unknown device %04x on subchannel " + "SenseID : unknown device %04x on subchannel " "0.%x.%04x\n", cdev->private->dev_id.devno, sch->schid.ssid, sch->schid.sch_no); break; @@ -294,7 +349,7 @@ ccw_device_recog_done(struct ccw_device *cdev, int state) } /* Issue device info message. */ CIO_DEBUG(KERN_INFO, 2, - "cio: SenseID : device 0.%x.%04x reports: " + "SenseID : device 0.%x.%04x reports: " "CU Type/Mod = %04X/%02X, Dev Type/Mod = " "%04X/%02X\n", cdev->private->dev_id.ssid, @@ -304,7 +359,7 @@ ccw_device_recog_done(struct ccw_device *cdev, int state) break; case DEV_STATE_BOXED: CIO_DEBUG(KERN_WARNING, 2, - "cio: SenseID : boxed device %04x on subchannel " + "SenseID : boxed device %04x on subchannel " "0.%x.%04x\n", cdev->private->dev_id.devno, sch->schid.ssid, sch->schid.sch_no); break; @@ -349,7 +404,7 @@ ccw_device_oper_notify(struct work_struct *work) sch = to_subchannel(cdev->dev.parent); if (sch->driver && sch->driver->notify) { spin_unlock_irqrestore(cdev->ccwlock, flags); - ret = sch->driver->notify(&sch->dev, CIO_OPER); + ret = sch->driver->notify(sch, CIO_OPER); spin_lock_irqsave(cdev->ccwlock, flags); } else ret = 0; @@ -389,7 +444,7 @@ ccw_device_done(struct ccw_device *cdev, int state) if (state == DEV_STATE_BOXED) CIO_DEBUG(KERN_WARNING, 2, - "cio: Boxed device %04x on subchannel %04x\n", + "Boxed device %04x on subchannel %04x\n", cdev->private->dev_id.devno, sch->schid.sch_no); if (cdev->private->flags.donotify) { @@ -500,7 +555,8 @@ ccw_device_recognition(struct ccw_device *cdev) (cdev->private->state != DEV_STATE_BOXED)) return -EINVAL; sch = to_subchannel(cdev->dev.parent); - ret = cio_enable_subchannel(sch, sch->schib.pmcw.isc); + ret = cio_enable_subchannel(sch, sch->schib.pmcw.isc, + (u32)(addr_t)sch); if (ret != 0) /* Couldn't enable the subchannel for i/o. Sick device. */ return ret; @@ -587,9 +643,10 @@ ccw_device_verify_done(struct ccw_device *cdev, int err) default: /* Reset oper notify indication after verify error. */ cdev->private->flags.donotify = 0; - if (cdev->online) + if (cdev->online) { + ccw_device_set_timeout(cdev, 0); dev_fsm_event(cdev, DEV_EVENT_NOTOPER); - else + } else ccw_device_done(cdev, DEV_STATE_NOT_OPER); break; } @@ -610,7 +667,8 @@ ccw_device_online(struct ccw_device *cdev) 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); + ret = cio_enable_subchannel(sch, sch->schib.pmcw.isc, + (u32)(addr_t)sch); if (ret != 0) { /* Couldn't enable the subchannel for i/o. Sick device. */ if (ret == -ENODEV) @@ -937,7 +995,7 @@ void device_kill_io(struct subchannel *sch) int ret; struct ccw_device *cdev; - cdev = sch->dev.driver_data; + cdev = sch_get_cdev(sch); ret = ccw_device_cancel_halt_clear(cdev); if (ret == -EBUSY) { ccw_device_set_timeout(cdev, 3*HZ); @@ -990,7 +1048,8 @@ 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) + if (cio_enable_subchannel(sch, sch->schib.pmcw.isc, + (u32)(addr_t)sch) != 0) /* Couldn't enable the subchannel for i/o. Sick device. */ return; @@ -1006,9 +1065,9 @@ device_trigger_reprobe(struct subchannel *sch) { struct ccw_device *cdev; - if (!sch->dev.driver_data) + cdev = sch_get_cdev(sch); + if (!cdev) return; - cdev = sch->dev.driver_data; if (cdev->private->state != DEV_STATE_DISCONNECTED) return; @@ -1028,7 +1087,7 @@ device_trigger_reprobe(struct subchannel *sch) 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; + sch->schib.pmcw.intparm = (u32)(addr_t)sch; /* We should also udate ssd info, but this has to wait. */ /* Check if this is another device which appeared on the same sch. */ if (sch->schib.pmcw.dev != cdev->private->dev_id.devno) { @@ -1223,21 +1282,4 @@ fsm_func_t *dev_jumptable[NR_DEV_STATES][NR_DEV_EVENTS] = { }, }; -/* - * 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 index 156f3f9786b..dc4d87f77f6 100644 --- a/drivers/s390/cio/device_id.c +++ b/drivers/s390/cio/device_id.c @@ -24,18 +24,20 @@ #include "css.h" #include "device.h" #include "ioasm.h" +#include "io_sch.h" -/* - * Input : - * devno - device number - * ps - pointer to sense ID data area - * Output : none +/** + * vm_vdev_to_cu_type - Convert vm virtual device into control unit type + * for certain devices. + * @class: virtual device class + * @type: virtual device type + * + * Returns control unit type if a match was made or %0xffff otherwise. */ -static void -VM_virtual_device_info (__u16 devno, struct senseid *ps) +static int vm_vdev_to_cu_type(int class, int type) { static struct { - int vrdcvcla, vrdcvtyp, cu_type; + int class, type, cu_type; } vm_devices[] = { { 0x08, 0x01, 0x3480 }, { 0x08, 0x02, 0x3430 }, @@ -67,8 +69,26 @@ VM_virtual_device_info (__u16 devno, struct senseid *ps) { 0x40, 0xc0, 0x5080 }, { 0x80, 0x00, 0x3215 }, }; + int i; + + for (i = 0; i < ARRAY_SIZE(vm_devices); i++) + if (class == vm_devices[i].class && type == vm_devices[i].type) + return vm_devices[i].cu_type; + + return 0xffff; +} + +/** + * diag_get_dev_info - retrieve device information via DIAG X'210' + * @devno: device number + * @ps: pointer to sense ID data area + * + * Returns zero on success, non-zero otherwise. + */ +static int diag_get_dev_info(u16 devno, struct senseid *ps) +{ struct diag210 diag_data; - int ccode, i; + int ccode; CIO_TRACE_EVENT (4, "VMvdinf"); @@ -78,21 +98,21 @@ VM_virtual_device_info (__u16 devno, struct senseid *ps) }; ccode = diag210 (&diag_data); - ps->reserved = 0xff; + if ((ccode == 0) || (ccode == 2)) { + 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 < ARRAY_SIZE(vm_devices); 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; + /* Special case for osa devices. */ + if (diag_data.vrdcvcla == 0x02 && diag_data.vrdcvtyp == 0x20) { + ps->cu_type = 0x3088; + ps->cu_model = 0x60; + return 0; } + ps->cu_type = vm_vdev_to_cu_type(diag_data.vrdcvcla, + diag_data.vrdcvtyp); + if (ps->cu_type != 0xffff) + return 0; + } + 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, " @@ -101,6 +121,8 @@ VM_virtual_device_info (__u16 devno, struct senseid *ps) diag_data.vrdcvcla, diag_data.vrdcvtyp, diag_data.vrdcrccl, diag_data.vrdccrty, diag_data.vrdccrmd); + + return -ENODEV; } /* @@ -129,6 +151,7 @@ __ccw_device_sense_id_start(struct ccw_device *cdev) /* Try on every path. */ ret = -ENODEV; while (cdev->private->imask != 0) { + cdev->private->senseid.cu_type = 0xFFFF; if ((sch->opm & cdev->private->imask) != 0 && cdev->private->iretry > 0) { cdev->private->iretry--; @@ -152,7 +175,6 @@ 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); @@ -172,13 +194,7 @@ ccw_device_check_sense_id(struct ccw_device *cdev) 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)) { /* Retry Sense ID if requested. */ @@ -219,15 +235,26 @@ ccw_device_check_sense_id(struct ccw_device *cdev) return -EAGAIN; } if (irb->scsw.cc == 3) { - if ((sch->orb.lpm & - sch->schib.pmcw.pim & sch->schib.pmcw.pam) != 0) + u8 lpm; + + lpm = to_io_private(sch)->orb.lpm; + if ((lpm & sch->schib.pmcw.pim & sch->schib.pmcw.pam) != 0) CIO_MSG_EVENT(2, "SenseID : path %02X for device %04x " "on subchannel 0.%x.%04x is " - "'not operational'\n", sch->orb.lpm, + "'not operational'\n", lpm, cdev->private->dev_id.devno, sch->schid.ssid, sch->schid.sch_no); return -EACCES; } + + /* Did we get a proper answer ? */ + if (irb->scsw.cc == 0 && 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 */ + } + /* Hmm, whatever happened, try again. */ CIO_MSG_EVENT(2, "SenseID : start_IO() for device %04x on " "subchannel 0.%x.%04x returns status %02X%02X\n", @@ -280,20 +307,17 @@ ccw_device_sense_id_irq(struct ccw_device *cdev, enum dev_event dev_event) break; /* fall through. */ default: /* Sense ID failed. Try asking VM. */ - if (MACHINE_IS_VM) { - VM_virtual_device_info (cdev->private->dev_id.devno, + if (MACHINE_IS_VM) + ret = diag_get_dev_info(cdev->private->dev_id.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); + else + /* + * If we can't couldn't identify the device type we + * consider the device "not operational". + */ + ret = -ENODEV; + + ccw_device_sense_id_done(cdev, ret); break; } } diff --git a/drivers/s390/cio/device_ops.c b/drivers/s390/cio/device_ops.c index 7fd2dadc329..49b58eb0fab 100644 --- a/drivers/s390/cio/device_ops.c +++ b/drivers/s390/cio/device_ops.c @@ -501,7 +501,7 @@ ccw_device_stlck(struct ccw_device *cdev) return -ENOMEM; } spin_lock_irqsave(sch->lock, flags); - ret = cio_enable_subchannel(sch, 3); + ret = cio_enable_subchannel(sch, 3, (u32)(addr_t)sch); if (ret) goto out_unlock; /* diff --git a/drivers/s390/cio/device_pgid.c b/drivers/s390/cio/device_pgid.c index cb1879a9681..c52449a1f9f 100644 --- a/drivers/s390/cio/device_pgid.c +++ b/drivers/s390/cio/device_pgid.c @@ -22,6 +22,7 @@ #include "css.h" #include "device.h" #include "ioasm.h" +#include "io_sch.h" /* * Helper function called from interrupt context to decide whether an @@ -155,10 +156,13 @@ __ccw_device_check_sense_pgid(struct ccw_device *cdev) return -EAGAIN; } if (irb->scsw.cc == 3) { + u8 lpm; + + lpm = to_io_private(sch)->orb.lpm; CIO_MSG_EVENT(2, "SNID - Device %04x on Subchannel 0.%x.%04x," " lpm %02X, became 'not operational'\n", cdev->private->dev_id.devno, sch->schid.ssid, - sch->schid.sch_no, sch->orb.lpm); + sch->schid.sch_no, lpm); return -EACCES; } i = 8 - ffs(cdev->private->imask); diff --git a/drivers/s390/cio/device_status.c b/drivers/s390/cio/device_status.c index aa96e675259..ebe0848cfe3 100644 --- a/drivers/s390/cio/device_status.c +++ b/drivers/s390/cio/device_status.c @@ -20,6 +20,7 @@ #include "css.h" #include "device.h" #include "ioasm.h" +#include "io_sch.h" /* * Check for any kind of channel or interface control check but don't @@ -310,6 +311,7 @@ int ccw_device_do_sense(struct ccw_device *cdev, struct irb *irb) { struct subchannel *sch; + struct ccw1 *sense_ccw; sch = to_subchannel(cdev->dev.parent); @@ -326,15 +328,16 @@ ccw_device_do_sense(struct ccw_device *cdev, struct irb *irb) /* * We have ending status but no sense information. Do a basic sense. */ - 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; + sense_ccw = &to_io_private(sch)->sense_ccw; + sense_ccw->cmd_code = CCW_CMD_BASIC_SENSE; + sense_ccw->cda = (__u32) __pa(cdev->private->irb.ecw); + sense_ccw->count = SENSE_MAX_COUNT; + sense_ccw->flags = CCW_FLAG_SLI; /* Reset internal retry indication. */ cdev->private->flags.intretry = 0; - return cio_start (sch, &sch->sense_ccw, 0xff); + return cio_start(sch, sense_ccw, 0xff); } /* diff --git a/drivers/s390/cio/io_sch.h b/drivers/s390/cio/io_sch.h new file mode 100644 index 00000000000..8c613160bfc --- /dev/null +++ b/drivers/s390/cio/io_sch.h @@ -0,0 +1,163 @@ +#ifndef S390_IO_SCH_H +#define S390_IO_SCH_H + +#include "schid.h" + +/* + * 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))); + +struct io_subchannel_private { + struct orb orb; /* operation request block */ + struct ccw1 sense_ccw; /* static ccw for sense command */ +} __attribute__ ((aligned(8))); + +#define to_io_private(n) ((struct io_subchannel_private *)n->private) +#define sch_get_cdev(n) (dev_get_drvdata(&n->dev)) +#define sch_set_cdev(n, c) (dev_set_drvdata(&n->dev, c)) + +#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 { + struct ccw_device *cdev; + struct subchannel *sch; + int state; /* device state */ + atomic_t onoff; + unsigned long registered; + struct ccw_dev_id dev_id; /* device id */ + struct subchannel_id schid; /* 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 */ + unsigned int intretry:1; /* retry internal operation */ + } __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[8]; /* path group IDs per chpid*/ + 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 */ +}; + +static inline int ssch(struct subchannel_id schid, volatile struct orb *addr) +{ + register struct subchannel_id reg1 asm("1") = schid; + int ccode; + + asm volatile( + " ssch 0(%2)\n" + " ipm %0\n" + " srl %0,28" + : "=d" (ccode) : "d" (reg1), "a" (addr), "m" (*addr) : "cc"); + return ccode; +} + +static inline int rsch(struct subchannel_id schid) +{ + register struct subchannel_id reg1 asm("1") = schid; + int ccode; + + asm volatile( + " rsch\n" + " ipm %0\n" + " srl %0,28" + : "=d" (ccode) : "d" (reg1) : "cc"); + return ccode; +} + +static inline int csch(struct subchannel_id schid) +{ + register struct subchannel_id reg1 asm("1") = schid; + int ccode; + + asm volatile( + " csch\n" + " ipm %0\n" + " srl %0,28" + : "=d" (ccode) : "d" (reg1) : "cc"); + return ccode; +} + +static inline int hsch(struct subchannel_id schid) +{ + register struct subchannel_id reg1 asm("1") = schid; + int ccode; + + asm volatile( + " hsch\n" + " ipm %0\n" + " srl %0,28" + : "=d" (ccode) : "d" (reg1) : "cc"); + return ccode; +} + +static inline int xsch(struct subchannel_id schid) +{ + register struct subchannel_id reg1 asm("1") = schid; + int ccode; + + asm volatile( + " .insn rre,0xb2760000,%1,0\n" + " ipm %0\n" + " srl %0,28" + : "=d" (ccode) : "d" (reg1) : "cc"); + return ccode; +} + +#endif diff --git a/drivers/s390/cio/ioasm.h b/drivers/s390/cio/ioasm.h index 7153dd95908..652ea3625f9 100644 --- a/drivers/s390/cio/ioasm.h +++ b/drivers/s390/cio/ioasm.h @@ -109,72 +109,6 @@ static inline int tpi( volatile struct tpi_info *addr) return ccode; } -static inline int ssch(struct subchannel_id schid, - volatile struct orb *addr) -{ - register struct subchannel_id reg1 asm ("1") = schid; - int ccode; - - asm volatile( - " ssch 0(%2)\n" - " ipm %0\n" - " srl %0,28" - : "=d" (ccode) : "d" (reg1), "a" (addr), "m" (*addr) : "cc"); - return ccode; -} - -static inline int rsch(struct subchannel_id schid) -{ - register struct subchannel_id reg1 asm ("1") = schid; - int ccode; - - asm volatile( - " rsch\n" - " ipm %0\n" - " srl %0,28" - : "=d" (ccode) : "d" (reg1) : "cc"); - return ccode; -} - -static inline int csch(struct subchannel_id schid) -{ - register struct subchannel_id reg1 asm ("1") = schid; - int ccode; - - asm volatile( - " csch\n" - " ipm %0\n" - " srl %0,28" - : "=d" (ccode) : "d" (reg1) : "cc"); - return ccode; -} - -static inline int hsch(struct subchannel_id schid) -{ - register struct subchannel_id reg1 asm ("1") = schid; - int ccode; - - asm volatile( - " hsch\n" - " ipm %0\n" - " srl %0,28" - : "=d" (ccode) : "d" (reg1) : "cc"); - return ccode; -} - -static inline int xsch(struct subchannel_id schid) -{ - register struct subchannel_id reg1 asm ("1") = schid; - int ccode; - - asm volatile( - " .insn rre,0xb2760000,%1,0\n" - " ipm %0\n" - " srl %0,28" - : "=d" (ccode) : "d" (reg1) : "cc"); - return ccode; -} - static inline int chsc(void *chsc_area) { typedef struct { char _[4096]; } addr_type; diff --git a/drivers/s390/cio/qdio.c b/drivers/s390/cio/qdio.c index 40a3208c7cf..e2a781b6b21 100644 --- a/drivers/s390/cio/qdio.c +++ b/drivers/s390/cio/qdio.c @@ -48,11 +48,11 @@ #include <asm/debug.h> #include <asm/s390_rdev.h> #include <asm/qdio.h> +#include <asm/airq.h> #include "cio.h" #include "css.h" #include "device.h" -#include "airq.h" #include "qdio.h" #include "ioasm.h" #include "chsc.h" @@ -96,7 +96,7 @@ static debug_info_t *qdio_dbf_slsb_in; 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_ind; static void tiqdio_tl(unsigned long); static DECLARE_TASKLET(tiqdio_tasklet,tiqdio_tl,0); @@ -399,7 +399,7 @@ qdio_get_indicator(void) { int i; - for (i=1;i<INDICATORS_PER_CACHELINE;i++) + for (i = 0; i < INDICATORS_PER_CACHELINE; i++) if (!indicator_used[i]) { indicator_used[i]=1; return indicators+i; @@ -1408,8 +1408,7 @@ __tiqdio_inbound_processing(struct qdio_q *q, int spare_ind_was_set) 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)) { + } else if (!q->siga_sync_done_on_outb_tis) { SYNC_MEMORY_ALL_OUTB; } } else { @@ -1911,8 +1910,7 @@ qdio_fill_thresholds(struct qdio_irq *irq_ptr, } } -static int -tiqdio_thinint_handler(void) +static void tiqdio_thinint_handler(void *ind, void *drv_data) { QDIO_DBF_TEXT4(0,trace,"thin_int"); @@ -1925,7 +1923,6 @@ tiqdio_thinint_handler(void) tiqdio_clear_global_summary(); tiqdio_inbound_checks(); - return 0; } static void @@ -2445,7 +2442,7 @@ tiqdio_set_subchannel_ind(struct qdio_irq *irq_ptr, int reset_to_zero) real_addr_dev_st_chg_ind=0; } else { real_addr_local_summary_bit= - virt_to_phys((volatile void *)indicators); + virt_to_phys((volatile void *)tiqdio_ind); real_addr_dev_st_chg_ind= virt_to_phys((volatile void *)irq_ptr->dev_st_chg_ind); } @@ -3740,23 +3737,25 @@ 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)); + + tiqdio_ind = + s390_register_adapter_interrupt(&tiqdio_thinint_handler, NULL); + if (IS_ERR(tiqdio_ind)) { + sprintf(dbf_text, "regthn%lx", PTR_ERR(tiqdio_ind)); QDIO_DBF_TEXT0(0,setup,dbf_text); QDIO_PRINT_ERR("failed to register adapter handler " \ - "(rc=%i).\nAdapter interrupts might " \ + "(rc=%li).\nAdapter interrupts might " \ "not work. Continuing.\n", - register_thinint_result); + PTR_ERR(tiqdio_ind)); + tiqdio_ind = NULL; } } static void tiqdio_unregister_thinints(void) { - if (!register_thinint_result) - s390_unregister_adapter_interrupt(&tiqdio_thinint_handler); + if (tiqdio_ind) + s390_unregister_adapter_interrupt(tiqdio_ind); } static int @@ -3768,8 +3767,8 @@ qdio_get_qdio_memory(void) for (i=1;i<INDICATORS_PER_CACHELINE;i++) indicator_used[i]=0; indicators = kzalloc(sizeof(__u32)*(INDICATORS_PER_CACHELINE), - GFP_KERNEL); - if (!indicators) + GFP_KERNEL); + if (!indicators) return -ENOMEM; return 0; } @@ -3780,7 +3779,6 @@ qdio_release_qdio_memory(void) kfree(indicators); } - static void qdio_unregister_dbf_views(void) { diff --git a/drivers/s390/cio/qdio.h b/drivers/s390/cio/qdio.h index 6d7aad18f6f..37870e4e938 100644 --- a/drivers/s390/cio/qdio.h +++ b/drivers/s390/cio/qdio.h @@ -57,7 +57,7 @@ of the queue to 0 */ #define QDIO_ESTABLISH_TIMEOUT (1*HZ) -#define QDIO_ACTIVATE_TIMEOUT ((5*HZ)>>10) +#define QDIO_ACTIVATE_TIMEOUT (5*HZ) #define QDIO_CLEANUP_CLEAR_TIMEOUT (20*HZ) #define QDIO_CLEANUP_HALT_TIMEOUT (10*HZ) #define QDIO_FORCE_CHECK_TIMEOUT (10*HZ) |