mirror of
https://github.com/torvalds/linux.git
synced 2024-11-01 01:31:44 +00:00
[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: Stefan Haberland <stefan.haberland@de.ibm.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
This commit is contained in:
parent
e58b0d902f
commit
b206181d63
@ -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,59 +1026,120 @@ 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) {
|
||||
rc = dasd_eckd_read_conf_lpm(device, &conf_data,
|
||||
&conf_len, lpm);
|
||||
if (rc && rc != -EOPNOTSUPP) { /* -EOPNOTSUPP is ok */
|
||||
DBF_EVENT_DEVID(DBF_WARNING, device->cdev,
|
||||
"Read configuration data returned "
|
||||
"error %d", rc);
|
||||
return rc;
|
||||
}
|
||||
if (conf_data == NULL) {
|
||||
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 */
|
||||
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 (dasd_eckd_path_access(conf_data, conf_len)) {
|
||||
case 0x02:
|
||||
path_data->npm |= lpm;
|
||||
break;
|
||||
case 0x03:
|
||||
path_data->ppm |= lpm;
|
||||
break;
|
||||
}
|
||||
path_data->opm |= lpm;
|
||||
if (conf_data != private->conf_data)
|
||||
kfree(conf_data);
|
||||
if (!(lpm & opm))
|
||||
continue;
|
||||
rc = dasd_eckd_read_conf_lpm(device, &conf_data,
|
||||
&conf_len, lpm);
|
||||
if (rc && rc != -EOPNOTSUPP) { /* -EOPNOTSUPP is ok */
|
||||
DBF_EVENT_DEVID(DBF_WARNING, device->cdev,
|
||||
"Read configuration data returned "
|
||||
"error %d", rc);
|
||||
return rc;
|
||||
}
|
||||
if (conf_data == NULL) {
|
||||
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 */
|
||||
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;
|
||||
}
|
||||
/*
|
||||
* 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:
|
||||
path_data->npm |= lpm;
|
||||
break;
|
||||
case 0x03:
|
||||
path_data->ppm |= lpm;
|
||||
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,64 +1243,129 @@ 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));
|
||||
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",
|
||||
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)) {
|
||||
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;
|
||||
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
|
||||
* 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);
|
||||
}
|
||||
/*
|
||||
* 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)
|
||||
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user