Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b206181d authored by Stefan Haberland's avatar Stefan Haberland Committed by Martin Schwidefsky
Browse files

[S390] dasd: add sanity check to detect path connection error



Prevents possible data corruption by detecting cabling error.
Therefor read and compare the UID for all available channel paths.

Signed-off-by: default avatarStefan Haberland <stefan.haberland@de.ibm.com>
Signed-off-by: default avatarMartin Schwidefsky <schwidefsky@de.ibm.com>
parent e58b0d90
Loading
Loading
Loading
Loading
+300 −109
Original line number Diff line number Diff line
@@ -752,24 +752,13 @@ dasd_eckd_cdl_reclen(int recid)
		return sizes_trk0[recid];
	return LABEL_SIZE;
}

/*
 * Generate device unique id that specifies the physical device.
 */
static int dasd_eckd_generate_uid(struct dasd_device *device)
/* create unique id from private structure. */
static void create_uid(struct dasd_eckd_private *private)
{
	struct dasd_eckd_private *private;
	struct dasd_uid *uid;
	int count;
	unsigned long flags;
	struct dasd_uid *uid;

	private = (struct dasd_eckd_private *) device->private;
	if (!private)
		return -ENODEV;
	if (!private->ned || !private->gneq)
		return -ENODEV;
	uid = &private->uid;
	spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
	memset(uid, 0, sizeof(struct dasd_uid));
	memcpy(uid->vendor, private->ned->HDA_manufacturer,
	       sizeof(uid->vendor) - 1);
@@ -792,6 +781,23 @@ static int dasd_eckd_generate_uid(struct dasd_device *device)
				private->vdsneq->uit[count]);
		}
	}
}

/*
 * Generate device unique id that specifies the physical device.
 */
static int dasd_eckd_generate_uid(struct dasd_device *device)
{
	struct dasd_eckd_private *private;
	unsigned long flags;

	private = (struct dasd_eckd_private *) device->private;
	if (!private)
		return -ENODEV;
	if (!private->ned || !private->gneq)
		return -ENODEV;
	spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
	create_uid(private);
	spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
	return 0;
}
@@ -811,6 +817,21 @@ static int dasd_eckd_get_uid(struct dasd_device *device, struct dasd_uid *uid)
	return -EINVAL;
}

/*
 * compare device UID with data of a given dasd_eckd_private structure
 * return 0 for match
 */
static int dasd_eckd_compare_path_uid(struct dasd_device *device,
				      struct dasd_eckd_private *private)
{
	struct dasd_uid device_uid;

	create_uid(private);
	dasd_eckd_get_uid(device, &device_uid);

	return memcmp(&device_uid, &private->uid, sizeof(struct dasd_uid));
}

static void dasd_eckd_fill_rcd_cqr(struct dasd_device *device,
				   struct dasd_ccw_req *cqr,
				   __u8 *rcd_buffer,
@@ -1005,17 +1026,19 @@ static int dasd_eckd_read_conf(struct dasd_device *device)
	int conf_len, conf_data_saved;
	int rc;
	__u8 lpm, opm;
	struct dasd_eckd_private *private;
	struct dasd_eckd_private *private, path_private;
	struct dasd_path *path_data;
	struct dasd_uid *uid;
	char print_path_uid[60], print_device_uid[60];

	private = (struct dasd_eckd_private *) device->private;
	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 & opm) {
		if (!(lpm & opm))
			continue;
		rc = dasd_eckd_read_conf_lpm(device, &conf_data,
					     &conf_len, lpm);
		if (rc && rc != -EOPNOTSUPP) {	/* -EOPNOTSUPP is ok */
@@ -1043,7 +1066,65 @@ static int dasd_eckd_read_conf(struct dasd_device *device)
				kfree(conf_data);
				continue;
			}
			/*
			 * build device UID that other path data
			 * can be compared to it
			 */
			dasd_eckd_generate_uid(device);
			conf_data_saved++;
		} else {
			path_private.conf_data = conf_data;
			path_private.conf_len = DASD_ECKD_RCD_DATA_SIZE;
			if (dasd_eckd_identify_conf_parts(
				    &path_private)) {
				path_private.conf_data = NULL;
				path_private.conf_len = 0;
				kfree(conf_data);
				continue;
			}

			if (dasd_eckd_compare_path_uid(
				    device, &path_private)) {
				uid = &path_private.uid;
				if (strlen(uid->vduit) > 0)
					snprintf(print_path_uid,
						 sizeof(print_path_uid),
						 "%s.%s.%04x.%02x.%s",
						 uid->vendor, uid->serial,
						 uid->ssid, uid->real_unit_addr,
						 uid->vduit);
				else
					snprintf(print_path_uid,
						 sizeof(print_path_uid),
						 "%s.%s.%04x.%02x",
						 uid->vendor, uid->serial,
						 uid->ssid,
						 uid->real_unit_addr);
				uid = &private->uid;
				if (strlen(uid->vduit) > 0)
					snprintf(print_device_uid,
						 sizeof(print_device_uid),
						 "%s.%s.%04x.%02x.%s",
						 uid->vendor, uid->serial,
						 uid->ssid, uid->real_unit_addr,
						 uid->vduit);
				else
					snprintf(print_device_uid,
						 sizeof(print_device_uid),
						 "%s.%s.%04x.%02x",
						 uid->vendor, uid->serial,
						 uid->ssid,
						 uid->real_unit_addr);
				dev_err(&device->cdev->dev,
					"Not all channel paths lead to "
					"the same device, path %02X leads to "
					"device %s instead of %s\n", lpm,
					print_path_uid, print_device_uid);
				return -EINVAL;
			}

			path_private.conf_data = NULL;
			path_private.conf_len = 0;
		}
		switch (dasd_eckd_path_access(conf_data, conf_len)) {
		case 0x02:
@@ -1054,10 +1135,11 @@ static int dasd_eckd_read_conf(struct dasd_device *device)
			break;
		}
		path_data->opm |= lpm;

		if (conf_data != private->conf_data)
			kfree(conf_data);
	}
	}

	return 0;
}

@@ -1090,12 +1172,61 @@ static int verify_fcx_max_data(struct dasd_device *device, __u8 lpm)
	return 0;
}

static int rebuild_device_uid(struct dasd_device *device,
			      struct path_verification_work_data *data)
{
	struct dasd_eckd_private *private;
	struct dasd_path *path_data;
	__u8 lpm, opm;
	int rc;

	rc = -ENODEV;
	private = (struct dasd_eckd_private *) device->private;
	path_data = &device->path_data;
	opm = device->path_data.opm;

	for (lpm = 0x80; lpm; lpm >>= 1) {
		if (!(lpm & opm))
			continue;
		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) {
			if (rc == -EOPNOTSUPP) /* -EOPNOTSUPP is ok */
				continue;
			DBF_EVENT_DEVID(DBF_WARNING, device->cdev,
					"Read configuration data "
					"returned error %d", rc);
			break;
		}
		memcpy(private->conf_data, data->rcd_buffer,
		       DASD_ECKD_RCD_DATA_SIZE);
		if (dasd_eckd_identify_conf_parts(private)) {
			rc = -ENODEV;
		} else /* first valid path is enough */
			break;
	}

	if (!rc)
		rc = dasd_eckd_generate_uid(device);

	return rc;
}

static void do_path_verification_work(struct work_struct *work)
{
	struct path_verification_work_data *data;
	struct dasd_device *device;
	struct dasd_eckd_private path_private;
	struct dasd_uid *uid;
	__u8 path_rcd_buf[DASD_ECKD_RCD_DATA_SIZE];
	__u8 lpm, opm, npm, ppm, epm;
	unsigned long flags;
	char print_uid[60];
	int rc;

	data = container_of(work, struct path_verification_work_data, worker);
@@ -1112,8 +1243,9 @@ static void do_path_verification_work(struct work_struct *work)
	ppm = 0;
	epm = 0;
	for (lpm = 0x80; lpm; lpm >>= 1) {
		if (lpm & data->tbvpm) {
			memset(data->rcd_buffer, 0, sizeof(data->rcd_buffer));
		if (!(lpm & data->tbvpm))
			continue;
		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,
@@ -1121,7 +1253,8 @@ static void do_path_verification_work(struct work_struct *work)
						     lpm);
		if (!rc) {
			switch (dasd_eckd_path_access(data->rcd_buffer,
						     DASD_ECKD_RCD_DATA_SIZE)) {
						      DASD_ECKD_RCD_DATA_SIZE)
				) {
			case 0x02:
				npm |= lpm;
				break;
@@ -1150,9 +1283,71 @@ static void do_path_verification_work(struct work_struct *work)
			opm &= ~lpm;
			npm &= ~lpm;
			ppm &= ~lpm;
			continue;
		}

		/*
		 * save conf_data for comparison after
		 * rebuild_device_uid may have changed
		 * the original data
		 */
		memcpy(&path_rcd_buf, data->rcd_buffer,
		       DASD_ECKD_RCD_DATA_SIZE);
		path_private.conf_data = (void *) &path_rcd_buf;
		path_private.conf_len = DASD_ECKD_RCD_DATA_SIZE;
		if (dasd_eckd_identify_conf_parts(&path_private)) {
			path_private.conf_data = NULL;
			path_private.conf_len = 0;
			continue;
		}

		/*
		 * compare path UID with device UID only if at least
		 * one valid path is left
		 * in other case the device UID may have changed and
		 * the first working path UID will be used as device UID
		 */
		if (device->path_data.opm &&
		    dasd_eckd_compare_path_uid(device, &path_private)) {
			/*
			 * the comparison was not successful
			 * rebuild the device UID with at least one
			 * known path in case a z/VM hyperswap command
			 * has changed the device
			 *
			 * after this compare again
			 *
			 * if either the rebuild or the recompare fails
			 * the path can not be used
			 */
			if (rebuild_device_uid(device, data) ||
			    dasd_eckd_compare_path_uid(
				    device, &path_private)) {
				uid = &path_private.uid;
				if (strlen(uid->vduit) > 0)
					snprintf(print_uid, sizeof(print_uid),
						 "%s.%s.%04x.%02x.%s",
						 uid->vendor, uid->serial,
						 uid->ssid, uid->real_unit_addr,
						 uid->vduit);
				else
					snprintf(print_uid, sizeof(print_uid),
						 "%s.%s.%04x.%02x",
						 uid->vendor, uid->serial,
						 uid->ssid,
						 uid->real_unit_addr);
				dev_err(&device->cdev->dev,
					"The newly added channel path %02X "
					"will not be used because it leads "
					"to a different device %s\n",
					lpm, print_uid);
				opm &= ~lpm;
				npm &= ~lpm;
				ppm &= ~lpm;
				continue;
			}
		}

		/*
		 * There is a small chance that a path is lost again between
		 * above path verification and the following modification of
@@ -1170,6 +1365,7 @@ static void do_path_verification_work(struct work_struct *work)
		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)
@@ -1441,11 +1637,6 @@ dasd_eckd_check_characteristics(struct dasd_device *device)
			device->default_expires = value;
	}

	/* Generate device unique id */
	rc = dasd_eckd_generate_uid(device);
	if (rc)
		goto out_err1;

	dasd_eckd_get_uid(device, &temp_uid);
	if (temp_uid.type == UA_BASE_DEVICE) {
		block = dasd_alloc_block();