From: Stefan Weinhuber <wein@de.ibm.com>

Add code to the dasd driver to select the I/O path based on the path
preferences of the device.

Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
---

 25-akpm/drivers/s390/block/dasd.c      |   21 +++++--
 25-akpm/drivers/s390/block/dasd_eckd.c |   97 +++++++++++++++++++++++----------
 25-akpm/drivers/s390/block/dasd_eckd.h |    8 ++
 25-akpm/drivers/s390/block/dasd_erp.c  |    3 -
 25-akpm/drivers/s390/cio/device_ops.c  |   22 +++++--
 25-akpm/include/asm-s390/ccwdev.h      |    2 
 6 files changed, 113 insertions(+), 40 deletions(-)

diff -puN drivers/s390/block/dasd.c~s390-dasd-preferred-path-support drivers/s390/block/dasd.c
--- 25/drivers/s390/block/dasd.c~s390-dasd-preferred-path-support	Thu Mar 24 15:28:58 2005
+++ 25-akpm/drivers/s390/block/dasd.c	Thu Mar 24 15:28:58 2005
@@ -7,7 +7,7 @@
  * Bugreports.to..: <Linux390@de.ibm.com>
  * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999-2001
  *
- * $Revision: 1.156 $
+ * $Revision: 1.158 $
  */
 
 #include <linux/config.h>
@@ -757,6 +757,17 @@ dasd_start_IO(struct dasd_ccw_req * cqr)
 		DBF_DEV_EVENT(DBF_ERR, device, "%s",
 			      "start_IO: request timeout, retry later");
 		break;
+	case -EACCES:
+		/* -EACCES indicates that the request used only a
+		 * subset of the available pathes and all these
+		 * pathes are gone.
+		 * Do a retry with all available pathes.
+		 */
+		cqr->lpm = LPM_ANYPATH;
+		DBF_DEV_EVENT(DBF_ERR, device, "%s",
+			      "start_IO: selected pathes gone,"
+			      " retry on all pathes");
+		break;
 	case -ENODEV:
 	case -EIO:
 		DBF_DEV_EVENT(DBF_ERR, device, "%s",
@@ -1222,7 +1233,9 @@ __dasd_start_head(struct dasd_device * d
 		rc = device->discipline->start_IO(cqr);
 		if (rc == 0)
 			dasd_set_timer(device, cqr->expires);
-		else
+		else if (rc == -EACCES) {
+			dasd_schedule_bh(device);
+		} else
 			/* Hmpf, try again in 1/2 sec */
 			dasd_set_timer(device, 50);
 	}
@@ -1813,8 +1826,8 @@ dasd_generic_set_online (struct ccw_devi
 	if (rc) {
 		printk (KERN_WARNING
 			"dasd_generic couldn't online device %s "
-			"with discipline %s\n", 
-			cdev->dev.bus_id, discipline->name);
+			"with discipline %s rc=%i\n",
+			cdev->dev.bus_id, discipline->name, rc);
 		dasd_delete_device(device);
 		return rc;
 	}
diff -puN drivers/s390/block/dasd_eckd.c~s390-dasd-preferred-path-support drivers/s390/block/dasd_eckd.c
--- 25/drivers/s390/block/dasd_eckd.c~s390-dasd-preferred-path-support	Thu Mar 24 15:28:58 2005
+++ 25-akpm/drivers/s390/block/dasd_eckd.c	Thu Mar 24 15:28:58 2005
@@ -7,7 +7,7 @@
  * Bugreports.to..: <Linux390@de.ibm.com>
  * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999,2000
  *
- * $Revision: 1.68 $
+ * $Revision: 1.69 $
  */
 
 #include <linux/config.h>
@@ -56,6 +56,7 @@ static struct dasd_discipline dasd_eckd_
 struct dasd_eckd_private {
 	struct dasd_eckd_characteristics rdc_data;
 	struct dasd_eckd_confdata conf_data;
+	struct dasd_eckd_path path_data;
 	struct eckd_count count_area[5];
 	int init_cqr_status;
 	int uses_cdl;
@@ -447,12 +448,72 @@ dasd_eckd_cdl_reclen(int recid)
 }
 
 static int
+dasd_eckd_read_conf(struct dasd_device *device)
+{
+	void *conf_data;
+	int conf_len, conf_data_saved;
+	int rc;
+	__u8 lpm;
+	struct dasd_eckd_private *private;
+	struct dasd_eckd_path *path_data;
+
+	private = (struct dasd_eckd_private *) device->private;
+	path_data = (struct dasd_eckd_path *) &private->path_data;
+	path_data->opm = ccw_device_get_path_mask(device->cdev);
+	lpm = 0x80;
+	conf_data_saved = 0;
+
+	/* get configuration data per operational path */
+	for (lpm = 0x80; lpm; lpm>>= 1) {
+		if (lpm & path_data->opm){
+			rc = read_conf_data_lpm(device->cdev, &conf_data,
+						&conf_len, lpm);
+			if (rc && rc != -EOPNOTSUPP) {	/* -EOPNOTSUPP is ok */
+				MESSAGE(KERN_WARNING,
+					"Read configuration data returned "
+					"error %d", rc);
+				return rc;
+			}
+			if (conf_data == NULL) {
+				MESSAGE(KERN_WARNING, "%s", "No configuration "
+					"data retrieved");
+				continue;	/* no errror */
+			}
+			if (conf_len != sizeof (struct dasd_eckd_confdata)) {
+				MESSAGE(KERN_WARNING,
+					"sizes of configuration data mismatch"
+					"%d (read) vs %ld (expected)",
+					conf_len,
+					sizeof (struct dasd_eckd_confdata));
+				kfree(conf_data);
+				continue;	/* no errror */
+			}
+			/* save first valid configuration data */
+			if (!conf_data_saved){
+				memcpy(&private->conf_data, conf_data,
+				       sizeof (struct dasd_eckd_confdata));
+				conf_data_saved++;
+			}
+			switch (((char *)conf_data)[242] & 0x07){
+			case 0x02:
+				path_data->npm |= lpm;
+				break;
+			case 0x03:
+				path_data->ppm |= lpm;
+				break;
+			}
+			kfree(conf_data);
+		}
+	}
+	return 0;
+}
+
+
+static int
 dasd_eckd_check_characteristics(struct dasd_device *device)
 {
 	struct dasd_eckd_private *private;
 	void *rdc_data;
-	void *conf_data;
-	int conf_len;
 	int rc;
 
 	private = (struct dasd_eckd_private *) device->private;
@@ -465,6 +526,7 @@ dasd_eckd_check_characteristics(struct d
 				    "data");
 			return -ENOMEM;
 		}
+		memset(private, 0, sizeof(struct dasd_eckd_private));
 		device->private = (void *) private;
 	}
 	/* Invalidate status of initial analysis. */
@@ -494,30 +556,9 @@ dasd_eckd_check_characteristics(struct d
 		    private->rdc_data.sec_per_trk);
 
 	/* Read Configuration Data */
-	rc = read_conf_data(device->cdev, &conf_data, &conf_len);
-	if (rc && rc != -EOPNOTSUPP) {	/* -EOPNOTSUPP is ok */
-		DEV_MESSAGE(KERN_WARNING, device,
-			    "Read configuration data returned error %d", rc);
-		return rc;
-	}
-	if (conf_data == NULL) {
-		DEV_MESSAGE(KERN_WARNING, device, "%s",
-			    "No configuration data retrieved");
-		return 0;	/* no errror */
-	}
-	if (conf_len != sizeof (struct dasd_eckd_confdata)) {
-		DEV_MESSAGE(KERN_WARNING, device,
-			    "sizes of configuration data mismatch"
-			    "%d (read) vs %ld (expected)",
-			    conf_len, sizeof (struct dasd_eckd_confdata));
-
-		kfree(conf_data); /* allocated by read_conf_data() */
-		return 0;	/* no errror */
-	}
-	memcpy(&private->conf_data, conf_data,
-	       sizeof (struct dasd_eckd_confdata));
-	kfree(conf_data); /* allocated by read_conf_data() */
-	return 0;
+	rc = dasd_eckd_read_conf (device);
+	return rc;
+
 }
 
 static struct dasd_ccw_req *
@@ -1096,7 +1137,7 @@ dasd_eckd_build_cp(struct dasd_device * 
 	}
 	cqr->device = device;
 	cqr->expires = 5 * 60 * HZ;	/* 5 minutes */
-	cqr->lpm = LPM_ANYPATH;
+	cqr->lpm = private->path_data.ppm;
 	cqr->retries = 256;
 	cqr->buildclk = get_clock();
 	cqr->status = DASD_CQR_FILLED;
diff -puN drivers/s390/block/dasd_eckd.h~s390-dasd-preferred-path-support drivers/s390/block/dasd_eckd.h
--- 25/drivers/s390/block/dasd_eckd.h~s390-dasd-preferred-path-support	Thu Mar 24 15:28:58 2005
+++ 25-akpm/drivers/s390/block/dasd_eckd.h	Thu Mar 24 15:28:58 2005
@@ -5,7 +5,7 @@
  * Bugreports.to..: <Linux390@de.ibm.com>
  * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999,2000
  *
- * $Revision: 1.9 $
+ * $Revision: 1.10 $
  */
 
 #ifndef DASD_ECKD_H
@@ -326,6 +326,12 @@ struct dasd_eckd_confdata {
 	} __attribute__ ((packed)) neq;
 } __attribute__ ((packed));
 
+struct dasd_eckd_path {
+	__u8 opm;
+	__u8 ppm;
+	__u8 npm;
+};
+
 /*
  * Perform Subsystem Function - Prepare for Read Subsystem Data	 
  */
diff -puN drivers/s390/block/dasd_erp.c~s390-dasd-preferred-path-support drivers/s390/block/dasd_erp.c
--- 25/drivers/s390/block/dasd_erp.c~s390-dasd-preferred-path-support	Thu Mar 24 15:28:58 2005
+++ 25-akpm/drivers/s390/block/dasd_erp.c	Thu Mar 24 15:28:58 2005
@@ -7,7 +7,7 @@
  * Bugreports.to..: <Linux390@de.ibm.com>
  * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999-2001
  *
- * $Revision: 1.13 $
+ * $Revision: 1.14 $
  */
 
 #include <linux/config.h>
@@ -95,6 +95,7 @@ dasd_default_erp_action(struct dasd_ccw_
                 DEV_MESSAGE (KERN_DEBUG, device, 
                              "default ERP called (%i retries left)",
                              cqr->retries);
+		cqr->lpm    = LPM_ANYPATH;
 		cqr->status = DASD_CQR_QUEUED;
         } else {
                 DEV_MESSAGE (KERN_WARNING, device, "%s",
diff -puN drivers/s390/cio/device_ops.c~s390-dasd-preferred-path-support drivers/s390/cio/device_ops.c
--- 25/drivers/s390/cio/device_ops.c~s390-dasd-preferred-path-support	Thu Mar 24 15:28:58 2005
+++ 25-akpm/drivers/s390/cio/device_ops.c	Thu Mar 24 15:28:58 2005
@@ -291,14 +291,14 @@ ccw_device_wake_up(struct ccw_device *cd
 }
 
 static inline int
-__ccw_device_retry_loop(struct ccw_device *cdev, struct ccw1 *ccw, long magic)
+__ccw_device_retry_loop(struct ccw_device *cdev, struct ccw1 *ccw, long magic, __u8 lpm)
 {
 	int ret;
 	struct subchannel *sch;
 
 	sch = to_subchannel(cdev->dev.parent);
 	do {
-		ret = cio_start (sch, ccw, 0);
+		ret = cio_start (sch, ccw, lpm);
 		if ((ret == -EBUSY) || (ret == -EACCES)) {
 			/* Try again later. */
 			spin_unlock_irq(&sch->lock);
@@ -388,7 +388,7 @@ read_dev_chars (struct ccw_device *cdev,
 		ret = -EBUSY;
 	else
 		/* 0x00D9C4C3 == ebcdic "RDC" */
-		ret = __ccw_device_retry_loop(cdev, rdc_ccw, 0x00D9C4C3);
+		ret = __ccw_device_retry_loop(cdev, rdc_ccw, 0x00D9C4C3, 0);
 
 	/* Restore interrupt handler. */
 	cdev->handler = handler;
@@ -401,10 +401,10 @@ read_dev_chars (struct ccw_device *cdev,
 }
 
 /*
- *  Read Configuration data
+ *  Read Configuration data using path mask
  */
 int
-read_conf_data (struct ccw_device *cdev, void **buffer, int *length)
+read_conf_data_lpm (struct ccw_device *cdev, void **buffer, int *length, __u8 lpm)
 {
 	void (*handler)(struct ccw_device *, unsigned long, struct irb *);
 	struct subchannel *sch;
@@ -457,7 +457,7 @@ read_conf_data (struct ccw_device *cdev,
 		ret = -EBUSY;
 	else
 		/* 0x00D9C3C4 == ebcdic "RCD" */
-		ret = __ccw_device_retry_loop(cdev, rcd_ccw, 0x00D9C3C4);
+		ret = __ccw_device_retry_loop(cdev, rcd_ccw, 0x00D9C3C4, lpm);
 
 	/* Restore interrupt handler. */
 	cdev->handler = handler;
@@ -480,6 +480,15 @@ read_conf_data (struct ccw_device *cdev,
 }
 
 /*
+ *  Read Configuration data
+ */
+int
+read_conf_data (struct ccw_device *cdev, void **buffer, int *length)
+{
+	return read_conf_data_lpm (cdev, buffer, length, 0);
+}
+
+/*
  * Try to break the lock on a boxed device.
  */
 int
@@ -580,3 +589,4 @@ EXPORT_SYMBOL(read_conf_data);
 EXPORT_SYMBOL(read_dev_chars);
 EXPORT_SYMBOL(_ccw_device_get_subchannel_number);
 EXPORT_SYMBOL(_ccw_device_get_device_number);
+EXPORT_SYMBOL_GPL(read_conf_data_lpm);
diff -puN include/asm-s390/ccwdev.h~s390-dasd-preferred-path-support include/asm-s390/ccwdev.h
--- 25/include/asm-s390/ccwdev.h~s390-dasd-preferred-path-support	Thu Mar 24 15:28:58 2005
+++ 25-akpm/include/asm-s390/ccwdev.h	Thu Mar 24 15:28:58 2005
@@ -164,6 +164,8 @@ extern int ccw_device_clear(struct ccw_d
 
 extern int read_dev_chars(struct ccw_device *cdev, void **buffer, int length);
 extern int read_conf_data(struct ccw_device *cdev, void **buffer, int *length);
+extern int read_conf_data_lpm(struct ccw_device *cdev, void **buffer,
+			      int *length, __u8 lpm);
 
 extern int ccw_device_set_online(struct ccw_device *cdev);
 extern int ccw_device_set_offline(struct ccw_device *cdev);
_