From a4d26c6aeceea330ee5e0fb6b017d57e3b252d29 Mon Sep 17 00:00:00 2001 From: Stefan Weinhuber Date: Wed, 5 Jan 2011 12:48:03 +0100 Subject: [S390] dasd: do path verification for paths added at runtime When a new path is added at runtime, the CIO layer will call the drivers path_event callback. The DASD device driver uses this callback to trigger a path verification for the new path. The driver will use only those paths for I/O, which have been successfully verified. Signed-off-by: Stefan Weinhuber Signed-off-by: Martin Schwidefsky --- drivers/s390/block/dasd_eckd.c | 261 ++++++++++++++++++++++++++++++++++------- 1 file changed, 221 insertions(+), 40 deletions(-) (limited to 'drivers/s390/block/dasd_eckd.c') diff --git a/drivers/s390/block/dasd_eckd.c b/drivers/s390/block/dasd_eckd.c index 549443af121c..a1ebf5722ae5 100644 --- a/drivers/s390/block/dasd_eckd.c +++ b/drivers/s390/block/dasd_eckd.c @@ -90,6 +90,18 @@ static struct { } *dasd_reserve_req; 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 * 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; } -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; - - 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->cmd_code = ciw->cmd; + ccw->cmd_code = DASD_ECKD_CCW_RCD; + ccw->flags = 0; 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->memdev = device; @@ -784,7 +797,29 @@ static struct dasd_ccw_req *dasd_eckd_build_rcd_lpm(struct dasd_device *device, cqr->retries = 256; cqr->buildclk = get_clock(); 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, @@ -797,32 +832,29 @@ static int dasd_eckd_read_conf_lpm(struct dasd_device *device, 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); - if (!ciw || ciw->cmd == 0) { + if (!ciw || ciw->cmd != DASD_ECKD_CCW_RCD) { ret = -EOPNOTSUPP; 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) { 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); + cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 1 /* RCD */, + 0, /* use rcd_buf as data ara */ + device); 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; } + dasd_eckd_fill_rcd_cqr(device, cqr, rcd_buf, lpm); ret = dasd_sleep_on(cqr); /* * 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) goto out_error; - *rcd_buffer_size = ciw->count; + *rcd_buffer_size = DASD_ECKD_RCD_DATA_SIZE; *rcd_buffer = rcd_buf; return 0; out_error: @@ -901,18 +933,18 @@ static int dasd_eckd_read_conf(struct dasd_device *device) void *conf_data; int conf_len, conf_data_saved; int rc; - __u8 lpm; + __u8 lpm, opm; struct dasd_eckd_private *private; - struct dasd_eckd_path *path_data; + struct dasd_path *path_data; 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; conf_data_saved = 0; /* get configuration data per operational path */ for (lpm = 0x80; lpm; lpm>>= 1) { - if (lpm & path_data->opm){ + if (lpm & opm) { rc = dasd_eckd_read_conf_lpm(device, &conf_data, &conf_len, lpm); 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", "No configuration data " "retrieved"); + /* no further analysis possible */ + path_data->opm |= lpm; continue; /* no error */ } /* save first valid configuration data */ @@ -948,6 +982,7 @@ static int dasd_eckd_read_conf(struct dasd_device *device) path_data->ppm |= lpm; break; } + path_data->opm |= lpm; if (conf_data != private->conf_data) kfree(conf_data); } @@ -955,6 +990,140 @@ static int dasd_eckd_read_conf(struct dasd_device *device) 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) { 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)) { dasd_eckd_reset_ccw_to_base_io(cqr); 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->block = block; 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->buildclk = get_clock(); cqr->status = DASD_CQR_FILLED; @@ -2194,7 +2364,7 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_cmd_track( cqr->memdev = startdev; cqr->block = block; 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->buildclk = get_clock(); cqr->status = DASD_CQR_FILLED; @@ -2484,7 +2654,7 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_tpm_track( cqr->memdev = startdev; cqr->block = block; 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->buildclk = get_clock(); cqr->status = DASD_CQR_FILLED; @@ -3624,6 +3794,7 @@ static struct ccw_driver dasd_eckd_driver = { .set_offline = dasd_generic_set_offline, .set_online = dasd_eckd_set_online, .notify = dasd_generic_notify, + .path_event = dasd_generic_path_event, .freeze = dasd_generic_pm_freeze, .thaw = 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, .uncheck_device = dasd_eckd_uncheck_device, .do_analysis = dasd_eckd_do_analysis, + .verify_path = dasd_eckd_verify_path, .ready_to_online = dasd_eckd_ready_to_online, .online_to_ready = dasd_eckd_online_to_ready, .fill_geometry = dasd_eckd_fill_geometry, @@ -3683,11 +3855,19 @@ dasd_eckd_init(void) GFP_KERNEL | GFP_DMA); if (!dasd_reserve_req) 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); if (!ret) wait_for_device_probe(); - else + else { + kfree(path_verification_worker); kfree(dasd_reserve_req); + } return ret; } @@ -3695,6 +3875,7 @@ static void __exit dasd_eckd_cleanup(void) { ccw_driver_unregister(&dasd_eckd_driver); + kfree(path_verification_worker); kfree(dasd_reserve_req); } -- cgit v1.2.1