[S390] cio: introduce cio_commit_config
authorSebastian Ott <sebott@linux.vnet.ibm.com>
Thu, 25 Dec 2008 12:39:13 +0000 (13:39 +0100)
committerMartin Schwidefsky <schwidefsky@de.ibm.com>
Thu, 25 Dec 2008 12:39:10 +0000 (13:39 +0100)
To change the configuration of a subchannel we alter the modifiable
bits of the subchannel's schib field and issue a modify subchannel.
There can be the case that not all changes were applied -or worse-
quietly overwritten by the hardware. With the next store subchannel
we obtain the current state of the hardware but lose our target
configuration.

With this patch we introduce a subchannel_config structure which
contains the target subchannel configuration. Additionally the msch
wrapper cio_modify is replaced with cio_commit_config which
copies the desired changes to a temporary schib. msch is then
called with the temporary schib. This schib is only written back
to the subchannel if all changes were applied.

Signed-off-by: Sebastian Ott <sebott@linux.vnet.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
drivers/s390/cio/cio.c
drivers/s390/cio/cio.h
drivers/s390/cio/cmf.c
drivers/s390/cio/css.c
drivers/s390/cio/device.c
drivers/s390/cio/device.h
drivers/s390/cio/device_fsm.c

index 9bdb463765c74b460cfb7a7f2db96842b1f75ae3..8f1cec49953232d4a9ae222ec394ec8d197b16d5 100644 (file)
@@ -330,30 +330,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,32 +436,24 @@ int cio_enable_subchannel(struct subchannel *sch, u32 intparm)
        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) {
-                       if (cio_update_schib(sch)) {
-                               ret = -ENODEV;
-                               break;
-                       }
-                       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);
@@ -436,7 +468,6 @@ EXPORT_SYMBOL_GPL(cio_enable_subchannel);
 int cio_disable_subchannel(struct subchannel *sch)
 {
        char dbf_txt[15];
-       int retry;
        int ret;
 
        CIO_TRACE_EVENT (2, "dissch");
@@ -454,27 +485,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) {
-                       if (cio_update_schib(sch)) {
-                               ret = -ENODEV;
-                               break;
-                       }
-                       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;
@@ -817,10 +830,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;
@@ -832,8 +844,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;
 }
index fb125efd6891968da00b5c763e9305c18e21d8f4..5150fba742ac24f3569d1199b0d1567cd1588522 100644 (file)
@@ -45,6 +45,19 @@ struct pmcw {
                                /*  ... in an operand exception.       */
 } __attribute__ ((packed));
 
+/* Target SCHIB configuration. */
+struct schib_config {
+       u64 mba;
+       u32 intparm;
+       u16 mbi;
+       u32 isc:3;
+       u32 ena:1;
+       u32 mme:2;
+       u32 mp:1;
+       u32 csense:1;
+       u32 mbfc:1;
+} __attribute__ ((packed));
+
 /*
  * subchannel information block
  */
@@ -83,6 +96,7 @@ struct subchannel {
        struct css_driver *driver;
        void *private; /* private per subchannel type data */
        struct work_struct work;
+       struct schib_config config;
 } __attribute__ ((aligned(8)));
 
 #define IO_INTERRUPT_TYPE         0 /* I/O interrupt type */
@@ -101,8 +115,8 @@ extern int cio_start_key (struct subchannel *, struct ccw1 *, __u8, __u8);
 extern int cio_cancel (struct subchannel *);
 extern int cio_set_options (struct subchannel *, int);
 extern int cio_get_options (struct subchannel *);
-extern int cio_modify (struct subchannel *);
 extern int cio_update_schib(struct subchannel *sch);
+extern int cio_commit_config(struct subchannel *sch);
 
 int cio_tm_start_key(struct subchannel *sch, struct tcw *tcw, u8 lpm, u8 key);
 int cio_tm_intrg(struct subchannel *sch);
index 288482b21048bbda4ee413dbc0b2912f3f0078cb..6ddd02308e143971f7ba4527cf0216707ddcbfdf 100644 (file)
@@ -185,58 +185,19 @@ static inline void cmf_activate(void *area, unsigned int onoff)
 static int set_schib(struct ccw_device *cdev, u32 mme, int mbfc,
                     unsigned long address)
 {
-       int ret;
-       int retry;
        struct subchannel *sch;
-       struct schib *schib;
 
        sch = to_subchannel(cdev->dev.parent);
-       schib = &sch->schib;
-       /* msch can silently fail, so do it again if necessary */
-       for (retry = 0; retry < 3; retry++) {
-               /* prepare schib */
-               if (cio_update_schib(sch))
-                       return -ENODEV;
-               schib->pmcw.mme  = mme;
-               schib->pmcw.mbfc = mbfc;
-               /* address can be either a block address or a block index */
-               if (mbfc)
-                       schib->mba = address;
-               else
-                       schib->pmcw.mbi = address;
-
-               /* try to submit it */
-               switch(ret = msch_err(sch->schid, schib)) {
-                       case 0:
-                               break;
-                       case 1:
-                       case 2: /* in I/O or status pending */
-                               ret = -EBUSY;
-                               break;
-                       case 3: /* subchannel is no longer valid */
-                               ret = -ENODEV;
-                               break;
-                       default: /* msch caught an exception */
-                               ret = -EINVAL;
-                               break;
-               }
-               if (cio_update_schib(sch))
-                       return -ENODEV;
-
-               if (ret)
-                       break;
 
-               /* check if it worked */
-               if (schib->pmcw.mme  == mme &&
-                   schib->pmcw.mbfc == mbfc &&
-                   (mbfc ? (schib->mba == address)
-                         : (schib->pmcw.mbi == address)))
-                       return 0;
+       sch->config.mme = mme;
+       sch->config.mbfc = mbfc;
+       /* address can be either a block address or a block index */
+       if (mbfc)
+               sch->config.mba = address;
+       else
+               sch->config.mbi = address;
 
-               ret = -EINVAL;
-       }
-
-       return ret;
+       return cio_commit_config(sch);
 }
 
 struct set_schib_struct {
index 76bbb1e74c2988c025d695c3927fc690ca067ba6..1b2d5149de5a4f4f2faff5fd4ea141865ce794da 100644 (file)
@@ -128,8 +128,8 @@ css_free_subchannel(struct subchannel *sch)
 {
        if (sch) {
                /* Reset intparm to zeroes. */
-               sch->schib.pmcw.intparm = 0;
-               cio_modify(sch);
+               sch->config.intparm = 0;
+               cio_commit_config(sch);
                kfree(sch->lock);
                kfree(sch);
        }
index 51e94212bf66e2e9ab1e6e0148f110aa76772bd3..d3127018fb81c8f49030490de21777d1f8a03205 100644 (file)
@@ -1020,8 +1020,8 @@ static void ccw_device_call_sch_unregister(struct work_struct *work)
        sch = to_subchannel(cdev->dev.parent);
        css_sch_device_unregister(sch);
        /* Reset intparm to zeroes. */
-       sch->schib.pmcw.intparm = 0;
-       cio_modify(sch);
+       sch->config.intparm = 0;
+       cio_commit_config(sch);
        /* Release cdev reference for workqueue processing.*/
        put_device(&cdev->dev);
        /* Release subchannel reference for local processing. */
@@ -1148,8 +1148,8 @@ static void ccw_device_move_to_sch(struct work_struct *work)
                spin_unlock_irq(former_parent->lock);
                css_sch_device_unregister(former_parent);
                /* Reset intparm to zeroes. */
-               former_parent->schib.pmcw.intparm = 0;
-               cio_modify(former_parent);
+               former_parent->config.intparm = 0;
+               cio_commit_config(former_parent);
        }
        sch_attach_device(sch, cdev);
 out:
@@ -1170,6 +1170,14 @@ static void io_subchannel_irq(struct subchannel *sch)
                dev_fsm_event(cdev, DEV_EVENT_INTERRUPT);
 }
 
+void io_subchannel_init_config(struct subchannel *sch)
+{
+       memset(&sch->config, 0, sizeof(sch->config));
+       sch->config.csense = 1;
+       if ((sch->lpm & (sch->lpm - 1)) != 0)
+               sch->config.mp = 1;
+}
+
 static void io_subchannel_init_fields(struct subchannel *sch)
 {
        if (cio_is_console(sch->schid))
@@ -1184,16 +1192,8 @@ static void io_subchannel_init_fields(struct subchannel *sch)
                      sch->schib.pmcw.dev, sch->schid.ssid,
                      sch->schid.sch_no, sch->schib.pmcw.pim,
                      sch->schib.pmcw.pam, sch->schib.pmcw.pom);
-       /* Initially set up some fields in the pmcw. */
-       sch->schib.pmcw.ena = 0;
-       sch->schib.pmcw.csense = 1;     /* concurrent sense */
-       if ((sch->lpm & (sch->lpm - 1)) != 0)
-               sch->schib.pmcw.mp = 1; /* multipath mode */
-       /* clean up possible residual cmf stuff */
-       sch->schib.pmcw.mme = 0;
-       sch->schib.pmcw.mbfc = 0;
-       sch->schib.pmcw.mbi = 0;
-       sch->schib.mba = 0;
+
+       io_subchannel_init_config(sch);
 }
 
 static void io_subchannel_do_unreg(struct work_struct *work)
@@ -1203,8 +1203,8 @@ static void io_subchannel_do_unreg(struct work_struct *work)
        sch = container_of(work, struct subchannel, work);
        css_sch_device_unregister(sch);
        /* Reset intparm to zeroes. */
-       sch->schib.pmcw.intparm = 0;
-       cio_modify(sch);
+       sch->config.intparm = 0;
+       cio_commit_config(sch);
        put_device(&sch->dev);
 }
 
@@ -1680,8 +1680,8 @@ static int io_subchannel_sch_event(struct subchannel *sch, int slow)
                spin_lock_irqsave(sch->lock, flags);
 
                /* Reset intparm to zeroes. */
-               sch->schib.pmcw.intparm = 0;
-               cio_modify(sch);
+               sch->config.intparm = 0;
+               cio_commit_config(sch);
                break;
        case REPROBE:
                ccw_device_trigger_reprobe(cdev);
index 104ed669db43946a0656fe5adaac51a8374cc4f1..0f2e63ea48deee03511be167208aa5d2742b4d09 100644 (file)
@@ -76,6 +76,7 @@ extern wait_queue_head_t ccw_device_init_wq;
 extern atomic_t ccw_device_init_count;
 
 void io_subchannel_recog_done(struct ccw_device *cdev);
+void io_subchannel_init_config(struct subchannel *sch);
 
 int ccw_device_cancel_halt_clear(struct ccw_device *);
 
index e1a3786779ba5791e624f1f70760b9853f71f284..9e249675c98df63628fbd18d2e49dcab6ea91db2 100644 (file)
@@ -1026,11 +1026,12 @@ void ccw_device_trigger_reprobe(struct ccw_device *cdev)
         * we have before performing device selection :/
         */
        sch->lpm = sch->schib.pmcw.pam & sch->opm;
-       /* Re-set some bits in the pmcw that were lost. */
-       sch->schib.pmcw.csense = 1;
-       sch->schib.pmcw.ena = 0;
-       if ((sch->lpm & (sch->lpm - 1)) != 0)
-               sch->schib.pmcw.mp = 1;
+       /*
+        * Use the initial configuration since we can't be shure that the old
+        * paths are valid.
+        */
+       io_subchannel_init_config(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) {