static void dasd_eckd_path_available_action()

in block/dasd_eckd.c [1288:1444]


static void dasd_eckd_path_available_action(struct dasd_device *device,
					    struct pe_handler_work_data *data)
{
	__u8 path_rcd_buf[DASD_ECKD_RCD_DATA_SIZE];
	__u8 lpm, opm, npm, ppm, epm, hpfpm, cablepm;
	struct dasd_conf_data *conf_data;
	struct dasd_conf path_conf;
	unsigned long flags;
	char print_uid[60];
	int rc, pos;

	opm = 0;
	npm = 0;
	ppm = 0;
	epm = 0;
	hpfpm = 0;
	cablepm = 0;

	for (lpm = 0x80; lpm; lpm >>= 1) {
		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,
						     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;
			hpfpm |= 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_conf.data = (void *)&path_rcd_buf;
		path_conf.len = DASD_ECKD_RCD_DATA_SIZE;
		if (dasd_eckd_identify_conf_parts(&path_conf)) {
			path_conf.data = NULL;
			path_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 (dasd_path_get_opm(device) &&
		    dasd_eckd_compare_path_uid(device, &path_conf)) {
			/*
			 * 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_conf)) {
				dasd_eckd_get_uid_string(&path_conf, print_uid);
				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;
				cablepm |= lpm;
				continue;
			}
		}

		conf_data = kzalloc(DASD_ECKD_RCD_DATA_SIZE, GFP_KERNEL);
		if (conf_data) {
			memcpy(conf_data, data->rcd_buffer,
			       DASD_ECKD_RCD_DATA_SIZE);
		} else {
			/*
			 * path is operational but path config data could not
			 * be stored due to low mem condition
			 * add it to the error path mask and schedule a path
			 * verification later that this could be added again
			 */
			epm |= lpm;
		}
		pos = pathmask_to_pos(lpm);
		dasd_eckd_store_conf_data(device, conf_data, pos);

		/*
		 * 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 (!dasd_path_get_opm(device) && opm) {
			dasd_path_set_opm(device, opm);
			dasd_generic_path_operational(device);
		} else {
			dasd_path_add_opm(device, opm);
		}
		dasd_path_add_nppm(device, npm);
		dasd_path_add_ppm(device, ppm);
		if (epm) {
			dasd_path_add_tbvpm(device, epm);
			dasd_device_set_timer(device, 50);
		}
		dasd_path_add_cablepm(device, cablepm);
		dasd_path_add_nohpfpm(device, hpfpm);
		spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);

		dasd_path_create_kobj(device, pos);
	}
}