/* * raw track access always map to 64k in memory * so it maps to 16 blocks of 4k per track
*/ #define DASD_RAW_BLOCK_PER_TRACK 16 #define DASD_RAW_BLOCKSIZE 4096 /* 64k are 128 x 512 byte sectors */ #define DASD_RAW_SECTORS_PER_TRACK 128
/* initial attempt at a probe function. this can be simplified once
* the other detection code is gone */ staticint
dasd_eckd_probe (struct ccw_device *cdev)
{ int ret;
/* set ECKD specific ccw-device options */
ret = ccw_device_set_options(cdev, CCWDEV_ALLOW_FORCE |
CCWDEV_DO_PATHGROUP | CCWDEV_DO_MULTIPATH); if (ret) {
DBF_EVENT_DEVID(DBF_WARNING, cdev, "%s", "dasd_eckd_probe: could not set " "ccw-device options"); return ret;
}
ret = dasd_generic_probe(cdev); return ret;
}
/* * calculate failing track from sense data depending if * it is an EAV device or not
*/ staticint dasd_eckd_track_from_irb(struct irb *irb, struct dasd_device *device,
sector_t *track)
{ struct dasd_eckd_private *private = device->private;
u8 *sense = NULL;
u32 cyl;
u8 head;
sense = dasd_get_sense(irb); if (!sense) {
DBF_DEV_EVENT(DBF_WARNING, device, "%s", "ESE error no sense data\n"); return -EINVAL;
} if (!(sense[27] & DASD_SENSE_BIT_2)) {
DBF_DEV_EVENT(DBF_WARNING, device, "%s", "ESE error no valid track data\n"); return -EINVAL;
}
rc = get_phys_clock(&data->ep_sys_time); /* * Ignore return code if XRC is not supported or * sync clock is switched off
*/ if ((rc && !private->rdc_data.facilities.XRC_supported) ||
rc == -EOPNOTSUPP || rc == -EACCES) return 0;
/* switch on System Time Stamp - needed for XRC Support */
data->ga_extended |= 0x08; /* switch on 'Time Stamp Valid' */
data->ga_extended |= 0x02; /* switch on 'Extended Parameter' */
/* * For some commands the System Time Stamp is set in the define extent * data when XRC is supported. The validity of the time stamp must be * reflected in the prefix data as well.
*/ if (dedata->ga_extended & 0x08 && dedata->ga_extended & 0x02)
pfxdata->validity.time_stamp = 1; /* 'Time Stamp Valid' */
memset(data, 0, sizeof(struct LO_eckd_data));
sector = 0; if (rec_on_trk) { switch (private->rdc_data.dev_type) { case 0x3390:
dn = ceil_quot(reclen + 6, 232);
d = 9 + ceil_quot(reclen + 6 * (dn + 1), 34);
sector = (49 + (rec_on_trk - 1) * (10 + d)) / 8; break; case 0x3380:
d = 7 + ceil_quot(reclen + 12, 32);
sector = (39 + (rec_on_trk - 1) * (8 + d)) / 7; break;
}
}
data->sector = sector;
data->count = no_rec; switch (cmd) { case DASD_ECKD_CCW_WRITE_HOME_ADDRESS:
data->operation.orientation = 0x3;
data->operation.operation = 0x03; break; case DASD_ECKD_CCW_READ_HOME_ADDRESS:
data->operation.orientation = 0x3;
data->operation.operation = 0x16; break; case DASD_ECKD_CCW_WRITE_RECORD_ZERO:
data->operation.orientation = 0x1;
data->operation.operation = 0x03;
data->count++; break; case DASD_ECKD_CCW_READ_RECORD_ZERO:
data->operation.orientation = 0x3;
data->operation.operation = 0x16;
data->count++; break; case DASD_ECKD_CCW_WRITE: case DASD_ECKD_CCW_WRITE_MT: case DASD_ECKD_CCW_WRITE_KD: case DASD_ECKD_CCW_WRITE_KD_MT:
data->auxiliary.last_bytes_used = 0x1;
data->length = reclen;
data->operation.operation = 0x01; break; case DASD_ECKD_CCW_WRITE_CKD: case DASD_ECKD_CCW_WRITE_CKD_MT:
data->auxiliary.last_bytes_used = 0x1;
data->length = reclen;
data->operation.operation = 0x03; break; case DASD_ECKD_CCW_READ: case DASD_ECKD_CCW_READ_MT: case DASD_ECKD_CCW_READ_KD: case DASD_ECKD_CCW_READ_KD_MT:
data->auxiliary.last_bytes_used = 0x1;
data->length = reclen;
data->operation.operation = 0x06; break; case DASD_ECKD_CCW_READ_CKD: case DASD_ECKD_CCW_READ_CKD_MT:
data->auxiliary.last_bytes_used = 0x1;
data->length = reclen;
data->operation.operation = 0x16; break; case DASD_ECKD_CCW_READ_COUNT:
data->operation.operation = 0x06; break; case DASD_ECKD_CCW_ERASE:
data->length = reclen;
data->auxiliary.last_bytes_used = 0x1;
data->operation.operation = 0x0b; break; default:
DBF_DEV_EVENT(DBF_ERR, device, "unknown locate record " "opcode 0x%x", cmd);
}
set_ch_t(&data->seek_addr,
trk / private->rdc_data.trk_per_cyl,
trk % private->rdc_data.trk_per_cyl);
data->search_arg.cyl = data->seek_addr.cyl;
data->search_arg.head = data->seek_addr.head;
data->search_arg.record = rec_on_trk;
}
/* * Returns 1 if the block is one of the special blocks that needs * to get read/written with the KD variant of the command. * That is DASD_ECKD_READ_KD_MT instead of DASD_ECKD_READ_MT and * DASD_ECKD_WRITE_KD_MT instead of DASD_ECKD_WRITE_MT. * Luckily the KD variants differ only by one bit (0x08) from the * normal variant. So don't wonder about code like: * if (dasd_eckd_cdl_special(blk_per_trk, recid)) * ccw->cmd_code |= 0x8;
*/ staticinlineint
dasd_eckd_cdl_special(int blk_per_trk, int recid)
{ if (recid < 3) return 1; if (recid < blk_per_trk) return 0; if (recid < 2 * blk_per_trk) return 1; return 0;
}
/* * Returns the record size for the special blocks of the cdl format. * Only returns something useful if dasd_eckd_cdl_special is true * for the recid.
*/ staticinlineint
dasd_eckd_cdl_reclen(int recid)
{ if (recid < 3) return sizes_trk0[recid]; return LABEL_SIZE;
} /* create unique id from private structure. */ staticvoid create_uid(struct dasd_conf *conf, struct dasd_uid *uid)
{ int count;
/* * compare device UID with data of a given dasd_eckd_private structure * return 0 for match
*/ staticint dasd_eckd_compare_path_uid(struct dasd_device *device, struct dasd_conf *path_conf)
{ struct dasd_uid device_uid; struct dasd_uid path_uid;
/* * Wakeup helper for read_conf * if the cqr is not done and needs some error recovery * the buffer has to be re-initialized with the EBCDIC "V1.0" * to show support for virtual device SNEQ
*/ staticvoid read_conf_cb(struct dasd_ccw_req *cqr, void *data)
{ struct ccw1 *ccw;
__u8 *rcd_buffer;
/* * 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) {
ret = -EOPNOTSUPP; goto out_error;
}
rcd_buf = kzalloc(DASD_ECKD_RCD_DATA_SIZE, GFP_KERNEL | GFP_DMA); if (!rcd_buf) {
ret = -ENOMEM; goto out_error;
}
cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 1 /* RCD */,
0, /* use rcd_buf as data ara */
device, NULL); if (IS_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);
cqr->callback = read_conf_cb;
ret = dasd_sleep_on(cqr); /* * on success we update the user input parms
*/
dasd_sfree_request(cqr, cqr->memdev); if (ret) goto out_error;
/* * path handling and read_conf allocate data * free it before replacing the pointer * also replace the old private->conf_data pointer * with the new one if this points to the same data
*/
cdp = device->path[chp].conf_data; if (private->conf.data == cdp) {
private->conf.data = (void *)conf_data;
dasd_eckd_identify_conf_parts(&private->conf);
}
ccw_device_get_schid(device->cdev, &sch_id);
device->path[chp].conf_data = conf_data;
device->path[chp].cssid = sch_id.cssid;
device->path[chp].ssid = sch_id.ssid;
chp_desc = ccw_device_get_chp_desc(device->cdev, chp); if (chp_desc)
device->path[chp].chpid = chp_desc->chpid;
kfree(chp_desc);
kfree(cdp);
}
path_conf.data = conf_data;
path_conf.len = DASD_ECKD_RCD_DATA_SIZE; if (dasd_eckd_identify_conf_parts(&path_conf)) return 1;
if (dasd_eckd_compare_path_uid(device, &path_conf)) {
dasd_eckd_get_uid_string(&path_conf, print_path_uid);
dasd_eckd_get_uid_string(&private->conf, print_device_uid);
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 1;
}
return 0;
}
staticint dasd_eckd_read_conf(struct dasd_device *device)
{ void *conf_data; int conf_len, conf_data_saved; int rc, path_err, pos;
__u8 lpm, opm; struct dasd_eckd_private *private;
private = device->private;
opm = ccw_device_get_path_mask(device->cdev);
conf_data_saved = 0;
path_err = 0; /* get configuration data per operational path */ for (lpm = 0x80; lpm; lpm>>= 1) { 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 */
dasd_path_add_opm(device, opm); continue; /* no error */
} /* save first valid configuration data */ if (!conf_data_saved) { /* initially clear previously stored conf_data */
dasd_eckd_clear_conf_data(device);
private->conf.data = conf_data;
private->conf.len = conf_len; if (dasd_eckd_identify_conf_parts(&private->conf)) {
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++;
} elseif (dasd_eckd_check_cabling(device, conf_data, lpm)) {
dasd_path_add_cablepm(device, lpm);
path_err = -EINVAL;
kfree(conf_data); continue;
}
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 = (u32)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;
}
/* * 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);
data = container_of(work, struct pe_handler_work_data, worker);
device = data->device;
/* delay path verification until device was resumed */ if (test_bit(DASD_FLAG_SUSPENDED, &device->flags)) {
schedule_work(work); return;
} /* check if path verification already running and delay if so */ if (test_and_set_bit(DASD_FLAG_PATH_VERIFY, &device->flags)) {
schedule_work(work); return;
}
if (data->tbvpm)
dasd_eckd_path_available_action(device, data); if (data->fcsecpm)
dasd_eckd_read_fc_security(device);
clear_bit(DASD_FLAG_PATH_VERIFY, &device->flags);
dasd_put_device(device); if (data->isglobal)
mutex_unlock(&dasd_pe_handler_mutex); else
kfree(data);
}
/* Prepare for Read Subsystem Data */
prssdp = (struct dasd_psf_prssd_data *) cqr->data;
memset(prssdp, 0, sizeof(struct dasd_psf_prssd_data));
prssdp->order = PSF_ORDER_PRSSD;
prssdp->suborder = 0x41; /* Read Feature Codes */ /* all other bytes of prssdp must be zero */
/* This command cannot be executed on an alias device */ if (private->uid.type == UA_BASE_PAV_ALIAS ||
private->uid.type == UA_HYPER_PAV_ALIAS) return 0;
/* * This value represents the total amount of available space. As more space is * allocated by ESE volumes, this value will decrease. * The data for this value is therefore updated on any call.
*/ staticint dasd_eckd_space_configured(struct dasd_device *device)
{ struct dasd_eckd_private *private = device->private; int rc;
rc = dasd_eckd_read_vol_info(device);
return rc ? : private->vsq.space_configured;
}
/* * The value of space allocated by an ESE volume may have changed and is * therefore updated on any call.
*/ staticint dasd_eckd_space_allocated(struct dasd_device *device)
{ struct dasd_eckd_private *private = device->private; int rc;
data = container_of(work, struct ext_pool_exhaust_work_data, worker);
device = data->device;
base = data->base;
if (!base)
base = device; if (dasd_eckd_space_configured(base) != 0) {
dasd_generic_space_avail(device);
} else {
dev_warn(&device->cdev->dev, "No space left in the extent pool\n");
DBF_DEV_EVENT(DBF_WARNING, device, "%s", "out of space");
}
/* This command cannot be executed on an alias device */ if (private->uid.type == UA_BASE_PAV_ALIAS ||
private->uid.type == UA_HYPER_PAV_ALIAS) return 0;
cqr->buildclk = get_tod_clock();
cqr->status = DASD_CQR_FILLED;
cqr->startdev = device;
cqr->memdev = device;
cqr->block = NULL;
cqr->retries = 256;
cqr->expires = device->default_expires * HZ; /* The command might not be supported. Suppress the error output */
__set_bit(DASD_CQR_SUPPRESS_CR, &cqr->flags);
rc = dasd_sleep_on_interruptible(cqr); if (rc == 0) {
dasd_eckd_cpy_ext_pool_data(device, lcq);
} else {
DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "Reading the logical configuration failed with rc=%d", rc);
}
dasd_sfree_request(cqr, cqr->memdev);
return rc;
}
/* * Depending on the device type, the extent size is specified either as * cylinders per extent (CKD) or size per extent (FBA) * A 1GB size corresponds to 1113cyl, and 16MB to 21cyl.
*/ staticint dasd_eckd_ext_size(struct dasd_device *device)
{ struct dasd_eckd_private *private = device->private; struct dasd_ext_pool_sum eps = private->eps;
if (!eps.flags.extent_size_valid) return 0; if (eps.extent_size.size_1G) return 1113; if (eps.extent_size.size_16M) return 21;
/* * Perform Subsystem Function. * It is necessary to trigger CIO for channel revalidation since this * call might change behaviour of DASD devices.
*/ staticint
dasd_eckd_psf_ssc(struct dasd_device *device, int enable_pav, unsignedlong flags)
{ struct dasd_ccw_req *cqr; int rc;
cqr = dasd_eckd_build_psf_ssc(device, enable_pav); if (IS_ERR(cqr)) return PTR_ERR(cqr);
/* * set flags e.g. turn on failfast, to prevent blocking * the calling function should handle failed requests
*/
cqr->flags |= flags;
rc = dasd_sleep_on(cqr); if (!rc) /* trigger CIO to reprobe devices */
css_schedule_reprobe(); elseif (cqr->intrc == -EAGAIN)
rc = -EAGAIN;
/* may be requested feature is not available on server,
* therefore just report error and go ahead */
DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "PSF-SSC for SSID %04x " "returned rc=%d", private->uid.ssid, rc); return rc;
}
/* * worker to do a validate server in case of a lost pathgroup
*/ staticvoid dasd_eckd_do_validate_server(struct work_struct *work)
{ struct dasd_device *device = container_of(work, struct dasd_device,
kick_validate); unsignedlong flags = 0;
set_bit(DASD_CQR_FLAGS_FAILFAST, &flags); if (dasd_eckd_validate_server(device, flags)
== -EAGAIN) { /* schedule worker again if failed */
schedule_work(&device->kick_validate); return;
}
dasd_put_device(device);
}
staticvoid dasd_eckd_kick_validate_server(struct dasd_device *device)
{
dasd_get_device(device); /* exit if device not online or in offline processing */ if (test_bit(DASD_FLAG_OFFLINE, &device->flags) ||
device->state < DASD_STATE_ONLINE) {
dasd_put_device(device); return;
} /* queue call to do_validate_server to the kernel event daemon. */ if (!schedule_work(&device->kick_validate))
dasd_put_device(device);
}
/* * return if the device is the copy relation primary if a copy relation is active
*/ staticint dasd_device_is_primary(struct dasd_device *device)
{ if (!device->copy) return 1;
if (device->copy->active->device == device) return 1;
/* * Check device characteristics. * If the device is accessible using ECKD discipline, the device is enabled.
*/ staticint
dasd_eckd_check_characteristics(struct dasd_device *device)
{ struct dasd_eckd_private *private = device->private; int rc, i; int readonly; unsignedlong value;
/* setup work queue for validate server*/
INIT_WORK(&device->kick_validate, dasd_eckd_do_validate_server); /* setup work queue for summary unit check */
INIT_WORK(&device->suc_work, dasd_alias_handle_summary_unit_check);
if (!ccw_device_is_pathgroup(device->cdev)) {
dev_warn(&device->cdev->dev, "A channel path group could not be established\n"); return -EIO;
} if (!ccw_device_is_multipath(device->cdev)) {
dev_info(&device->cdev->dev, "The DASD is not operating in multipath mode\n");
} if (!private) { private = kzalloc(sizeof(*private), GFP_KERNEL | GFP_DMA); if (!private) {
dev_warn(&device->cdev->dev, "Allocating memory for private DASD data " "failed\n"); return -ENOMEM;
}
device->private = private;
} else {
memset(private, 0, sizeof(*private));
} /* Invalidate status of initial analysis. */
private->init_cqr_status = -1; /* Set default cache operations. */
private->attrib.operation = DASD_NORMAL_CACHE;
private->attrib.nr_cyl = 0;
/* Read Configuration Data */
rc = dasd_eckd_read_conf(device); if (rc) goto out_err1;
if (private->conf.gneq) {
value = 1; for (i = 0; i < private->conf.gneq->timeout.value; i++)
value = 10 * value;
value = value * private->conf.gneq->timeout.number; /* do not accept useless values */ if (value != 0 && value <= DASD_EXPIRES_MAX)
device->default_expires = value;
}
/* Read Volume Information */
dasd_eckd_read_vol_info(device);
/* Read Extent Pool Information */
dasd_eckd_read_ext_pool_info(device);
if ((device->features & DASD_FEATURE_USERAW) &&
!(private->rdc_data.facilities.RT_in_LR)) {
dev_err(&device->cdev->dev, "The storage server does not " "support raw-track access\n");
rc = -EINVAL; goto out_err3;
}
/* find the valid cylinder size */ if (private->rdc_data.no_cyl == LV_COMPAT_CYL &&
private->rdc_data.long_no_cyl)
private->real_cyl = private->rdc_data.long_no_cyl; else
private->real_cyl = private->rdc_data.no_cyl;
private->fcx_max_data = get_fcx_max_data(device);
readonly = dasd_device_is_ro(device); if (readonly)
set_bit(DASD_FLAG_DEVICE_RO, &device->flags);
/* differentiate between 'no record found' and any other error */ staticint dasd_eckd_analysis_evaluation(struct dasd_ccw_req *init_cqr)
{ char *sense; if (init_cqr->status == DASD_CQR_DONE) return INIT_CQR_OK; elseif (init_cqr->status == DASD_CQR_NEED_ERP ||
init_cqr->status == DASD_CQR_FAILED) {
sense = dasd_get_sense(&init_cqr->irb); if (sense && (sense[1] & SNS1_NO_REC_FOUND)) return INIT_CQR_UNFORMATTED; else return INIT_CQR_ERROR;
} else return INIT_CQR_ERROR;
}
/* * This is the callback function for the init_analysis cqr. It saves * the status of the initial analysis ccw before it frees it and kicks * the device to continue the startup sequence. This will call * dasd_eckd_do_analysis again (if the devices has not been marked * for deletion in the meantime).
*/ staticvoid dasd_eckd_analysis_callback(struct dasd_ccw_req *init_cqr, void *data)
{ struct dasd_device *device = init_cqr->startdev; struct dasd_eckd_private *private = device->private;
init_cqr = dasd_eckd_analysis_ccw(block->base); if (IS_ERR(init_cqr)) return PTR_ERR(init_cqr);
init_cqr->callback = dasd_eckd_analysis_callback;
init_cqr->callback_data = NULL;
init_cqr->expires = 5*HZ; /* first try without ERP, so we can later handle unformatted * devices as special case
*/
clear_bit(DASD_CQR_FLAGS_USE_ERP, &init_cqr->flags);
init_cqr->retries = 0;
dasd_add_request_head(init_cqr); return -EAGAIN;
}
status = private->init_cqr_status;
private->init_cqr_status = -1; if (status == INIT_CQR_ERROR) { /* try again, this time with full ERP */
init_cqr = dasd_eckd_analysis_ccw(device);
dasd_sleep_on(init_cqr);
status = dasd_eckd_analysis_evaluation(init_cqr);
dasd_sfree_request(init_cqr, device);
}
if (status == INIT_CQR_UNFORMATTED) {
dev_warn(&device->cdev->dev, "The DASD is not formatted\n"); return -EMEDIUMTYPE;
} elseif (status == INIT_CQR_ERROR) {
dev_err(&device->cdev->dev, "Detecting the DASD disk layout failed because " "of an I/O error\n"); return -EIO;
}
private->uses_cdl = 1; /* Check Track 0 for Compatible Disk Layout */
count_area = NULL; for (i = 0; i < 3; i++) { if (private->count_area[i].kl != 4 ||
private->count_area[i].dl != dasd_eckd_cdl_reclen(i) - 4 ||
private->count_area[i].cyl != 0 ||
private->count_area[i].head != count_area_head[i] ||
private->count_area[i].record != count_area_rec[i]) {
private->uses_cdl = 0; break;
}
} if (i == 3)
count_area = &private->count_area[3];
if (private->uses_cdl == 0) { for (i = 0; i < 5; i++) { if ((private->count_area[i].kl != 0) ||
(private->count_area[i].dl !=
private->count_area[0].dl) ||
private->count_area[i].cyl != 0 ||
private->count_area[i].head != count_area_head[i] ||
private->count_area[i].record != count_area_rec[i]) break;
} if (i == 5)
count_area = &private->count_area[0];
} else { if (private->count_area[3].record == 1)
dev_warn(&device->cdev->dev, "Track 0 has no records following the VTOC\n");
}
if (count_area != NULL && count_area->kl == 0) { /* we found nothing violating our disk layout */ if (dasd_check_blocksize(count_area->dl) == 0)
block->bp_block = count_area->dl;
} if (block->bp_block == 0) {
dev_warn(&device->cdev->dev, "The disk layout of the DASD is not supported\n"); return -EMEDIUMTYPE;
}
block->s2b_shift = 0; /* bits to shift 512 to get a block */ for (sb = 512; sb < block->bp_block; sb = sb << 1)
block->s2b_shift++;
/* * fdata->intensity is a bit string that tells us what to do: * Bit 0: write record zero * Bit 1: write home address, currently not supported * Bit 2: invalidate tracks * Bit 3: use OS/390 compatible disk layout (cdl) * Bit 4: do not allow storage subsystem to modify record zero * Only some bit combinations do make sense.
*/ if (fdata->intensity & 0x10) {
r0_perm = 0;
intensity = fdata->intensity & ~0x10;
} else {
r0_perm = 1;
intensity = fdata->intensity;
}
/* * Wrapper function to build a CCW request depending on input data
*/ staticstruct dasd_ccw_req *
dasd_eckd_format_build_ccw_req(struct dasd_device *base, struct format_data_t *fdata, int enable_pav, int tpm, struct eckd_count *fmt_buffer, int rpt)
{ struct dasd_ccw_req *ccw_req;
if (fdata->start_unit >=
(private->real_cyl * private->rdc_data.trk_per_cyl)) {
dev_warn(&base->cdev->dev, "Start track number %u used in formatting is too big\n",
fdata->start_unit); return -EINVAL;
} if (fdata->stop_unit >=
(private->real_cyl * private->rdc_data.trk_per_cyl)) {
dev_warn(&base->cdev->dev, "Stop track number %u used in formatting is too big\n",
fdata->stop_unit); return -EINVAL;
} if (fdata->start_unit > fdata->stop_unit) {
dev_warn(&base->cdev->dev, "Start track %u used in formatting exceeds end track\n",
fdata->start_unit); return -EINVAL;
} if (dasd_check_blocksize(fdata->blksize) != 0) {
dev_warn(&base->cdev->dev, "The DASD cannot be formatted with block size %u\n",
fdata->blksize); return -EINVAL;
} return 0;
}
/* * This function will process format_data originally coming from an IOCTL
*/ staticint dasd_eckd_format_process_data(struct dasd_device *base, struct format_data_t *fdata, int enable_pav, int tpm, struct eckd_count *fmt_buffer, int rpt, struct irb *irb)
{ struct dasd_eckd_private *private = base->private; struct dasd_ccw_req *cqr, *n; struct list_head format_queue; struct dasd_device *device; char *sense = NULL; int old_start, old_stop, format_step; int step, retry; int rc;
rc = dasd_eckd_format_sanity_checks(base, fdata); if (rc) return rc;
if (cqr->status == DASD_CQR_FAILED) { /* * Only get sense data if called by format * check
*/ if (fmt_buffer && irb) {
sense = dasd_get_sense(&cqr->irb);
memcpy(irb, &cqr->irb, sizeof(*irb));
}
rc = -EIO;
}
list_del_init(&cqr->blocklist);
dasd_ffree_request(cqr, device);
private->count--;
}
if (rc && rc != -EIO) goto out; if (rc == -EIO) { /* * In case fewer than the expected records are on the * track, we will most likely get a 'No Record Found' * error (in command mode) or a 'File Protected' error * (in transport mode). Those particular cases shouldn't * pass the -EIO to the IOCTL, therefore reset the rc * and continue.
*/ if (sense &&
(sense[1] & SNS1_NO_REC_FOUND ||
sense[1] & SNS1_FILE_PROTECTED))
retry = 1; else goto out;
}
spin_lock_irqsave(&block->format_lock, flags); if (cqr->trkcount != atomic_read(&block->trkcount)) { /* * The number of formatted tracks has changed after request * start and we can not tell if the current track was involved. * To avoid data corruption treat it as if the current track is * involved
*/
rc = true; goto out;
}
list_for_each_entry(format, &block->format_list, list) { if (format->track == to_format->track) {
rc = true; goto out;
}
}
list_add_tail(&to_format->list, &block->format_list);
if (curr_trk < first_trk || curr_trk > last_trk) {
DBF_DEV_EVENT(DBF_WARNING, startdev, "ESE error track %llu not within range %llu - %llu\n",
curr_trk, first_trk, last_trk); return ERR_PTR(-EINVAL);
}
format->track = curr_trk; /* test if track is already in formatting by another thread */ if (test_and_set_format_track(format, cqr)) { /* this is no real error so do not count down retries */
cqr->retries++; return ERR_PTR(-EEXIST);
}
rc = dasd_eckd_format_sanity_checks(base, &fdata); if (rc) return ERR_PTR(-EINVAL);
/* * We're building the request with PAV disabled as we're reusing * the former startdev.
*/
fcqr = dasd_eckd_build_format(base, startdev, &fdata, 0); if (IS_ERR(fcqr)) return fcqr;
/* * When data is read from an unformatted area of an ESE volume, this function * returns zeroed data and thereby mimics a read of zero data. * * The first unformatted track is the one that got the NRF error, the address is * encoded in the sense data. * * All tracks before have returned valid data and should not be touched. * All tracks after the unformatted track might be formatted or not. This is * currently not known, remember the processed data and return the remainder of * the request to the blocklayer in __dasd_cleanup_cqr().
*/ staticint dasd_eckd_ese_read(struct dasd_ccw_req *cqr, struct irb *irb)
{ struct dasd_eckd_private *private;
sector_t first_trk, last_trk;
sector_t first_blk, last_blk; unsignedint blksize, off; unsignedint recs_per_trk; struct dasd_device *base; struct req_iterator iter; struct dasd_block *block; unsignedint skip_block; unsignedint blk_count; struct request *req; struct bio_vec bv;
sector_t curr_trk;
sector_t end_blk; char *dst; int rc;
/* sanity check if the current track from sense data is valid */ if (curr_trk < first_trk || curr_trk > last_trk) {
DBF_DEV_EVENT(DBF_WARNING, base, "ESE error track %llu not within range %llu - %llu\n",
curr_trk, first_trk, last_trk); return -EINVAL;
}
/* * if not the first track got the NRF error we have to skip over valid * blocks
*/ if (curr_trk != first_trk)
skip_block = curr_trk * recs_per_trk - first_blk;
/* we have no information beyond the current track */
end_blk = (curr_trk + 1) * recs_per_trk;
/* * Helper function to count consecutive records of a single track.
*/ staticint dasd_eckd_count_records(struct eckd_count *fmt_buffer, int start, int max)
{ int head; int i;
head = fmt_buffer[start].head;
/* * There are 3 conditions where we stop counting: * - if data reoccurs (same head and record may reoccur), which may * happen due to the way DASD_ECKD_CCW_READ_COUNT works * - when the head changes, because we're iterating over several tracks * then (DASD_ECKD_CCW_READ_COUNT_MT) * - when we've reached the end of sensible data in the buffer (the * record will be 0 then)
*/ for (i = start; i < max; i++) { if (i > start) { if ((fmt_buffer[i].head == head &&
fmt_buffer[i].record == 1) ||
fmt_buffer[i].head != head ||
fmt_buffer[i].record == 0) break;
}
}
return i - start;
}
/* * Evaluate a given range of tracks. Data like number of records, blocksize, * record ids, and key length are compared with expected data. * * If a mismatch occurs, the corresponding error bit is set, as well as * additional information, depending on the error.
*/ staticvoid dasd_eckd_format_evaluate_tracks(struct eckd_count *fmt_buffer, struct format_check_t *cdata, int rpt_max, int rpt_exp, int trk_per_cyl, int tpm)
{ struct ch_t geo; int max_entries; int count = 0; int trkcount; int blksize; int pos = 0; int i, j; int kl;
for (i = cdata->expect.start_unit; i <= cdata->expect.stop_unit; i++) { /* Calculate the correct next starting position in the buffer */ if (tpm) { while (fmt_buffer[pos].record == 0 &&
fmt_buffer[pos].dl == 0) { if (pos++ > max_entries) break;
}
} else { if (i != cdata->expect.start_unit)
pos += rpt_max - count;
}
/* Calculate the expected geo values for the current track */
set_ch_t(&geo, i / trk_per_cyl, i % trk_per_cyl);
/* Count and check number of records */
count = dasd_eckd_count_records(fmt_buffer, pos, pos + rpt_max);
if (count < rpt_exp) {
cdata->result = DASD_FMT_ERR_TOO_FEW_RECORDS; break;
} if (count > rpt_exp) {
cdata->result = DASD_FMT_ERR_TOO_MANY_RECORDS; break;
}
/* * Check the format of a range of tracks of a DASD.
*/ staticint dasd_eckd_check_device_format(struct dasd_device *base, struct format_check_t *cdata, int enable_pav)
{ struct dasd_eckd_private *private = base->private; struct eckd_count *fmt_buffer; struct irb irb; int rpt_max, rpt_exp; int fmt_buffer_size; int trk_per_cyl; int trkcount; int tpm = 0; int rc;
trk_per_cyl = private->rdc_data.trk_per_cyl;
/* Get maximum and expected amount of records per track */
rpt_max = recs_per_track(&private->rdc_data, 0, 512) + 1;
rpt_exp = recs_per_track(&private->rdc_data, 0, cdata->expect.blksize);
fmt_buffer = kzalloc(fmt_buffer_size, GFP_KERNEL | GFP_DMA); if (!fmt_buffer) return -ENOMEM;
/* * A certain FICON feature subset is needed to operate in transport * mode. Additionally, the support for transport mode is implicitly * checked by comparing the buffer size with fcx_max_data. As long as * the buffer size is smaller we can operate in transport mode and * process multiple tracks. If not, only one track at once is being * processed using command mode.
*/ if ((private->features.feature[40] & 0x04) &&
fmt_buffer_size <= private->fcx_max_data)
tpm = 1;
rc = dasd_eckd_format_process_data(base, &cdata->expect, enable_pav,
tpm, fmt_buffer, rpt_max, &irb); if (rc && rc != -EIO) goto out; if (rc == -EIO) { /* * If our first attempt with transport mode enabled comes back * with an incorrect length error, we're going to retry the * check with command mode.
*/ if (tpm && scsw_cstat(&irb.scsw) == 0x40) {
tpm = 0;
rc = dasd_eckd_format_process_data(base, &cdata->expect,
enable_pav, tpm,
fmt_buffer, rpt_max,
&irb); if (rc) goto out;
} else { goto out;
}
}
switch (cdev->id.cu_type) { case 0x3990: case 0x2105: case 0x2107: case 0x1750: return dasd_3990_erp_action; case 0x9343: case 0x3880: default: return dasd_default_erp_action;
}
}
/* first of all check for state change pending interrupt */
mask = DEV_STAT_ATTENTION | DEV_STAT_DEV_END | DEV_STAT_UNIT_EXCEP; if ((scsw_dstat(&irb->scsw) & mask) == mask) { /* * for alias only, not in offline processing * and only if not suspended
*/ if (!device->block && private->lcu &&
device->state == DASD_STATE_ONLINE &&
!test_bit(DASD_FLAG_OFFLINE, &device->flags) &&
!test_bit(DASD_FLAG_SUSPENDED, &device->flags)) { /* schedule worker to reload device */
dasd_reload_device(device);
}
dasd_generic_handle_state_change(device); return;
}
sense = dasd_get_sense(irb); if (!sense) return;
/* summary unit check */ if ((sense[27] & DASD_SENSE_BIT_0) && (sense[7] == 0x0D) &&
(scsw_dstat(&irb->scsw) & DEV_STAT_UNIT_CHECK)) { if (test_and_set_bit(DASD_FLAG_SUC, &device->flags)) {
DBF_DEV_EVENT(DBF_WARNING, device, "%s", "eckd suc: device already notified"); return;
}
sense = dasd_get_sense(irb); if (!sense) {
DBF_DEV_EVENT(DBF_WARNING, device, "%s", "eckd suc: no reason code available");
clear_bit(DASD_FLAG_SUC, &device->flags); return;
}
private->suc_reason = sense[8];
DBF_DEV_EVENT(DBF_NOTICE, device, "%s %x", "eckd handle summary unit check: reason",
private->suc_reason);
dasd_get_device(device); if (!schedule_work(&device->suc_work))
dasd_put_device(device);
return;
}
/* service information message SIM */ if (!cqr && !(sense[27] & DASD_SENSE_BIT_0) &&
((sense[6] & DASD_SIM_SENSE) == DASD_SIM_SENSE)) {
dasd_3990_erp_handle_sim(device, sense); return;
}
/* loss of device reservation is handled via base devices only * as alias devices may be used with several bases
*/ if (device->block && (sense[27] & DASD_SENSE_BIT_0) &&
(sense[7] == 0x3F) &&
(scsw_dstat(&irb->scsw) & DEV_STAT_UNIT_CHECK) &&
test_bit(DASD_FLAG_IS_RESERVED, &device->flags)) { if (device->features & DASD_FEATURE_FAILONSLCK)
set_bit(DASD_FLAG_LOCK_STOLEN, &device->flags);
clear_bit(DASD_FLAG_IS_RESERVED, &device->flags);
dev_err(&device->cdev->dev, "The device reservation was lost\n");
}
}
if (first_trk >= trks_per_vol) {
dev_warn(&device->cdev->dev, "Start track number %u used in the space release command is too big\n",
first_trk);
rc = -EINVAL;
} elseif (last_trk >= trks_per_vol) {
dev_warn(&device->cdev->dev, "Stop track number %u used in the space release command is too big\n",
last_trk);
rc = -EINVAL;
} elseif (first_trk > last_trk) {
dev_warn(&device->cdev->dev, "Start track %u used in the space release command exceeds the end track\n",
first_trk);
rc = -EINVAL;
} return rc;
}
/* * Helper function to count the amount of involved extents within a given range * with extent alignment in mind.
*/ staticint count_exts(unsignedint from, unsignedint to, int trks_per_ext)
{ int cur_pos = 0; int count = 0; int tmp;
if (from == to) return 1;
/* Count first partial extent */ if (from % trks_per_ext != 0) {
tmp = from + trks_per_ext - (from % trks_per_ext) - 1; if (tmp > to)
tmp = to;
cur_pos = tmp - from + 1;
count++;
} /* Count full extents */ if (to - (from + cur_pos) + 1 >= trks_per_ext) {
tmp = to - ((to - trks_per_ext + 1) % trks_per_ext);
count += (tmp - (from + cur_pos) + 1) / trks_per_ext;
cur_pos = tmp;
} /* Count last partial extent */ if (cur_pos < to)
count++;
return count;
}
staticint dasd_in_copy_relation(struct dasd_device *device)
{ struct dasd_pprc_data_sc4 *temp; int rc;
if (!dasd_eckd_pprc_enabled(device)) return 0;
temp = kzalloc(sizeof(*temp), GFP_KERNEL); if (!temp) return -ENOMEM;
rc = dasd_eckd_query_pprc_status(device, temp); if (!rc)
rc = temp->dev_info[0].state;
kfree(temp); return rc;
}
/* * Release allocated space for a given range or an entire volume.
*/ staticstruct dasd_ccw_req *
dasd_eckd_dso_ras(struct dasd_device *device, struct dasd_block *block, struct request *req, unsignedint first_trk, unsignedint last_trk, int by_extent)
{ struct dasd_eckd_private *private = device->private; struct dasd_dso_ras_ext_range *ras_range; struct dasd_rssd_features *features; struct dasd_dso_ras_data *ras_data;
u16 heads, beg_head, end_head; int cur_to_trk, cur_from_trk; struct dasd_ccw_req *cqr;
u32 beg_cyl, end_cyl; int copy_relation; struct ccw1 *ccw; int trks_per_ext;
size_t ras_size;
size_t size; int nr_exts; void *rq; int i;
if (dasd_eckd_ras_sanity_checks(device, first_trk, last_trk)) return ERR_PTR(-EINVAL);
copy_relation = dasd_in_copy_relation(device); if (copy_relation < 0) return ERR_PTR(copy_relation);
ras_data->order = DSO_ORDER_RAS;
ras_data->flags.vol_type = 0; /* CKD volume */ /* Release specified extents or entire volume */
ras_data->op_flags.by_extent = by_extent; /* * This bit guarantees initialisation of tracks within an extent that is * not fully specified, but is only supported with a certain feature * subset and for devices not in a copy relation.
*/ if (features->feature[56] & 0x01 && !copy_relation)
ras_data->op_flags.guarantee_init = 1;
/* Track based I/O needs IDAWs for each page, and not just for * 64 bit addresses. We need additional idals for pages * that get filled from two tracks, so we use the number * of records as upper limit.
*/
cidaw = last_rec - first_rec + 1;
trkcount = last_trk - first_trk + 1;
/* 1x prefix + one read/write ccw per track */
cplength = 1 + trkcount;
/* Allocate the ccw request. */
cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, cplength, datasize,
startdev, blk_mq_rq_to_pdu(req)); if (IS_ERR(cqr)) return cqr;
ccw = cqr->cpaddr; /* transfer length factor: how many bytes to read from the last track */ if (first_trk == last_trk)
tlf = last_offs - first_offs + 1; else
tlf = last_offs + 1;
tlf *= blksize;
if (prefix_LRE(ccw++, cqr->data, first_trk,
last_trk, cmd, basedev, startdev,
1 /* format */, first_offs + 1,
trkcount, blksize,
tlf) == -EAGAIN) { /* Clock not in sync and XRC is enabled. * Try again later.
*/
dasd_sfree_request(cqr, startdev); return ERR_PTR(-EAGAIN);
}
/* * The translation of request into ccw programs must meet the * following conditions: * - all idaws but the first and the last must address full pages * (or 2K blocks on 31-bit) * - the scope of a ccw and it's idal ends with the track boundaries
*/
idaws = (dma64_t *)(cqr->data + sizeof(struct PFX_eckd_data));
recid = first_rec;
new_track = 1;
end_idaw = 0;
len_to_track_end = 0;
idaw_dst = NULL;
idaw_len = 0;
rq_for_each_segment(bv, req, iter) {
dst = bvec_virt(&bv);
seg_len = bv.bv_len; while (seg_len) { if (new_track) {
trkid = recid;
recoffs = sector_div(trkid, blk_per_trk);
count_to_trk_end = blk_per_trk - recoffs;
count = min((last_rec - recid + 1),
(sector_t)count_to_trk_end);
len_to_track_end = count * blksize;
ccw[-1].flags |= CCW_FLAG_CC;
ccw->cmd_code = cmd;
ccw->count = len_to_track_end;
ccw->cda = virt_to_dma32(idaws);
ccw->flags = CCW_FLAG_IDA;
ccw++;
recid += count;
new_track = 0; /* first idaw for a ccw may start anywhere */ if (!idaw_dst)
idaw_dst = dst;
} /* If we start a new idaw, we must make sure that it * starts on an IDA_BLOCK_SIZE boundary. * If we continue an idaw, we must make sure that the * current segment begins where the so far accumulated * idaw ends
*/ if (!idaw_dst) { if ((unsignedlong)(dst) & (IDA_BLOCK_SIZE - 1)) {
dasd_sfree_request(cqr, startdev); return ERR_PTR(-ERANGE);
} else
idaw_dst = dst;
} if ((idaw_dst + idaw_len) != dst) {
dasd_sfree_request(cqr, startdev); return ERR_PTR(-ERANGE);
}
part_len = min(seg_len, len_to_track_end);
seg_len -= part_len;
dst += part_len;
idaw_len += part_len;
len_to_track_end -= part_len; /* collected memory area ends on an IDA_BLOCK border, * -> create an idaw * idal_create_words will handle cases where idaw_len * is larger then IDA_BLOCK_SIZE
*/ if (!((unsignedlong)(idaw_dst + idaw_len) & (IDA_BLOCK_SIZE - 1)))
end_idaw = 1; /* We also need to end the idaw at track end */ if (!len_to_track_end) {
new_track = 1;
end_idaw = 1;
} if (end_idaw) {
idaws = idal_create_words(idaws, idaw_dst,
idaw_len);
idaw_dst = NULL;
idaw_len = 0;
end_idaw = 0;
}
}
}
/* trackbased I/O needs address all memory via TIDAWs, * not just for 64 bit addresses. This allows us to map * each segment directly to one tidaw. * In the case of write requests, additional tidaws may * be needed when a segment crosses a track boundary.
*/
trkcount = last_trk - first_trk + 1;
ctidaw = 0;
rq_for_each_segment(bv, req, iter) {
++ctidaw;
} if (rq_data_dir(req) == WRITE)
ctidaw += (last_trk - first_trk);
/* transfer length factor: how many bytes to read from the last track */ if (first_trk == last_trk)
tlf = last_offs - first_offs + 1; else
tlf = last_offs + 1;
tlf *= blksize;
itcw = itcw_init(cqr->data, itcw_size, itcw_op, 0, ctidaw, 0); if (IS_ERR(itcw)) {
ret = -EINVAL; goto out_error;
}
cqr->cpaddr = itcw_get_tcw(itcw); if (prepare_itcw(itcw, first_trk, last_trk,
cmd, basedev, startdev,
first_offs + 1,
trkcount, blksize,
(last_rec - first_rec + 1) * blksize,
tlf, blk_per_trk) == -EAGAIN) { /* Clock not in sync and XRC is enabled. * Try again later.
*/
ret = -EAGAIN; goto out_error;
}
len_to_track_end = 0; /* * A tidaw can address 4k of memory, but must not cross page boundaries * We can let the block layer handle this by setting seg_boundary_mask * to page boundaries and max_segment_size to page size when setting up * the request queue. * For write requests, a TIDAW must not cross track boundaries, because * we have to set the CBC flag on the last tidaw for each track.
*/ if (rq_data_dir(req) == WRITE) {
new_track = 1;
recid = first_rec;
rq_for_each_segment(bv, req, iter) {
dst = bvec_virt(&bv);
seg_len = bv.bv_len; while (seg_len) { if (new_track) {
trkid = recid;
offs = sector_div(trkid, blk_per_trk);
count_to_trk_end = blk_per_trk - offs;
count = min((last_rec - recid + 1),
(sector_t)count_to_trk_end);
len_to_track_end = count * blksize;
recid += count;
new_track = 0;
}
part_len = min(seg_len, len_to_track_end);
seg_len -= part_len;
len_to_track_end -= part_len; /* We need to end the tidaw at track end */ if (!len_to_track_end) {
new_track = 1;
tidaw_flags = TIDAW_FLAGS_INSERT_CBC;
} else
tidaw_flags = 0;
last_tidaw = itcw_add_tidaw(itcw, tidaw_flags,
dst, part_len); if (IS_ERR(last_tidaw)) {
ret = -EINVAL; goto out_error;
}
dst += part_len;
}
}
} else {
rq_for_each_segment(bv, req, iter) {
dst = bvec_virt(&bv);
last_tidaw = itcw_add_tidaw(itcw, 0x00,
dst, bv.bv_len); if (IS_ERR(last_tidaw)) {
ret = -EINVAL; goto out_error;
}
}
}
last_tidaw->flags |= TIDAW_FLAGS_LAST;
last_tidaw->flags &= ~TIDAW_FLAGS_INSERT_CBC;
itcw_finalize(itcw);
/* Set flags to suppress output for expected errors */ if (dasd_eckd_is_ese(basedev)) {
set_bit(DASD_CQR_SUPPRESS_NRF, &cqr->flags);
set_bit(DASD_CQR_SUPPRESS_IT, &cqr->flags);
}
/* * Modify ccw/tcw in cqr so it can be started on a base device. * * Note that this is not enough to restart the cqr! * Either reset cqr->startdev as well (summary unit check handling) * or restart via separate cqr (as in ERP handling).
*/ void dasd_eckd_reset_ccw_to_base_io(struct dasd_ccw_req *cqr)
{ struct ccw1 *ccw; struct PFX_eckd_data *pfxdata; struct tcw *tcw; struct tccb *tccb; struct dcw *dcw;
/* * SECTION: ioctl functions for eckd devices.
*/
/* * Release device ioctl. * Buils a channel programm to releases a prior reserved * (see dasd_eckd_reserve) device.
*/ staticint
dasd_eckd_release(struct dasd_device *device)
{ struct dasd_ccw_req *cqr; int rc; struct ccw1 *ccw; int useglobal;
rc = dasd_sleep_on_immediatly(cqr); if (!rc)
clear_bit(DASD_FLAG_IS_RESERVED, &device->flags);
if (useglobal)
mutex_unlock(&dasd_reserve_mutex); else
dasd_sfree_request(cqr, cqr->memdev); return rc;
}
/* * Reserve device ioctl. * Options are set to 'synchronous wait for interrupt' and * 'timeout the request'. This leads to a terminate IO if * the interrupt is outstanding for a certain time.
*/ staticint
dasd_eckd_reserve(struct dasd_device *device)
{ struct dasd_ccw_req *cqr; int rc; struct ccw1 *ccw; int useglobal;
rc = dasd_sleep_on_immediatly(cqr); if (!rc)
set_bit(DASD_FLAG_IS_RESERVED, &device->flags);
if (useglobal)
mutex_unlock(&dasd_reserve_mutex); else
dasd_sfree_request(cqr, cqr->memdev); return rc;
}
/* * SNID - Sense Path Group ID * This ioctl may be used in situations where I/O is stalled due to * a reserve, so if the normal dasd_smalloc_request fails, we use the * preallocated dasd_reserve_req.
*/ staticint dasd_eckd_snid(struct dasd_device *device, void __user *argp)
{ struct dasd_ccw_req *cqr; int rc; struct ccw1 *ccw; int useglobal; struct dasd_snid_ioctl_data usrparm;
if (!capable(CAP_SYS_ADMIN)) return -EACCES;
if (copy_from_user(&usrparm, argp, sizeof(usrparm))) return -EFAULT;
/* * Set attributes (cache operations) * Stores the attributes for cache operation to be used in Define Extend (DE).
*/ staticint
dasd_eckd_set_attrib(struct dasd_device *device, void __user *argp)
{ struct dasd_eckd_private *private = device->private; struct attrib_data_t attrib;
if (!capable(CAP_SYS_ADMIN)) return -EACCES; if (!argp) return -EINVAL;
if (copy_from_user(&attrib, argp, sizeof(struct attrib_data_t))) return -EFAULT;
private->attrib = attrib;
dev_info(&device->cdev->dev, "The DASD cache mode was set to %x (%i cylinder prestage)\n",
private->attrib.operation, private->attrib.nr_cyl); return 0;
}
switch (cmd) { case BIODASDGATTR: return dasd_eckd_get_attrib(device, argp); case BIODASDSATTR: return dasd_eckd_set_attrib(device, argp); case BIODASDPSRD: return dasd_eckd_performance(device, argp); case BIODASDRLSE: return dasd_eckd_release(device); case BIODASDRSRV: return dasd_eckd_reserve(device); case BIODASDSLCK: return dasd_eckd_steal_lock(device); case BIODASDSNID: return dasd_eckd_snid(device, argp); case BIODASDSYMMIO: return dasd_symm_io(device, argp); default: return -ENOTTY;
}
}
/* * Dump the range of CCWs into 'page' buffer * and return number of printed chars.
*/ staticvoid
dasd_eckd_dump_ccw_range(struct dasd_device *device, struct ccw1 *from, struct ccw1 *to, char *page)
{ int len, count; char *datap;
len = 0; while (from <= to) {
len += sprintf(page + len, "CCW %px: %08X %08X DAT:",
from, ((int *) from)[0], ((int *) from)[1]);
/* get pointer to data (consider IDALs) */ if (from->flags & CCW_FLAG_IDA)
datap = dma64_to_virt(*((dma64_t *)dma32_to_virt(from->cda))); else
datap = dma32_to_virt(from->cda);
/* dump data (max 128 bytes) */ for (count = 0; count < from->count && count < 128; count++) { if (count % 32 == 0)
len += sprintf(page + len, "\n"); if (count % 8 == 0)
len += sprintf(page + len, " "); if (count % 4 == 0)
len += sprintf(page + len, " ");
len += sprintf(page + len, "%02x", datap[count]);
}
len += sprintf(page + len, "\n");
from++;
} if (len > 0)
dev_err(&device->cdev->dev, "%s", page);
}
/* * Print sense data and related channel program. * Parts are printed because printk buffer is only 1024 bytes.
*/ staticvoid dasd_eckd_dump_sense_ccw(struct dasd_device *device, struct dasd_ccw_req *req, struct irb *irb)
{ struct ccw1 *first, *last, *fail, *from, *to; struct device *dev; int len, sl, sct; char *page;
dev = &device->cdev->dev;
page = (char *) get_zeroed_page(GFP_ATOMIC); if (page == NULL) {
DBF_DEV_EVENT(DBF_WARNING, device, "%s", "No memory to dump sense data\n"); return;
} /* dump the sense data */
len = sprintf(page, "I/O status report:\n");
len += sprintf(page + len, "in req: %px CC:%02X FC:%02X AC:%02X SC:%02X DS:%02X CS:%02X RC:%d\n",
req, scsw_cc(&irb->scsw), scsw_fctl(&irb->scsw),
scsw_actl(&irb->scsw), scsw_stctl(&irb->scsw),
scsw_dstat(&irb->scsw), scsw_cstat(&irb->scsw),
req ? req->intrc : 0);
len += sprintf(page + len, "Failing CCW: %px\n",
dma32_to_virt(irb->scsw.cmd.cpa)); if (irb->esw.esw0.erw.cons) { for (sl = 0; sl < 4; sl++) {
len += sprintf(page + len, "Sense(hex) %2d-%2d:",
(8 * sl), ((8 * sl) + 7));
for (sct = 0; sct < 8; sct++) {
len += sprintf(page + len, " %02x",
irb->ecw[8 * sl + sct]);
}
len += sprintf(page + len, "\n");
}
if (irb->ecw[27] & DASD_SENSE_BIT_0) { /* 24 Byte Sense Data */
sprintf(page + len, "24 Byte: %x MSG %x, %s MSGb to SYSOP\n",
irb->ecw[7] >> 4, irb->ecw[7] & 0x0f,
irb->ecw[1] & 0x10 ? "" : "no");
} else { /* 32 Byte Sense Data */
sprintf(page + len, "32 Byte: Format: %x Exception class %x\n",
irb->ecw[6] & 0x0f, irb->ecw[22] >> 4);
}
} else {
sprintf(page + len, "SORRY - NO VALID SENSE AVAILABLE\n");
}
dev_err(dev, "%s", page);
if (req) { /* req == NULL for unsolicited interrupts */ /* dump the Channel Program (max 140 Bytes per line) */ /* Count CCW and print first CCWs (maximum 7) */
first = req->cpaddr; for (last = first; last->flags & (CCW_FLAG_CC | CCW_FLAG_DC); last++);
to = min(first + 6, last);
dev_err(dev, "Related CP in req: %px\n", req);
dasd_eckd_dump_ccw_range(device, first, to, page);
/* print failing CCW area (maximum 4) */ /* scsw->cda is either valid or zero */
from = ++to;
fail = dma32_to_virt(irb->scsw.cmd.cpa); /* failing CCW */ if (from < fail - 2) {
from = fail - 2; /* there is a gap - print header */
dev_err(dev, "......\n");
}
to = min(fail + 1, last);
dasd_eckd_dump_ccw_range(device, from, to, page + len);
/* print last CCWs (maximum 2) */
len = 0;
from = max(from, ++to); if (from < last - 1) {
from = last - 1; /* there is a gap - print header */
dev_err(dev, "......\n");
}
dasd_eckd_dump_ccw_range(device, from, last, page + len);
}
free_page((unsignedlong) page);
}
/* * Print sense data from a tcw.
*/ staticvoid dasd_eckd_dump_sense_tcw(struct dasd_device *device, struct dasd_ccw_req *req, struct irb *irb)
{ char *page; int len, sl, sct, residual; struct tsb *tsb;
u8 *sense, *rcq;
page = (char *) get_zeroed_page(GFP_ATOMIC); if (page == NULL) {
DBF_DEV_EVENT(DBF_WARNING, device, " %s", "No memory to dump sense data"); return;
} /* dump the sense data */
len = sprintf(page, "I/O status report:\n");
len += sprintf(page + len, "in req: %px CC:%02X FC:%02X AC:%02X SC:%02X DS:%02X " "CS:%02X fcxs:%02X schxs:%02X RC:%d\n",
req, scsw_cc(&irb->scsw), scsw_fctl(&irb->scsw),
scsw_actl(&irb->scsw), scsw_stctl(&irb->scsw),
scsw_dstat(&irb->scsw), scsw_cstat(&irb->scsw),
irb->scsw.tm.fcxs,
(irb->scsw.tm.ifob << 7) | irb->scsw.tm.sesq,
req ? req->intrc : 0);
len += sprintf(page + len, "Failing TCW: %px\n",
dma32_to_virt(irb->scsw.tm.tcw));
tsb = NULL;
sense = NULL; if (irb->scsw.tm.tcw && (irb->scsw.tm.fcxs & 0x01))
tsb = tcw_get_tsb(dma32_to_virt(irb->scsw.tm.tcw));
if (tsb) {
len += sprintf(page + len, "tsb->length %d\n", tsb->length);
len += sprintf(page + len, "tsb->flags %x\n", tsb->flags);
len += sprintf(page + len, "tsb->dcw_offset %d\n", tsb->dcw_offset);
len += sprintf(page + len, "tsb->count %d\n", tsb->count);
residual = tsb->count - 28;
len += sprintf(page + len, "residual %d\n", residual);
switch (tsb->flags & 0x07) { case 1: /* tsa_iostat */
len += sprintf(page + len, "tsb->tsa.iostat.dev_time %d\n",
tsb->tsa.iostat.dev_time);
len += sprintf(page + len, "tsb->tsa.iostat.def_time %d\n",
tsb->tsa.iostat.def_time);
len += sprintf(page + len, "tsb->tsa.iostat.queue_time %d\n",
tsb->tsa.iostat.queue_time);
len += sprintf(page + len, "tsb->tsa.iostat.dev_busy_time %d\n",
tsb->tsa.iostat.dev_busy_time);
len += sprintf(page + len, "tsb->tsa.iostat.dev_act_time %d\n",
tsb->tsa.iostat.dev_act_time);
sense = tsb->tsa.iostat.sense; break; case 2: /* ts_ddpc */
len += sprintf(page + len, "tsb->tsa.ddpc.rc %d\n",
tsb->tsa.ddpc.rc); for (sl = 0; sl < 2; sl++) {
len += sprintf(page + len, "tsb->tsa.ddpc.rcq %2d-%2d: ",
(8 * sl), ((8 * sl) + 7));
rcq = tsb->tsa.ddpc.rcq; for (sct = 0; sct < 8; sct++) {
len += sprintf(page + len, "%02x",
rcq[8 * sl + sct]);
}
len += sprintf(page + len, "\n");
}
sense = tsb->tsa.ddpc.sense; break; case 3: /* tsa_intrg */
len += sprintf(page + len, "tsb->tsa.intrg.: not supported yet\n"); break;
}
if (sense) { for (sl = 0; sl < 4; sl++) {
len += sprintf(page + len, "Sense(hex) %2d-%2d:",
(8 * sl), ((8 * sl) + 7)); for (sct = 0; sct < 8; sct++) {
len += sprintf(page + len, " %02x",
sense[8 * sl + sct]);
}
len += sprintf(page + len, "\n");
}
if (sense[27] & DASD_SENSE_BIT_0) { /* 24 Byte Sense Data */
sprintf(page + len, "24 Byte: %x MSG %x, %s MSGb to SYSOP\n",
sense[7] >> 4, sense[7] & 0x0f,
sense[1] & 0x10 ? "" : "no");
} else { /* 32 Byte Sense Data */
sprintf(page + len, "32 Byte: Format: %x Exception class %x\n",
sense[6] & 0x0f, sense[22] >> 4);
}
} else {
sprintf(page + len, "SORRY - NO VALID SENSE AVAILABLE\n");
}
} else {
sprintf(page + len, "SORRY - NO TSB DATA AVAILABLE\n");
}
dev_err(&device->cdev->dev, "%s", page);
free_page((unsignedlong) page);
}
/* * In some cases certain errors might be expected and * log messages shouldn't be written then. * Check if the according suppress bit is set.
*/ if (sense && (sense[1] & SNS1_INV_TRACK_FORMAT) &&
!(sense[2] & SNS2_ENV_DATA_PRESENT) &&
test_bit(DASD_CQR_SUPPRESS_IT, &req->flags)) return;
if (sense && sense[0] & SNS0_CMD_REJECT &&
test_bit(DASD_CQR_SUPPRESS_CR, &req->flags)) return;
if (sense && sense[1] & SNS1_NO_REC_FOUND &&
test_bit(DASD_CQR_SUPPRESS_NRF, &req->flags)) return;
if (scsw_cstat(&irb->scsw) == 0x40 &&
test_bit(DASD_CQR_SUPPRESS_IL, &req->flags)) return;
if (scsw_is_tm(&irb->scsw))
dasd_eckd_dump_sense_tcw(device, req, irb); else
dasd_eckd_dump_sense_ccw(device, req, irb);
}
/* Read Configuration Data */
rc = dasd_eckd_read_conf(device); if (rc) goto out_err;
dasd_eckd_read_fc_security(device);
rc = dasd_eckd_generate_uid(device); if (rc) goto out_err; /* * update unit address configuration and * add device to alias management
*/
dasd_alias_update_add_device(device);
dasd_eckd_get_uid(device, &uid);
if (old_base != uid.base_unit_addr) {
dasd_eckd_get_uid_string(&private->conf, print_uid);
dev_info(&device->cdev->dev, "An Alias device was reassigned to a new base device " "with UID: %s\n", print_uid);
} return 0;
cqr->lpm = lpum;
retry:
cqr->startdev = device;
cqr->memdev = device;
cqr->block = NULL;
cqr->expires = 10 * HZ;
set_bit(DASD_CQR_VERIFY_PATH, &cqr->flags); /* dasd_sleep_on_immediatly does not do complex error * recovery so clear erp flag and set retry counter to
* do basic erp */
clear_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags);
cqr->retries = 256;
/* Prepare for Read Subsystem Data */
prssdp = (struct dasd_psf_prssd_data *) cqr->data;
memset(prssdp, 0, sizeof(struct dasd_psf_prssd_data));
prssdp->order = PSF_ORDER_PRSSD;
prssdp->suborder = 0x03; /* Message Buffer */ /* all other bytes of prssdp must be zero */
cqr->buildclk = get_tod_clock();
cqr->status = DASD_CQR_FILLED;
rc = dasd_sleep_on_immediatly(cqr); if (rc == 0) {
prssdp = (struct dasd_psf_prssd_data *) cqr->data;
message_buf = (struct dasd_rssd_messages *)
(prssdp + 1);
memcpy(messages, message_buf, sizeof(struct dasd_rssd_messages));
} elseif (cqr->lpm) { /* * on z/VM we might not be able to do I/O on the requested path * but instead we get the required information on any path * so retry with open path mask
*/
cqr->lpm = 0; goto retry;
} else
DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "Reading messages failed with rc=%d\n"
, rc);
dasd_sfree_request(cqr, cqr->memdev); return rc;
}
/* Prepare for Read Subsystem Data */
prssdp = (struct dasd_psf_prssd_data *) cqr->data;
memset(prssdp, 0, sizeof(struct dasd_psf_prssd_data));
prssdp->order = PSF_ORDER_PRSSD;
prssdp->suborder = PSF_SUBORDER_QHA; /* query host access */ /* LSS and Volume that will be queried */
prssdp->lss = private->conf.ned->ID;
prssdp->volume = private->conf.ned->unit_addr; /* all other bytes of prssdp must be zero */
/* * The function will swap the role of a given copy pair. * During the swap operation the relation of the blockdevice is disconnected * from the old primary and connected to the new. * * IO is paused on the block queue before swap and may be resumed afterwards.
*/ staticint dasd_eckd_copy_pair_swap(struct dasd_device *device, char *prim_busid, char *sec_busid)
{ struct dasd_device *primary, *secondary; struct dasd_copy_relation *copy; struct dasd_block *block; struct gendisk *gdp;
copy = device->copy; if (!copy) return DASD_COPYPAIRSWAP_INVALID;
primary = copy->active->device; if (!primary) return DASD_COPYPAIRSWAP_INVALID; /* double check if swap has correct primary */ if (strncmp(dev_name(&primary->cdev->dev), prim_busid, DASD_BUS_ID_SIZE) != 0) return DASD_COPYPAIRSWAP_PRIMARY;
secondary = copy_relation_find_device(copy, sec_busid); if (!secondary) return DASD_COPYPAIRSWAP_SECONDARY;
/* * usually the device should be quiesced for swap * for paranoia stop device and requeue requests again
*/
dasd_device_set_stop_bits(primary, DASD_STOPPED_PPRC);
dasd_device_set_stop_bits(secondary, DASD_STOPPED_PPRC);
dasd_generic_requeue_all_requests(primary);
/* * return configuration data that is referenced by record selector * if a record selector is specified or per default return the * conf_data pointer for the path specified by lpum
*/ staticstruct dasd_conf_data *dasd_eckd_get_ref_conf(struct dasd_device *device,
__u8 lpum, struct dasd_cuir_message *cuir)
{ struct dasd_conf_data *conf_data; int path, pos;
/* * This function determines the scope of a reconfiguration request by * analysing the path and device selection data provided in the CUIR request. * Returns a path mask containing CUIR affected paths for the give device. * * If the CUIR request does not contain the required information return the * path mask of the path the attention message for the CUIR request was reveived * on.
*/ staticint dasd_eckd_cuir_scope(struct dasd_device *device, __u8 lpum, struct dasd_cuir_message *cuir)
{ struct dasd_conf_data *ref_conf_data; unsignedlong bitmask = 0, mask = 0; struct dasd_conf_data *conf_data; unsignedint pos, path; char *ref_gneq, *gneq; char *ref_ned, *ned; int tbcpm = 0;
/* if CUIR request does not specify the scope use the path
the attention message was presented on */ if (!cuir->ned_map ||
!(cuir->neq_map[0] | cuir->neq_map[1] | cuir->neq_map[2])) return lpum;
/* get reference conf data */
ref_conf_data = dasd_eckd_get_ref_conf(device, lpum, cuir); /* reference ned is determined by ned_map field */
pos = 8 - ffs(cuir->ned_map);
ref_ned = (char *)&ref_conf_data->neds[pos];
ref_gneq = (char *)&ref_conf_data->gneq; /* transfer 24 bit neq_map to mask */
mask = cuir->neq_map[2];
mask |= cuir->neq_map[1] << 8;
mask |= cuir->neq_map[0] << 16;
for (path = 0; path < 8; path++) { /* initialise data per path */
bitmask = mask;
conf_data = device->path[path].conf_data;
pos = 8 - ffs(cuir->ned_map);
ned = (char *) &conf_data->neds[pos]; /* compare reference ned and per path ned */ if (memcmp(ref_ned, ned, sizeof(*ned)) != 0) continue;
gneq = (char *)&conf_data->gneq; /* compare reference gneq and per_path gneq under 24 bit mask where mask bit 0 equals byte 7 of
the gneq and mask bit 24 equals byte 31 */ while (bitmask) {
pos = ffs(bitmask) - 1; if (memcmp(&ref_gneq[31 - pos], &gneq[31 - pos], 1)
!= 0) break;
clear_bit(pos, &bitmask);
} if (bitmask) continue; /* device and path match the reference values
add path to CUIR scope */
tbcpm |= 0x80 >> path;
} return tbcpm;
}
staticvoid dasd_eckd_cuir_notify_user(struct dasd_device *device, unsignedlong paths, int action)
{ int pos;
while (paths) { /* get position of bit in mask */
pos = 8 - ffs(paths); /* get channel path descriptor from this position */ if (action == CUIR_QUIESCE)
pr_warn("Service on the storage server caused path %x.%02x to go offline",
device->path[pos].cssid,
device->path[pos].chpid); elseif (action == CUIR_RESUME)
pr_info("Path %x.%02x is back online after service on the storage server",
device->path[pos].cssid,
device->path[pos].chpid);
clear_bit(7 - pos, &paths);
}
}
tbcpm = dasd_eckd_cuir_scope(device, lpum, cuir); /* nothing to do if path is not in use */ if (!(dasd_path_get_opm(device) & tbcpm)) return 0; if (!(dasd_path_get_opm(device) & ~tbcpm)) { /* no path would be left if the CUIR action is taken
return error */ return -EINVAL;
} /* remove device from operational path mask */
dasd_path_remove_opm(device, tbcpm);
dasd_path_add_cuirpm(device, tbcpm); return tbcpm;
}
/* * walk through all devices and build a path mask to quiesce them * return an error if the last path to a device would be removed * * if only part of the devices are quiesced and an error * occurs no onlining necessary, the storage server will * notify the already set offline devices again
*/ staticint dasd_eckd_cuir_quiesce(struct dasd_device *device, __u8 lpum, struct dasd_cuir_message *cuir)
{ struct dasd_eckd_private *private = device->private; struct alias_pav_group *pavgroup, *tempgroup; struct dasd_device *dev, *n; unsignedlong paths = 0; unsignedlong flags; int tbcpm;
/* active devices */
list_for_each_entry_safe(dev, n, &private->lcu->active_devices,
alias_list) {
spin_lock_irqsave(get_ccwdev_lock(dev->cdev), flags);
tbcpm = dasd_eckd_cuir_remove_path(dev, lpum, cuir);
spin_unlock_irqrestore(get_ccwdev_lock(dev->cdev), flags); if (tbcpm < 0) goto out_err;
paths |= tbcpm;
} /* inactive devices */
list_for_each_entry_safe(dev, n, &private->lcu->inactive_devices,
alias_list) {
spin_lock_irqsave(get_ccwdev_lock(dev->cdev), flags);
tbcpm = dasd_eckd_cuir_remove_path(dev, lpum, cuir);
spin_unlock_irqrestore(get_ccwdev_lock(dev->cdev), flags); if (tbcpm < 0) goto out_err;
paths |= tbcpm;
} /* devices in PAV groups */
list_for_each_entry_safe(pavgroup, tempgroup,
&private->lcu->grouplist, group) {
list_for_each_entry_safe(dev, n, &pavgroup->baselist,
alias_list) {
spin_lock_irqsave(get_ccwdev_lock(dev->cdev), flags);
tbcpm = dasd_eckd_cuir_remove_path(dev, lpum, cuir);
spin_unlock_irqrestore(
get_ccwdev_lock(dev->cdev), flags); if (tbcpm < 0) goto out_err;
paths |= tbcpm;
}
list_for_each_entry_safe(dev, n, &pavgroup->aliaslist,
alias_list) {
spin_lock_irqsave(get_ccwdev_lock(dev->cdev), flags);
tbcpm = dasd_eckd_cuir_remove_path(dev, lpum, cuir);
spin_unlock_irqrestore(
get_ccwdev_lock(dev->cdev), flags); if (tbcpm < 0) goto out_err;
paths |= tbcpm;
}
} /* notify user about all paths affected by CUIR action */
dasd_eckd_cuir_notify_user(device, paths, CUIR_QUIESCE); return 0;
out_err: return tbcpm;
}
dasd_eckd_psf_cuir_response(device, response,
cuir->message_id, lpum);
DBF_DEV_EVENT(DBF_WARNING, device, "CUIR response: %d on message ID %08x", response,
cuir->message_id); /* to make sure there is no attention left schedule work again */
device->discipline->check_attention(device, lpum);
}
switch (oos->code) { case REPO_WARN: case POOL_WARN:
dev_warn(&device->cdev->dev, "Extent pool usage has reached a critical value\n");
dasd_eckd_oos_resume(device); break; case REPO_EXHAUST: case POOL_EXHAUST:
dev_warn(&device->cdev->dev, "Extent pool is exhausted\n"); break; case REPO_RELIEVE: case POOL_RELIEVE:
dev_info(&device->cdev->dev, "Extent pool physical space constraint has been relieved\n"); break;
}
/* In any case, update related data */
dasd_eckd_read_ext_pool_info(device);
/* to make sure there is no attention left schedule work again */
device->discipline->check_attention(device, lpum);
}
if (!private->fcx_max_data) { /* sanity check for no HPF, the error makes no sense */
DBF_DEV_EVENT(DBF_WARNING, device, "%s", "Trying to disable HPF for a non HPF device"); return;
} if (irb->scsw.tm.sesq == SCSW_SESQ_DEV_NOFCX) {
dasd_eckd_disable_hpf_device(device);
} elseif (irb->scsw.tm.sesq == SCSW_SESQ_PATH_NOFCX) { if (dasd_eckd_disable_hpf_path(device, irb->esw.esw1.lpum)) return;
dasd_eckd_disable_hpf_device(device);
dasd_path_set_tbvpm(device,
dasd_path_get_hpfpm(device));
} /* * prevent that any new I/O ist started on the device and schedule a * requeue of existing requests
*/
dasd_device_set_stop_bits(device, DASD_STOPPED_NOT_ACC);
dasd_schedule_requeue(device);
}
staticunsignedint dasd_eckd_max_sectors(struct dasd_block *block)
{ if (block->base->features & DASD_FEATURE_USERAW) { /* * the max_blocks value for raw_track access is 256 * it is higher than the native ECKD value because we * only need one ccw per track * so the max_hw_sectors are * 2048 x 512B = 1024kB = 16 tracks
*/ return DASD_ECKD_MAX_BLOCKS_RAW << block->s2b_shift;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.