diff options
Diffstat (limited to 'drivers/s390/cio/cio.c')
-rw-r--r-- | drivers/s390/cio/cio.c | 245 |
1 files changed, 141 insertions, 104 deletions
diff --git a/drivers/s390/cio/cio.c b/drivers/s390/cio/cio.c index 3db2c38..8a8df75 100644 --- a/drivers/s390/cio/cio.c +++ b/drivers/s390/cio/cio.c @@ -9,6 +9,9 @@ * Martin Schwidefsky (schwidefsky@de.ibm.com) */ +#define KMSG_COMPONENT "cio" +#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt + #include <linux/module.h> #include <linux/init.h> #include <linux/slab.h> @@ -104,44 +107,6 @@ cio_get_options (struct subchannel *sch) return flags; } -/* - * Use tpi to get a pending interrupt, call the interrupt handler and - * return a pointer to the subchannel structure. - */ -static int -cio_tpi(void) -{ - struct tpi_info *tpi_info; - struct subchannel *sch; - struct irb *irb; - int irq_context; - - tpi_info = (struct tpi_info *) __LC_SUBCHANNEL_ID; - if (tpi (NULL) != 1) - return 0; - irb = (struct irb *) __LC_IRB; - /* Store interrupt response block to lowcore. */ - if (tsch (tpi_info->schid, irb) != 0) - /* Not status pending or not operational. */ - return 1; - sch = (struct subchannel *)(unsigned long)tpi_info->intparm; - if (!sch) - return 1; - irq_context = in_interrupt(); - if (!irq_context) - local_bh_disable(); - irq_enter (); - spin_lock(sch->lock); - memcpy(&sch->schib.scsw, &irb->scsw, sizeof(union scsw)); - if (sch->driver && sch->driver->irq) - sch->driver->irq(sch); - spin_unlock(sch->lock); - irq_exit (); - if (!irq_context) - _local_bh_enable(); - return 1; -} - static int cio_start_handle_notoper(struct subchannel *sch, __u8 lpm) { @@ -152,11 +117,13 @@ cio_start_handle_notoper(struct subchannel *sch, __u8 lpm) else sch->lpm = 0; - stsch (sch->schid, &sch->schib); - CIO_MSG_EVENT(2, "cio_start: 'not oper' status for " "subchannel 0.%x.%04x!\n", sch->schid.ssid, sch->schid.sch_no); + + if (cio_update_schib(sch)) + return -ENODEV; + sprintf(dbf_text, "no%s", dev_name(&sch->dev)); CIO_TRACE_EVENT(0, dbf_text); CIO_HEX_EVENT(0, &sch->schib, sizeof (struct schib)); @@ -354,7 +321,8 @@ cio_cancel (struct subchannel *sch) switch (ccode) { case 0: /* success */ /* Update information in scsw. */ - stsch (sch->schid, &sch->schib); + if (cio_update_schib(sch)) + return -ENODEV; return 0; case 1: /* status pending */ return -EBUSY; @@ -365,30 +333,70 @@ cio_cancel (struct subchannel *sch) } } + +static void cio_apply_config(struct subchannel *sch, struct schib *schib) +{ + schib->pmcw.intparm = sch->config.intparm; + schib->pmcw.mbi = sch->config.mbi; + schib->pmcw.isc = sch->config.isc; + schib->pmcw.ena = sch->config.ena; + schib->pmcw.mme = sch->config.mme; + schib->pmcw.mp = sch->config.mp; + schib->pmcw.csense = sch->config.csense; + schib->pmcw.mbfc = sch->config.mbfc; + if (sch->config.mbfc) + schib->mba = sch->config.mba; +} + +static int cio_check_config(struct subchannel *sch, struct schib *schib) +{ + return (schib->pmcw.intparm == sch->config.intparm) && + (schib->pmcw.mbi == sch->config.mbi) && + (schib->pmcw.isc == sch->config.isc) && + (schib->pmcw.ena == sch->config.ena) && + (schib->pmcw.mme == sch->config.mme) && + (schib->pmcw.mp == sch->config.mp) && + (schib->pmcw.csense == sch->config.csense) && + (schib->pmcw.mbfc == sch->config.mbfc) && + (!sch->config.mbfc || (schib->mba == sch->config.mba)); +} + /* - * Function: cio_modify - * Issues a "Modify Subchannel" on the specified subchannel + * cio_commit_config - apply configuration to the subchannel */ -int -cio_modify (struct subchannel *sch) +int cio_commit_config(struct subchannel *sch) { - int ccode, retry, ret; + struct schib schib; + int ccode, retry, ret = 0; + + if (stsch(sch->schid, &schib) || !css_sch_is_valid(&schib)) + return -ENODEV; - ret = 0; for (retry = 0; retry < 5; retry++) { - ccode = msch_err (sch->schid, &sch->schib); - if (ccode < 0) /* -EIO if msch gets a program check. */ + /* copy desired changes to local schib */ + cio_apply_config(sch, &schib); + ccode = msch_err(sch->schid, &schib); + if (ccode < 0) /* -EIO if msch gets a program check. */ return ccode; switch (ccode) { case 0: /* successfull */ - return 0; - case 1: /* status pending */ + if (stsch(sch->schid, &schib) || + !css_sch_is_valid(&schib)) + return -ENODEV; + if (cio_check_config(sch, &schib)) { + /* commit changes from local schib */ + memcpy(&sch->schib, &schib, sizeof(schib)); + return 0; + } + ret = -EAGAIN; + break; + case 1: /* status pending */ return -EBUSY; - case 2: /* busy */ - udelay (100); /* allow for recovery */ + case 2: /* busy */ + udelay(100); /* allow for recovery */ ret = -EBUSY; break; - case 3: /* not operational */ + case 3: /* not operational */ return -ENODEV; } } @@ -396,6 +404,23 @@ cio_modify (struct subchannel *sch) } /** + * cio_update_schib - Perform stsch and update schib if subchannel is valid. + * @sch: subchannel on which to perform stsch + * Return zero on success, -ENODEV otherwise. + */ +int cio_update_schib(struct subchannel *sch) +{ + struct schib schib; + + if (stsch(sch->schid, &schib) || !css_sch_is_valid(&schib)) + return -ENODEV; + + memcpy(&sch->schib, &schib, sizeof(schib)); + return 0; +} +EXPORT_SYMBOL_GPL(cio_update_schib); + +/** * cio_enable_subchannel - enable a subchannel. * @sch: subchannel to be enabled * @intparm: interruption parameter to set @@ -403,7 +428,6 @@ cio_modify (struct subchannel *sch) int cio_enable_subchannel(struct subchannel *sch, u32 intparm) { char dbf_txt[15]; - int ccode; int retry; int ret; @@ -412,33 +436,27 @@ int cio_enable_subchannel(struct subchannel *sch, u32 intparm) if (sch_is_pseudo_sch(sch)) return -EINVAL; - ccode = stsch (sch->schid, &sch->schib); - if (ccode) + if (cio_update_schib(sch)) return -ENODEV; - for (retry = 5, ret = 0; retry > 0; retry--) { - sch->schib.pmcw.ena = 1; - sch->schib.pmcw.isc = sch->isc; - sch->schib.pmcw.intparm = intparm; - ret = cio_modify(sch); - if (ret == -ENODEV) - break; - if (ret == -EIO) + sch->config.ena = 1; + sch->config.isc = sch->isc; + sch->config.intparm = intparm; + + for (retry = 0; retry < 3; retry++) { + ret = cio_commit_config(sch); + if (ret == -EIO) { /* - * Got a program check in cio_modify. Try without + * Got a program check in msch. Try without * the concurrent sense bit the next time. */ - sch->schib.pmcw.csense = 0; - if (ret == 0) { - stsch (sch->schid, &sch->schib); - if (sch->schib.pmcw.ena) - break; - } - if (ret == -EBUSY) { + sch->config.csense = 0; + } else if (ret == -EBUSY) { struct irb irb; if (tsch(sch->schid, &irb) != 0) break; - } + } else + break; } sprintf (dbf_txt, "ret:%d", ret); CIO_TRACE_EVENT (2, dbf_txt); @@ -453,8 +471,6 @@ EXPORT_SYMBOL_GPL(cio_enable_subchannel); int cio_disable_subchannel(struct subchannel *sch) { char dbf_txt[15]; - int ccode; - int retry; int ret; CIO_TRACE_EVENT (2, "dissch"); @@ -462,8 +478,7 @@ int cio_disable_subchannel(struct subchannel *sch) if (sch_is_pseudo_sch(sch)) return 0; - ccode = stsch (sch->schid, &sch->schib); - if (ccode == 3) /* Not operational. */ + if (cio_update_schib(sch)) return -ENODEV; if (scsw_actl(&sch->schib.scsw) != 0) @@ -473,24 +488,9 @@ int cio_disable_subchannel(struct subchannel *sch) */ return -EBUSY; - for (retry = 5, ret = 0; retry > 0; retry--) { - sch->schib.pmcw.ena = 0; - ret = cio_modify(sch); - if (ret == -ENODEV) - break; - if (ret == -EBUSY) - /* - * The subchannel is busy or status pending. - * We'll disable when the next interrupt was delivered - * via the state machine. - */ - break; - if (ret == 0) { - stsch (sch->schid, &sch->schib); - if (!sch->schib.pmcw.ena) - break; - } - } + sch->config.ena = 0; + ret = cio_commit_config(sch); + sprintf (dbf_txt, "ret:%d", ret); CIO_TRACE_EVENT (2, dbf_txt); return ret; @@ -687,6 +687,43 @@ static char console_sch_name[10] = "0.x.xxxx"; static struct io_subchannel_private console_priv; static int console_subchannel_in_use; +/* + * Use tpi to get a pending interrupt, call the interrupt handler and + * return a pointer to the subchannel structure. + */ +static int cio_tpi(void) +{ + struct tpi_info *tpi_info; + struct subchannel *sch; + struct irb *irb; + int irq_context; + + tpi_info = (struct tpi_info *) __LC_SUBCHANNEL_ID; + if (tpi(NULL) != 1) + return 0; + irb = (struct irb *) __LC_IRB; + /* Store interrupt response block to lowcore. */ + if (tsch(tpi_info->schid, irb) != 0) + /* Not status pending or not operational. */ + return 1; + sch = (struct subchannel *)(unsigned long)tpi_info->intparm; + if (!sch) + return 1; + irq_context = in_interrupt(); + if (!irq_context) + local_bh_disable(); + irq_enter(); + spin_lock(sch->lock); + memcpy(&sch->schib.scsw, &irb->scsw, sizeof(union scsw)); + if (sch->driver && sch->driver->irq) + sch->driver->irq(sch); + spin_unlock(sch->lock); + irq_exit(); + if (!irq_context) + _local_bh_enable(); + return 1; +} + void *cio_get_console_priv(void) { return &console_priv; @@ -780,7 +817,7 @@ cio_probe_console(void) sch_no = cio_get_console_sch_no(); if (sch_no == -1) { console_subchannel_in_use = 0; - printk(KERN_WARNING "cio: No ccw console found!\n"); + pr_warning("No CCW console was found\n"); return ERR_PTR(-ENODEV); } memset(&console_subchannel, 0, sizeof(struct subchannel)); @@ -796,10 +833,9 @@ cio_probe_console(void) * enable console I/O-interrupt subclass */ isc_register(CONSOLE_ISC); - console_subchannel.schib.pmcw.isc = CONSOLE_ISC; - console_subchannel.schib.pmcw.intparm = - (u32)(addr_t)&console_subchannel; - ret = cio_modify(&console_subchannel); + console_subchannel.config.isc = CONSOLE_ISC; + console_subchannel.config.intparm = (u32)(addr_t)&console_subchannel; + ret = cio_commit_config(&console_subchannel); if (ret) { isc_unregister(CONSOLE_ISC); console_subchannel_in_use = 0; @@ -811,8 +847,8 @@ cio_probe_console(void) void cio_release_console(void) { - console_subchannel.schib.pmcw.intparm = 0; - cio_modify(&console_subchannel); + console_subchannel.config.intparm = 0; + cio_commit_config(&console_subchannel); isc_unregister(CONSOLE_ISC); console_subchannel_in_use = 0; } @@ -852,7 +888,8 @@ __disable_subchannel_easy(struct subchannel_id schid, struct schib *schib) cc = msch(schid, schib); if (cc) return (cc==3?-ENODEV:-EBUSY); - stsch(schid, schib); + if (stsch(schid, schib) || !css_sch_is_valid(schib)) + return -ENODEV; if (!schib->pmcw.ena) return 0; } |