[S390] dasd: do path verification for paths added at runtime

When a new path is added at runtime, the CIO layer will call the drivers
path_event callback. The DASD device driver uses this callback to trigger
a path verification for the new path. The driver will use only those
paths for I/O, which have been successfully verified.

Signed-off-by: Stefan Weinhuber <wein@de.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
diff --git a/drivers/s390/block/dasd_eckd.c b/drivers/s390/block/dasd_eckd.c
index 549443a..a1ebf57 100644
--- a/drivers/s390/block/dasd_eckd.c
+++ b/drivers/s390/block/dasd_eckd.c
@@ -90,6 +90,18 @@
 } *dasd_reserve_req;
 static DEFINE_MUTEX(dasd_reserve_mutex);
 
+/* definitions for the path verification worker */
+struct path_verification_work_data {
+	struct work_struct worker;
+	struct dasd_device *device;
+	struct dasd_ccw_req cqr;
+	struct ccw1 ccw;
+	__u8 rcd_buffer[DASD_ECKD_RCD_DATA_SIZE];
+	int isglobal;
+	__u8 tbvpm;
+};
+static struct path_verification_work_data *path_verification_worker;
+static DEFINE_MUTEX(dasd_path_verification_mutex);
 
 /* initial attempt at a probe function. this can be simplified once
  * the other detection code is gone */
@@ -755,26 +767,27 @@
 	return -EINVAL;
 }
 
-static struct dasd_ccw_req *dasd_eckd_build_rcd_lpm(struct dasd_device *device,
-						    void *rcd_buffer,
-						    struct ciw *ciw, __u8 lpm)
+static void dasd_eckd_fill_rcd_cqr(struct dasd_device *device,
+				   struct dasd_ccw_req *cqr,
+				   __u8 *rcd_buffer,
+				   __u8 lpm)
 {
-	struct dasd_ccw_req *cqr;
 	struct ccw1 *ccw;
-
-	cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 1 /* RCD */, ciw->count,
-				   device);
-
-	if (IS_ERR(cqr)) {
-		DBF_DEV_EVENT(DBF_WARNING, device, "%s",
-			      "Could not allocate RCD request");
-		return cqr;
-	}
+	/*
+	 * buffer has to start with EBCDIC "V1.0" to show
+	 * support for virtual device SNEQ
+	 */
+	rcd_buffer[0] = 0xE5;
+	rcd_buffer[1] = 0xF1;
+	rcd_buffer[2] = 0x4B;
+	rcd_buffer[3] = 0xF0;
 
 	ccw = cqr->cpaddr;
-	ccw->cmd_code = ciw->cmd;
+	ccw->cmd_code = DASD_ECKD_CCW_RCD;
+	ccw->flags = 0;
 	ccw->cda = (__u32)(addr_t)rcd_buffer;
-	ccw->count = ciw->count;
+	ccw->count = DASD_ECKD_RCD_DATA_SIZE;
+	cqr->magic = DASD_ECKD_MAGIC;
 
 	cqr->startdev = device;
 	cqr->memdev = device;
@@ -784,7 +797,29 @@
 	cqr->retries = 256;
 	cqr->buildclk = get_clock();
 	cqr->status = DASD_CQR_FILLED;
-	return cqr;
+	set_bit(DASD_CQR_VERIFY_PATH, &cqr->flags);
+}
+
+static int dasd_eckd_read_conf_immediately(struct dasd_device *device,
+					   struct dasd_ccw_req *cqr,
+					   __u8 *rcd_buffer,
+					   __u8 lpm)
+{
+	struct ciw *ciw;
+	int rc;
+	/*
+	 * sanity check: scan for RCD command in extended SenseID data
+	 * some devices do not support RCD
+	 */
+	ciw = ccw_device_get_ciw(device->cdev, CIW_TYPE_RCD);
+	if (!ciw || ciw->cmd != DASD_ECKD_CCW_RCD)
+		return -EOPNOTSUPP;
+
+	dasd_eckd_fill_rcd_cqr(device, cqr, rcd_buffer, lpm);
+	clear_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags);
+	cqr->retries = 5;
+	rc = dasd_sleep_on_immediatly(cqr);
+	return rc;
 }
 
 static int dasd_eckd_read_conf_lpm(struct dasd_device *device,
@@ -797,32 +832,29 @@
 	struct dasd_ccw_req *cqr;
 
 	/*
-	 * scan for RCD command in extended SenseID data
+	 * sanity check: scan for RCD command in extended SenseID data
+	 * some devices do not support RCD
 	 */
 	ciw = ccw_device_get_ciw(device->cdev, CIW_TYPE_RCD);
-	if (!ciw || ciw->cmd == 0) {
+	if (!ciw || ciw->cmd != DASD_ECKD_CCW_RCD) {
 		ret = -EOPNOTSUPP;
 		goto out_error;
 	}
-	rcd_buf = kzalloc(ciw->count, GFP_KERNEL | GFP_DMA);
+	rcd_buf = kzalloc(DASD_ECKD_RCD_DATA_SIZE, GFP_KERNEL | GFP_DMA);
 	if (!rcd_buf) {
 		ret = -ENOMEM;
 		goto out_error;
 	}
-
-	/*
-	 * buffer has to start with EBCDIC "V1.0" to show
-	 * support for virtual device SNEQ
-	 */
-	rcd_buf[0] = 0xE5;
-	rcd_buf[1] = 0xF1;
-	rcd_buf[2] = 0x4B;
-	rcd_buf[3] = 0xF0;
-	cqr = dasd_eckd_build_rcd_lpm(device, rcd_buf, ciw, lpm);
+	cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 1 /* RCD */,
+				   0, /* use rcd_buf as data ara */
+				   device);
 	if (IS_ERR(cqr)) {
-		ret =  PTR_ERR(cqr);
+		DBF_DEV_EVENT(DBF_WARNING, device, "%s",
+			      "Could not allocate RCD request");
+		ret = -ENOMEM;
 		goto out_error;
 	}
+	dasd_eckd_fill_rcd_cqr(device, cqr, rcd_buf, lpm);
 	ret = dasd_sleep_on(cqr);
 	/*
 	 * on success we update the user input parms
@@ -831,7 +863,7 @@
 	if (ret)
 		goto out_error;
 
-	*rcd_buffer_size = ciw->count;
+	*rcd_buffer_size = DASD_ECKD_RCD_DATA_SIZE;
 	*rcd_buffer = rcd_buf;
 	return 0;
 out_error:
@@ -901,18 +933,18 @@
 	void *conf_data;
 	int conf_len, conf_data_saved;
 	int rc;
-	__u8 lpm;
+	__u8 lpm, opm;
 	struct dasd_eckd_private *private;
-	struct dasd_eckd_path *path_data;
+	struct dasd_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);
+	path_data = &device->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){
+		if (lpm & opm) {
 			rc = dasd_eckd_read_conf_lpm(device, &conf_data,
 						     &conf_len, lpm);
 			if (rc && rc != -EOPNOTSUPP) {	/* -EOPNOTSUPP is ok */
@@ -925,6 +957,8 @@
 				DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "%s",
 						"No configuration data "
 						"retrieved");
+				/* no further analysis possible */
+				path_data->opm |= lpm;
 				continue;	/* no error */
 			}
 			/* save first valid configuration data */
@@ -948,6 +982,7 @@
 				path_data->ppm |= lpm;
 				break;
 			}
+			path_data->opm |= lpm;
 			if (conf_data != private->conf_data)
 				kfree(conf_data);
 		}
@@ -955,6 +990,140 @@
 	return 0;
 }
 
+static int verify_fcx_max_data(struct dasd_device *device, __u8 lpm)
+{
+	struct dasd_eckd_private *private;
+	int mdc;
+	u32 fcx_max_data;
+
+	private = (struct dasd_eckd_private *) device->private;
+	if (private->fcx_max_data) {
+		mdc = ccw_device_get_mdc(device->cdev, lpm);
+		if ((mdc < 0)) {
+			dev_warn(&device->cdev->dev,
+				 "Detecting the maximum data size for zHPF "
+				 "requests failed (rc=%d) for a new path %x\n",
+				 mdc, lpm);
+			return mdc;
+		}
+		fcx_max_data = mdc * FCX_MAX_DATA_FACTOR;
+		if (fcx_max_data < private->fcx_max_data) {
+			dev_warn(&device->cdev->dev,
+				 "The maximum data size for zHPF requests %u "
+				 "on a new path %x is below the active maximum "
+				 "%u\n", fcx_max_data, lpm,
+				 private->fcx_max_data);
+			return -EACCES;
+		}
+	}
+	return 0;
+}
+
+static void do_path_verification_work(struct work_struct *work)
+{
+	struct path_verification_work_data *data;
+	struct dasd_device *device;
+	__u8 lpm, opm, npm, ppm, epm;
+	unsigned long flags;
+	int rc;
+
+	data = container_of(work, struct path_verification_work_data, worker);
+	device = data->device;
+
+	opm = 0;
+	npm = 0;
+	ppm = 0;
+	epm = 0;
+	for (lpm = 0x80; lpm; lpm >>= 1) {
+		if (lpm & data->tbvpm) {
+			memset(data->rcd_buffer, 0, sizeof(data->rcd_buffer));
+			memset(&data->cqr, 0, sizeof(data->cqr));
+			data->cqr.cpaddr = &data->ccw;
+			rc = dasd_eckd_read_conf_immediately(device, &data->cqr,
+							     data->rcd_buffer,
+							     lpm);
+			if (!rc) {
+				switch (dasd_eckd_path_access(data->rcd_buffer,
+						     DASD_ECKD_RCD_DATA_SIZE)) {
+				case 0x02:
+					npm |= lpm;
+					break;
+				case 0x03:
+					ppm |= lpm;
+					break;
+				}
+				opm |= lpm;
+			} else if (rc == -EOPNOTSUPP) {
+				DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "%s",
+				       "path verification: No configuration "
+				       "data retrieved");
+				opm |= lpm;
+			} else if (rc == -EAGAIN) {
+				DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "%s",
+					"path verification: device is stopped,"
+					" try again later");
+				epm |= lpm;
+			} else {
+				dev_warn(&device->cdev->dev,
+					 "Reading device feature codes failed "
+					 "(rc=%d) for new path %x\n", rc, lpm);
+				continue;
+			}
+			if (verify_fcx_max_data(device, lpm)) {
+				opm &= ~lpm;
+				npm &= ~lpm;
+				ppm &= ~lpm;
+			}
+		}
+	}
+	/*
+	 * There is a small chance that a path is lost again between
+	 * above path verification and the following modification of
+	 * the device opm mask. We could avoid that race here by using
+	 * yet another path mask, but we rather deal with this unlikely
+	 * situation in dasd_start_IO.
+	 */
+	spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
+	if (!device->path_data.opm && opm) {
+		device->path_data.opm = opm;
+		dasd_generic_path_operational(device);
+	} else
+		device->path_data.opm |= opm;
+	device->path_data.npm |= npm;
+	device->path_data.ppm |= ppm;
+	device->path_data.tbvpm |= epm;
+	spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
+
+	dasd_put_device(device);
+	if (data->isglobal)
+		mutex_unlock(&dasd_path_verification_mutex);
+	else
+		kfree(data);
+}
+
+static int dasd_eckd_verify_path(struct dasd_device *device, __u8 lpm)
+{
+	struct path_verification_work_data *data;
+
+	data = kmalloc(sizeof(*data), GFP_ATOMIC | GFP_DMA);
+	if (!data) {
+		if (mutex_trylock(&dasd_path_verification_mutex)) {
+			data = path_verification_worker;
+			data->isglobal = 1;
+		} else
+			return -ENOMEM;
+	} else {
+		memset(data, 0, sizeof(*data));
+		data->isglobal = 0;
+	}
+	INIT_WORK(&data->worker, do_path_verification_work);
+	dasd_get_device(device);
+	data->device = device;
+	data->tbvpm = lpm;
+	schedule_work(&data->worker);
+	return 0;
+}
+
 static int dasd_eckd_read_features(struct dasd_device *device)
 {
 	struct dasd_psf_prssd_data *prssdp;
@@ -1749,6 +1918,7 @@
 	if (cqr->block && (cqr->startdev != cqr->block->base)) {
 		dasd_eckd_reset_ccw_to_base_io(cqr);
 		cqr->startdev = cqr->block->base;
+		cqr->lpm = cqr->block->base->path_data.opm;
 	}
 };
 
@@ -2017,7 +2187,7 @@
 	cqr->memdev = startdev;
 	cqr->block = block;
 	cqr->expires = startdev->default_expires * HZ;	/* default 5 minutes */
-	cqr->lpm = private->path_data.ppm;
+	cqr->lpm = startdev->path_data.ppm;
 	cqr->retries = 256;
 	cqr->buildclk = get_clock();
 	cqr->status = DASD_CQR_FILLED;
@@ -2194,7 +2364,7 @@
 	cqr->memdev = startdev;
 	cqr->block = block;
 	cqr->expires = startdev->default_expires * HZ;	/* default 5 minutes */
-	cqr->lpm = private->path_data.ppm;
+	cqr->lpm = startdev->path_data.ppm;
 	cqr->retries = 256;
 	cqr->buildclk = get_clock();
 	cqr->status = DASD_CQR_FILLED;
@@ -2484,7 +2654,7 @@
 	cqr->memdev = startdev;
 	cqr->block = block;
 	cqr->expires = startdev->default_expires * HZ;	/* default 5 minutes */
-	cqr->lpm = private->path_data.ppm;
+	cqr->lpm = startdev->path_data.ppm;
 	cqr->retries = 256;
 	cqr->buildclk = get_clock();
 	cqr->status = DASD_CQR_FILLED;
@@ -3624,6 +3794,7 @@
 	.set_offline = dasd_generic_set_offline,
 	.set_online  = dasd_eckd_set_online,
 	.notify      = dasd_generic_notify,
+	.path_event  = dasd_generic_path_event,
 	.freeze      = dasd_generic_pm_freeze,
 	.thaw	     = dasd_generic_restore_device,
 	.restore     = dasd_generic_restore_device,
@@ -3651,6 +3822,7 @@
 	.check_device = dasd_eckd_check_characteristics,
 	.uncheck_device = dasd_eckd_uncheck_device,
 	.do_analysis = dasd_eckd_do_analysis,
+	.verify_path = dasd_eckd_verify_path,
 	.ready_to_online = dasd_eckd_ready_to_online,
 	.online_to_ready = dasd_eckd_online_to_ready,
 	.fill_geometry = dasd_eckd_fill_geometry,
@@ -3683,11 +3855,19 @@
 				   GFP_KERNEL | GFP_DMA);
 	if (!dasd_reserve_req)
 		return -ENOMEM;
+	path_verification_worker = kmalloc(sizeof(*path_verification_worker),
+				   GFP_KERNEL | GFP_DMA);
+	if (!path_verification_worker) {
+		kfree(dasd_reserve_req);
+		return -ENOMEM;
+	}
 	ret = ccw_driver_register(&dasd_eckd_driver);
 	if (!ret)
 		wait_for_device_probe();
-	else
+	else {
+		kfree(path_verification_worker);
 		kfree(dasd_reserve_req);
+	}
 	return ret;
 }
 
@@ -3695,6 +3875,7 @@
 dasd_eckd_cleanup(void)
 {
 	ccw_driver_unregister(&dasd_eckd_driver);
+	kfree(path_verification_worker);
 	kfree(dasd_reserve_req);
 }