[S390] dasd: Add support for enhanced VM UID

When z/VM provides two virtual devices (minidisks) that reside on the
same real device, both will receive the configuration data from the
real device and thus get the same uid. To fix this problem, z/VM
provides an additional configuration data record that allows to
distinguish between minidisks.
z/VM APAR VM64273 needs be installed so this fix has an effect.

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 3590fdb..773b3fe 100644
--- a/drivers/s390/block/dasd_eckd.c
+++ b/drivers/s390/block/dasd_eckd.c
@@ -313,8 +313,8 @@
 	memset(pfxdata, 0, sizeof(*pfxdata));
 	/* prefix data */
 	pfxdata->format = 0;
-	pfxdata->base_address = basepriv->conf_data.ned1.unit_addr;
-	pfxdata->base_lss = basepriv->conf_data.ned1.ID;
+	pfxdata->base_address = basepriv->ned->unit_addr;
+	pfxdata->base_lss = basepriv->ned->ID;
 	pfxdata->validity.define_extend = 1;
 
 	/* private uid is kept up to date, conf_data may be outdated */
@@ -536,36 +536,40 @@
 /*
  * Generate device unique id that specifies the physical device.
  */
-static int
-dasd_eckd_generate_uid(struct dasd_device *device, struct dasd_uid *uid)
+static int dasd_eckd_generate_uid(struct dasd_device *device,
+				  struct dasd_uid *uid)
 {
 	struct dasd_eckd_private *private;
-	struct dasd_eckd_confdata *confdata;
+	int count;
 
 	private = (struct dasd_eckd_private *) device->private;
 	if (!private)
 		return -ENODEV;
-	confdata = &private->conf_data;
-	if (!confdata)
+	if (!private->ned || !private->gneq)
 		return -ENODEV;
 
 	memset(uid, 0, sizeof(struct dasd_uid));
-	memcpy(uid->vendor, confdata->ned1.HDA_manufacturer,
+	memcpy(uid->vendor, private->ned->HDA_manufacturer,
 	       sizeof(uid->vendor) - 1);
 	EBCASC(uid->vendor, sizeof(uid->vendor) - 1);
-	memcpy(uid->serial, confdata->ned1.HDA_location,
+	memcpy(uid->serial, private->ned->HDA_location,
 	       sizeof(uid->serial) - 1);
 	EBCASC(uid->serial, sizeof(uid->serial) - 1);
-	uid->ssid = confdata->neq.subsystemID;
-	uid->real_unit_addr = confdata->ned1.unit_addr;
-	if (confdata->ned2.sneq.flags == 0x40 &&
-	    confdata->ned2.sneq.format == 0x0001) {
-		uid->type = confdata->ned2.sneq.sua_flags;
+	uid->ssid = private->gneq->subsystemID;
+	uid->real_unit_addr = private->ned->unit_addr;;
+	if (private->sneq) {
+		uid->type = private->sneq->sua_flags;
 		if (uid->type == UA_BASE_PAV_ALIAS)
-			uid->base_unit_addr = confdata->ned2.sneq.base_unit_addr;
+			uid->base_unit_addr = private->sneq->base_unit_addr;
 	} else {
 		uid->type = UA_BASE_DEVICE;
 	}
+	if (private->vdsneq) {
+		for (count = 0; count < 16; count++) {
+			sprintf(uid->vduit+2*count, "%02x",
+				private->vdsneq->uit[count]);
+		}
+	}
 	return 0;
 }
 
@@ -623,6 +627,15 @@
 		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);
 	if (IS_ERR(cqr)) {
 		ret =  PTR_ERR(cqr);
@@ -646,8 +659,62 @@
 	return ret;
 }
 
-static int
-dasd_eckd_read_conf(struct dasd_device *device)
+static int dasd_eckd_identify_conf_parts(struct dasd_eckd_private *private)
+{
+
+	struct dasd_sneq *sneq;
+	int i, count;
+
+	private->ned = NULL;
+	private->sneq = NULL;
+	private->vdsneq = NULL;
+	private->gneq = NULL;
+	count = private->conf_len / sizeof(struct dasd_sneq);
+	sneq = (struct dasd_sneq *)private->conf_data;
+	for (i = 0; i < count; ++i) {
+		if (sneq->flags.identifier == 1 && sneq->format == 1)
+			private->sneq = sneq;
+		else if (sneq->flags.identifier == 1 && sneq->format == 4)
+			private->vdsneq = (struct vd_sneq *)sneq;
+		else if (sneq->flags.identifier == 2)
+			private->gneq = (struct dasd_gneq *)sneq;
+		else if (sneq->flags.identifier == 3 && sneq->res1 == 1)
+			private->ned = (struct dasd_ned *)sneq;
+		sneq++;
+	}
+	if (!private->ned || !private->gneq) {
+		private->ned = NULL;
+		private->sneq = NULL;
+		private->vdsneq = NULL;
+		private->gneq = NULL;
+		return -EINVAL;
+	}
+	return 0;
+
+};
+
+static unsigned char dasd_eckd_path_access(void *conf_data, int conf_len)
+{
+	struct dasd_gneq *gneq;
+	int i, count, found;
+
+	count = conf_len / sizeof(*gneq);
+	gneq = (struct dasd_gneq *)conf_data;
+	found = 0;
+	for (i = 0; i < count; ++i) {
+		if (gneq->flags.identifier == 2) {
+			found = 1;
+			break;
+		}
+		gneq++;
+	}
+	if (found)
+		return ((char *)gneq)[18] & 0x07;
+	else
+		return 0;
+}
+
+static int dasd_eckd_read_conf(struct dasd_device *device)
 {
 	void *conf_data;
 	int conf_len, conf_data_saved;
@@ -661,7 +728,6 @@
 	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){
@@ -678,22 +744,20 @@
 					"data retrieved");
 				continue;	/* no error */
 			}
-			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 error */
-			}
 			/* save first valid configuration data */
-			if (!conf_data_saved){
-				memcpy(&private->conf_data, conf_data,
-				       sizeof(struct dasd_eckd_confdata));
+			if (!conf_data_saved) {
+				kfree(private->conf_data);
+				private->conf_data = conf_data;
+				private->conf_len = conf_len;
+				if (dasd_eckd_identify_conf_parts(private)) {
+					private->conf_data = NULL;
+					private->conf_len = 0;
+					kfree(conf_data);
+					continue;
+				}
 				conf_data_saved++;
 			}
-			switch (((char *)conf_data)[242] & 0x07){
+			switch (dasd_eckd_path_access(conf_data, conf_len)) {
 			case 0x02:
 				path_data->npm |= lpm;
 				break;
@@ -701,7 +765,8 @@
 				path_data->ppm |= lpm;
 				break;
 			}
-			kfree(conf_data);
+			if (conf_data != private->conf_data)
+				kfree(conf_data);
 		}
 	}
 	return 0;
@@ -952,6 +1017,7 @@
 	dasd_free_block(device->block);
 	device->block = NULL;
 out_err1:
+	kfree(private->conf_data);
 	kfree(device->private);
 	device->private = NULL;
 	return rc;
@@ -959,7 +1025,17 @@
 
 static void dasd_eckd_uncheck_device(struct dasd_device *device)
 {
+	struct dasd_eckd_private *private;
+
+	private = (struct dasd_eckd_private *) device->private;
 	dasd_alias_disconnect_device_from_lcu(device);
+	private->ned = NULL;
+	private->sneq = NULL;
+	private->vdsneq = NULL;
+	private->gneq = NULL;
+	private->conf_len = 0;
+	kfree(private->conf_data);
+	private->conf_data = NULL;
 }
 
 static struct dasd_ccw_req *
@@ -1746,9 +1822,10 @@
 	info->characteristics_size = sizeof(struct dasd_eckd_characteristics);
 	memcpy(info->characteristics, &private->rdc_data,
 	       sizeof(struct dasd_eckd_characteristics));
-	info->confdata_size = sizeof(struct dasd_eckd_confdata);
-	memcpy(info->configuration_data, &private->conf_data,
-	       sizeof(struct dasd_eckd_confdata));
+	info->confdata_size = min((unsigned long)private->conf_len,
+				  sizeof(info->configuration_data));
+	memcpy(info->configuration_data, private->conf_data,
+	       info->confdata_size);
 	return 0;
 }