|
@@ -90,6 +90,18 @@ static struct {
|
|
} *dasd_reserve_req;
|
|
} *dasd_reserve_req;
|
|
static DEFINE_MUTEX(dasd_reserve_mutex);
|
|
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
|
|
/* initial attempt at a probe function. this can be simplified once
|
|
* the other detection code is gone */
|
|
* the other detection code is gone */
|
|
@@ -755,26 +767,27 @@ static int dasd_eckd_get_uid(struct dasd_device *device, struct dasd_uid *uid)
|
|
return -EINVAL;
|
|
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;
|
|
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 = 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->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->startdev = device;
|
|
cqr->memdev = device;
|
|
cqr->memdev = device;
|
|
@@ -784,7 +797,29 @@ static struct dasd_ccw_req *dasd_eckd_build_rcd_lpm(struct dasd_device *device,
|
|
cqr->retries = 256;
|
|
cqr->retries = 256;
|
|
cqr->buildclk = get_clock();
|
|
cqr->buildclk = get_clock();
|
|
cqr->status = DASD_CQR_FILLED;
|
|
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,
|
|
static int dasd_eckd_read_conf_lpm(struct dasd_device *device,
|
|
@@ -797,32 +832,29 @@ static int dasd_eckd_read_conf_lpm(struct dasd_device *device,
|
|
struct dasd_ccw_req *cqr;
|
|
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);
|
|
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;
|
|
ret = -EOPNOTSUPP;
|
|
goto out_error;
|
|
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) {
|
|
if (!rcd_buf) {
|
|
ret = -ENOMEM;
|
|
ret = -ENOMEM;
|
|
goto out_error;
|
|
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)) {
|
|
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;
|
|
goto out_error;
|
|
}
|
|
}
|
|
|
|
+ dasd_eckd_fill_rcd_cqr(device, cqr, rcd_buf, lpm);
|
|
ret = dasd_sleep_on(cqr);
|
|
ret = dasd_sleep_on(cqr);
|
|
/*
|
|
/*
|
|
* on success we update the user input parms
|
|
* on success we update the user input parms
|
|
@@ -831,7 +863,7 @@ static int dasd_eckd_read_conf_lpm(struct dasd_device *device,
|
|
if (ret)
|
|
if (ret)
|
|
goto out_error;
|
|
goto out_error;
|
|
|
|
|
|
- *rcd_buffer_size = ciw->count;
|
|
|
|
|
|
+ *rcd_buffer_size = DASD_ECKD_RCD_DATA_SIZE;
|
|
*rcd_buffer = rcd_buf;
|
|
*rcd_buffer = rcd_buf;
|
|
return 0;
|
|
return 0;
|
|
out_error:
|
|
out_error:
|
|
@@ -901,18 +933,18 @@ static int dasd_eckd_read_conf(struct dasd_device *device)
|
|
void *conf_data;
|
|
void *conf_data;
|
|
int conf_len, conf_data_saved;
|
|
int conf_len, conf_data_saved;
|
|
int rc;
|
|
int rc;
|
|
- __u8 lpm;
|
|
|
|
|
|
+ __u8 lpm, opm;
|
|
struct dasd_eckd_private *private;
|
|
struct dasd_eckd_private *private;
|
|
- struct dasd_eckd_path *path_data;
|
|
|
|
|
|
+ struct dasd_path *path_data;
|
|
|
|
|
|
private = (struct dasd_eckd_private *) device->private;
|
|
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;
|
|
lpm = 0x80;
|
|
conf_data_saved = 0;
|
|
conf_data_saved = 0;
|
|
/* get configuration data per operational path */
|
|
/* get configuration data per operational path */
|
|
for (lpm = 0x80; lpm; lpm>>= 1) {
|
|
for (lpm = 0x80; lpm; lpm>>= 1) {
|
|
- if (lpm & path_data->opm){
|
|
|
|
|
|
+ if (lpm & opm) {
|
|
rc = dasd_eckd_read_conf_lpm(device, &conf_data,
|
|
rc = dasd_eckd_read_conf_lpm(device, &conf_data,
|
|
&conf_len, lpm);
|
|
&conf_len, lpm);
|
|
if (rc && rc != -EOPNOTSUPP) { /* -EOPNOTSUPP is ok */
|
|
if (rc && rc != -EOPNOTSUPP) { /* -EOPNOTSUPP is ok */
|
|
@@ -925,6 +957,8 @@ static int dasd_eckd_read_conf(struct dasd_device *device)
|
|
DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "%s",
|
|
DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "%s",
|
|
"No configuration data "
|
|
"No configuration data "
|
|
"retrieved");
|
|
"retrieved");
|
|
|
|
+ /* no further analysis possible */
|
|
|
|
+ path_data->opm |= lpm;
|
|
continue; /* no error */
|
|
continue; /* no error */
|
|
}
|
|
}
|
|
/* save first valid configuration data */
|
|
/* save first valid configuration data */
|
|
@@ -948,6 +982,7 @@ static int dasd_eckd_read_conf(struct dasd_device *device)
|
|
path_data->ppm |= lpm;
|
|
path_data->ppm |= lpm;
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
|
|
+ path_data->opm |= lpm;
|
|
if (conf_data != private->conf_data)
|
|
if (conf_data != private->conf_data)
|
|
kfree(conf_data);
|
|
kfree(conf_data);
|
|
}
|
|
}
|
|
@@ -955,6 +990,140 @@ static int dasd_eckd_read_conf(struct dasd_device *device)
|
|
return 0;
|
|
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)
|
|
static int dasd_eckd_read_features(struct dasd_device *device)
|
|
{
|
|
{
|
|
struct dasd_psf_prssd_data *prssdp;
|
|
struct dasd_psf_prssd_data *prssdp;
|
|
@@ -1749,6 +1918,7 @@ static void dasd_eckd_handle_terminated_request(struct dasd_ccw_req *cqr)
|
|
if (cqr->block && (cqr->startdev != cqr->block->base)) {
|
|
if (cqr->block && (cqr->startdev != cqr->block->base)) {
|
|
dasd_eckd_reset_ccw_to_base_io(cqr);
|
|
dasd_eckd_reset_ccw_to_base_io(cqr);
|
|
cqr->startdev = cqr->block->base;
|
|
cqr->startdev = cqr->block->base;
|
|
|
|
+ cqr->lpm = cqr->block->base->path_data.opm;
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
@@ -2017,7 +2187,7 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_cmd_single(
|
|
cqr->memdev = startdev;
|
|
cqr->memdev = startdev;
|
|
cqr->block = block;
|
|
cqr->block = block;
|
|
cqr->expires = startdev->default_expires * HZ; /* default 5 minutes */
|
|
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->retries = 256;
|
|
cqr->buildclk = get_clock();
|
|
cqr->buildclk = get_clock();
|
|
cqr->status = DASD_CQR_FILLED;
|
|
cqr->status = DASD_CQR_FILLED;
|
|
@@ -2194,7 +2364,7 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_cmd_track(
|
|
cqr->memdev = startdev;
|
|
cqr->memdev = startdev;
|
|
cqr->block = block;
|
|
cqr->block = block;
|
|
cqr->expires = startdev->default_expires * HZ; /* default 5 minutes */
|
|
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->retries = 256;
|
|
cqr->buildclk = get_clock();
|
|
cqr->buildclk = get_clock();
|
|
cqr->status = DASD_CQR_FILLED;
|
|
cqr->status = DASD_CQR_FILLED;
|
|
@@ -2484,7 +2654,7 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_tpm_track(
|
|
cqr->memdev = startdev;
|
|
cqr->memdev = startdev;
|
|
cqr->block = block;
|
|
cqr->block = block;
|
|
cqr->expires = startdev->default_expires * HZ; /* default 5 minutes */
|
|
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->retries = 256;
|
|
cqr->buildclk = get_clock();
|
|
cqr->buildclk = get_clock();
|
|
cqr->status = DASD_CQR_FILLED;
|
|
cqr->status = DASD_CQR_FILLED;
|
|
@@ -3624,6 +3794,7 @@ static struct ccw_driver dasd_eckd_driver = {
|
|
.set_offline = dasd_generic_set_offline,
|
|
.set_offline = dasd_generic_set_offline,
|
|
.set_online = dasd_eckd_set_online,
|
|
.set_online = dasd_eckd_set_online,
|
|
.notify = dasd_generic_notify,
|
|
.notify = dasd_generic_notify,
|
|
|
|
+ .path_event = dasd_generic_path_event,
|
|
.freeze = dasd_generic_pm_freeze,
|
|
.freeze = dasd_generic_pm_freeze,
|
|
.thaw = dasd_generic_restore_device,
|
|
.thaw = dasd_generic_restore_device,
|
|
.restore = dasd_generic_restore_device,
|
|
.restore = dasd_generic_restore_device,
|
|
@@ -3651,6 +3822,7 @@ static struct dasd_discipline dasd_eckd_discipline = {
|
|
.check_device = dasd_eckd_check_characteristics,
|
|
.check_device = dasd_eckd_check_characteristics,
|
|
.uncheck_device = dasd_eckd_uncheck_device,
|
|
.uncheck_device = dasd_eckd_uncheck_device,
|
|
.do_analysis = dasd_eckd_do_analysis,
|
|
.do_analysis = dasd_eckd_do_analysis,
|
|
|
|
+ .verify_path = dasd_eckd_verify_path,
|
|
.ready_to_online = dasd_eckd_ready_to_online,
|
|
.ready_to_online = dasd_eckd_ready_to_online,
|
|
.online_to_ready = dasd_eckd_online_to_ready,
|
|
.online_to_ready = dasd_eckd_online_to_ready,
|
|
.fill_geometry = dasd_eckd_fill_geometry,
|
|
.fill_geometry = dasd_eckd_fill_geometry,
|
|
@@ -3683,11 +3855,19 @@ dasd_eckd_init(void)
|
|
GFP_KERNEL | GFP_DMA);
|
|
GFP_KERNEL | GFP_DMA);
|
|
if (!dasd_reserve_req)
|
|
if (!dasd_reserve_req)
|
|
return -ENOMEM;
|
|
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);
|
|
ret = ccw_driver_register(&dasd_eckd_driver);
|
|
if (!ret)
|
|
if (!ret)
|
|
wait_for_device_probe();
|
|
wait_for_device_probe();
|
|
- else
|
|
|
|
|
|
+ else {
|
|
|
|
+ kfree(path_verification_worker);
|
|
kfree(dasd_reserve_req);
|
|
kfree(dasd_reserve_req);
|
|
|
|
+ }
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -3695,6 +3875,7 @@ static void __exit
|
|
dasd_eckd_cleanup(void)
|
|
dasd_eckd_cleanup(void)
|
|
{
|
|
{
|
|
ccw_driver_unregister(&dasd_eckd_driver);
|
|
ccw_driver_unregister(&dasd_eckd_driver);
|
|
|
|
+ kfree(path_verification_worker);
|
|
kfree(dasd_reserve_req);
|
|
kfree(dasd_reserve_req);
|
|
}
|
|
}
|
|
|
|
|