diff options
author | Jan Höppner <hoeppner@linux.vnet.ibm.com> | 2015-08-19 13:41:20 +0200 |
---|---|---|
committer | Martin Schwidefsky <schwidefsky@de.ibm.com> | 2016-04-15 18:16:39 +0200 |
commit | 8fd575200db5b53f6ea6818dd017f1b43190db12 (patch) | |
tree | 670f65e645d0d669864979d13a5e91448b53f8ad /drivers/s390/block/dasd_eckd.c | |
parent | s390/sclp: event type macro cleanup (diff) | |
download | linux-8fd575200db5b53f6ea6818dd017f1b43190db12.tar.xz linux-8fd575200db5b53f6ea6818dd017f1b43190db12.zip |
s390/dasd: Add new ioctl BIODASDCHECKFMT
Implement new DASD IOCTL BIODASDCHECKFMT to check a range of tracks on a
DASD volume for correct formatting. The following characteristics are
checked:
- Block size
- ECKD key length
- ECKD record ID
- Number of records per track
Signed-off-by: Jan Höppner <hoeppner@linux.vnet.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Diffstat (limited to 'drivers/s390/block/dasd_eckd.c')
-rw-r--r-- | drivers/s390/block/dasd_eckd.c | 535 |
1 files changed, 519 insertions, 16 deletions
diff --git a/drivers/s390/block/dasd_eckd.c b/drivers/s390/block/dasd_eckd.c index 3b70d378d1c1..42b34cd1f002 100644 --- a/drivers/s390/block/dasd_eckd.c +++ b/drivers/s390/block/dasd_eckd.c @@ -121,6 +121,11 @@ struct check_attention_work_data { __u8 lpum; }; +static int prepare_itcw(struct itcw *, unsigned int, unsigned int, int, + struct dasd_device *, struct dasd_device *, + unsigned int, int, unsigned int, unsigned int, + unsigned int, unsigned int); + /* initial attempt at a probe function. this can be simplified once * the other detection code is gone */ static int @@ -257,10 +262,13 @@ define_extent(struct ccw1 *ccw, struct DE_eckd_data *data, unsigned int trk, case DASD_ECKD_CCW_READ_CKD_MT: case DASD_ECKD_CCW_READ_KD: case DASD_ECKD_CCW_READ_KD_MT: - case DASD_ECKD_CCW_READ_COUNT: data->mask.perm = 0x1; data->attributes.operation = private->attrib.operation; break; + case DASD_ECKD_CCW_READ_COUNT: + data->mask.perm = 0x1; + data->attributes.operation = DASD_BYPASS_CACHE; + break; case DASD_ECKD_CCW_WRITE: case DASD_ECKD_CCW_WRITE_MT: case DASD_ECKD_CCW_WRITE_KD: @@ -529,10 +537,13 @@ static int prefix_LRE(struct ccw1 *ccw, struct PFX_eckd_data *pfxdata, case DASD_ECKD_CCW_READ_CKD_MT: case DASD_ECKD_CCW_READ_KD: case DASD_ECKD_CCW_READ_KD_MT: - case DASD_ECKD_CCW_READ_COUNT: dedata->mask.perm = 0x1; dedata->attributes.operation = basepriv->attrib.operation; break; + case DASD_ECKD_CCW_READ_COUNT: + dedata->mask.perm = 0x1; + dedata->attributes.operation = DASD_BYPASS_CACHE; + break; case DASD_ECKD_CCW_READ_TRACK: case DASD_ECKD_CCW_READ_TRACK_DATA: dedata->mask.perm = 0x1; @@ -2096,6 +2107,180 @@ dasd_eckd_fill_geometry(struct dasd_block *block, struct hd_geometry *geo) return 0; } +/* + * Build the TCW request for the format check + */ +static struct dasd_ccw_req * +dasd_eckd_build_check_tcw(struct dasd_device *base, struct format_data_t *fdata, + int enable_pav, struct eckd_count *fmt_buffer, + int rpt) +{ + struct dasd_eckd_private *start_priv; + struct dasd_device *startdev = NULL; + struct tidaw *last_tidaw = NULL; + struct dasd_ccw_req *cqr; + struct itcw *itcw; + int itcw_size; + int count; + int rc; + int i; + + if (enable_pav) + startdev = dasd_alias_get_start_dev(base); + + if (!startdev) + startdev = base; + + start_priv = startdev->private; + + count = rpt * (fdata->stop_unit - fdata->start_unit + 1); + + /* + * we're adding 'count' amount of tidaw to the itcw. + * calculate the corresponding itcw_size + */ + itcw_size = itcw_calc_size(0, count, 0); + + cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 0, itcw_size, startdev); + if (IS_ERR(cqr)) + return cqr; + + start_priv->count++; + + itcw = itcw_init(cqr->data, itcw_size, ITCW_OP_READ, 0, count, 0); + if (IS_ERR(itcw)) { + rc = -EINVAL; + goto out_err; + } + + cqr->cpaddr = itcw_get_tcw(itcw); + rc = prepare_itcw(itcw, fdata->start_unit, fdata->stop_unit, + DASD_ECKD_CCW_READ_COUNT_MT, base, startdev, 0, count, + sizeof(struct eckd_count), + count * sizeof(struct eckd_count), 0, rpt); + if (rc) + goto out_err; + + for (i = 0; i < count; i++) { + last_tidaw = itcw_add_tidaw(itcw, 0, fmt_buffer++, + sizeof(struct eckd_count)); + if (IS_ERR(last_tidaw)) { + rc = -EINVAL; + goto out_err; + } + } + + last_tidaw->flags |= TIDAW_FLAGS_LAST; + itcw_finalize(itcw); + + cqr->cpmode = 1; + cqr->startdev = startdev; + cqr->memdev = startdev; + cqr->basedev = base; + cqr->retries = startdev->default_retries; + cqr->expires = startdev->default_expires * HZ; + cqr->buildclk = get_tod_clock(); + cqr->status = DASD_CQR_FILLED; + /* Set flags to suppress output for expected errors */ + set_bit(DASD_CQR_SUPPRESS_FP, &cqr->flags); + set_bit(DASD_CQR_SUPPRESS_IL, &cqr->flags); + + return cqr; + +out_err: + dasd_sfree_request(cqr, startdev); + + return ERR_PTR(rc); +} + +/* + * Build the CCW request for the format check + */ +static struct dasd_ccw_req * +dasd_eckd_build_check(struct dasd_device *base, struct format_data_t *fdata, + int enable_pav, struct eckd_count *fmt_buffer, int rpt) +{ + struct dasd_eckd_private *start_priv; + struct dasd_eckd_private *base_priv; + struct dasd_device *startdev = NULL; + struct dasd_ccw_req *cqr; + struct ccw1 *ccw; + void *data; + int cplength, datasize; + int use_prefix; + int count; + int i; + + if (enable_pav) + startdev = dasd_alias_get_start_dev(base); + + if (!startdev) + startdev = base; + + start_priv = startdev->private; + base_priv = base->private; + + count = rpt * (fdata->stop_unit - fdata->start_unit + 1); + + use_prefix = base_priv->features.feature[8] & 0x01; + + if (use_prefix) { + cplength = 1; + datasize = sizeof(struct PFX_eckd_data); + } else { + cplength = 2; + datasize = sizeof(struct DE_eckd_data) + + sizeof(struct LO_eckd_data); + } + cplength += count; + + cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, cplength, datasize, + startdev); + if (IS_ERR(cqr)) + return cqr; + + start_priv->count++; + data = cqr->data; + ccw = cqr->cpaddr; + + if (use_prefix) { + prefix_LRE(ccw++, data, fdata->start_unit, fdata->stop_unit, + DASD_ECKD_CCW_READ_COUNT, base, startdev, 1, 0, + count, 0, 0); + } else { + define_extent(ccw++, data, fdata->start_unit, fdata->stop_unit, + DASD_ECKD_CCW_READ_COUNT, startdev); + + data += sizeof(struct DE_eckd_data); + ccw[-1].flags |= CCW_FLAG_CC; + + locate_record(ccw++, data, fdata->start_unit, 0, count, + DASD_ECKD_CCW_READ_COUNT, base, 0); + } + + for (i = 0; i < count; i++) { + ccw[-1].flags |= CCW_FLAG_CC; + ccw->cmd_code = DASD_ECKD_CCW_READ_COUNT; + ccw->flags = CCW_FLAG_SLI; + ccw->count = 8; + ccw->cda = (__u32)(addr_t) fmt_buffer; + ccw++; + fmt_buffer++; + } + + cqr->startdev = startdev; + cqr->memdev = startdev; + cqr->basedev = base; + cqr->retries = DASD_RETRIES; + cqr->expires = startdev->default_expires * HZ; + cqr->buildclk = get_tod_clock(); + cqr->status = DASD_CQR_FILLED; + /* Set flags to suppress output for expected errors */ + set_bit(DASD_CQR_SUPPRESS_NRF, &cqr->flags); + + return cqr; +} + static struct dasd_ccw_req * dasd_eckd_build_format(struct dasd_device *base, struct format_data_t *fdata, @@ -2363,9 +2548,24 @@ dasd_eckd_build_format(struct dasd_device *base, */ static struct dasd_ccw_req * dasd_eckd_format_build_ccw_req(struct dasd_device *base, - struct format_data_t *fdata, int enable_pav) + struct format_data_t *fdata, int enable_pav, + int tpm, struct eckd_count *fmt_buffer, int rpt) { - return dasd_eckd_build_format(base, fdata, enable_pav); + struct dasd_ccw_req *ccw_req; + + if (!fmt_buffer) { + ccw_req = dasd_eckd_build_format(base, fdata, enable_pav); + } else { + if (tpm) + ccw_req = dasd_eckd_build_check_tcw(base, fdata, + enable_pav, + fmt_buffer, rpt); + else + ccw_req = dasd_eckd_build_check(base, fdata, enable_pav, + fmt_buffer, rpt); + } + + return ccw_req; } /* @@ -2410,12 +2610,15 @@ static int dasd_eckd_format_sanity_checks(struct dasd_device *base, */ static int dasd_eckd_format_process_data(struct dasd_device *base, struct format_data_t *fdata, - int enable_pav) + 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; @@ -2429,8 +2632,18 @@ static int dasd_eckd_format_process_data(struct dasd_device *base, old_start = fdata->start_unit; old_stop = fdata->stop_unit; - format_step = DASD_CQR_MAX_CCW / recs_per_track(&private->rdc_data, 0, - fdata->blksize); + if (!tpm && fmt_buffer != NULL) { + /* Command Mode / Format Check */ + format_step = 1; + } else if (tpm && fmt_buffer != NULL) { + /* Transport Mode / Format Check */ + format_step = DASD_CQR_MAX_CCW / rpt; + } else { + /* Normal Formatting */ + format_step = DASD_CQR_MAX_CCW / + recs_per_track(&private->rdc_data, 0, fdata->blksize); + } + do { retry = 0; while (fdata->start_unit <= old_stop) { @@ -2441,7 +2654,8 @@ static int dasd_eckd_format_process_data(struct dasd_device *base, } cqr = dasd_eckd_format_build_ccw_req(base, fdata, - enable_pav); + enable_pav, tpm, + fmt_buffer, rpt); if (IS_ERR(cqr)) { rc = PTR_ERR(cqr); if (rc == -ENOMEM) { @@ -2459,6 +2673,10 @@ static int dasd_eckd_format_process_data(struct dasd_device *base, } list_add_tail(&cqr->blocklist, &format_queue); + if (fmt_buffer) { + step = fdata->stop_unit - fdata->start_unit + 1; + fmt_buffer += rpt * step; + } fdata->start_unit = fdata->stop_unit + 1; fdata->stop_unit = old_stop; } @@ -2469,15 +2687,41 @@ out_err: list_for_each_entry_safe(cqr, n, &format_queue, blocklist) { device = cqr->startdev; private = device->private; - if (cqr->status == DASD_CQR_FAILED) + + 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_sfree_request(cqr, device); private->count--; } - if (rc) + 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; + } } while (retry); @@ -2491,7 +2735,225 @@ out: static int dasd_eckd_format_device(struct dasd_device *base, struct format_data_t *fdata, int enable_pav) { - return dasd_eckd_format_process_data(base, fdata, enable_pav); + return dasd_eckd_format_process_data(base, fdata, enable_pav, 0, NULL, + 0, NULL); +} + +/* + * Helper function to count consecutive records of a single track. + */ +static int 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. + */ +static void 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; + + trkcount = cdata->expect.stop_unit - cdata->expect.start_unit + 1; + max_entries = trkcount * rpt_max; + + 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; + } + + for (j = 0; j < count; j++, pos++) { + blksize = cdata->expect.blksize; + kl = 0; + + /* + * Set special values when checking CDL formatted + * devices. + */ + if ((cdata->expect.intensity & 0x08) && + geo.cyl == 0 && geo.head == 0) { + if (j < 3) { + blksize = sizes_trk0[j] - 4; + kl = 4; + } + } + if ((cdata->expect.intensity & 0x08) && + geo.cyl == 0 && geo.head == 1) { + blksize = LABEL_SIZE - 44; + kl = 44; + } + + /* Check blocksize */ + if (fmt_buffer[pos].dl != blksize) { + cdata->result = DASD_FMT_ERR_BLKSIZE; + goto out; + } + /* Check if key length is 0 */ + if (fmt_buffer[pos].kl != kl) { + cdata->result = DASD_FMT_ERR_KEY_LENGTH; + goto out; + } + /* Check if record_id is correct */ + if (fmt_buffer[pos].cyl != geo.cyl || + fmt_buffer[pos].head != geo.head || + fmt_buffer[pos].record != (j + 1)) { + cdata->result = DASD_FMT_ERR_RECORD_ID; + goto out; + } + } + } + +out: + /* + * In case of no errors, we need to decrease by one + * to get the correct positions. + */ + if (!cdata->result) { + i--; + pos--; + } + + cdata->unit = i; + cdata->num_records = count; + cdata->rec = fmt_buffer[pos].record; + cdata->blksize = fmt_buffer[pos].dl; + cdata->key_length = fmt_buffer[pos].kl; +} + +/* + * Check the format of a range of tracks of a DASD. + */ +static int 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); + + trkcount = cdata->expect.stop_unit - cdata->expect.start_unit + 1; + fmt_buffer_size = trkcount * rpt_max * sizeof(struct eckd_count); + + 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; + } + } + + dasd_eckd_format_evaluate_tracks(fmt_buffer, cdata, rpt_max, rpt_exp, + trk_per_cyl, tpm); + +out: + kfree(fmt_buffer); + + return rc; } static void dasd_eckd_handle_terminated_request(struct dasd_ccw_req *cqr) @@ -3038,6 +3500,16 @@ static int prepare_itcw(struct itcw *itcw, lredata->auxiliary.check_bytes = 0x2; pfx_cmd = DASD_ECKD_CCW_PFX; break; + case DASD_ECKD_CCW_READ_COUNT_MT: + dedata->mask.perm = 0x1; + dedata->attributes.operation = DASD_BYPASS_CACHE; + dedata->ga_extended |= 0x42; + dedata->blk_size = blksize; + lredata->operation.orientation = 0x2; + lredata->operation.operation = 0x16; + lredata->auxiliary.check_bytes = 0x01; + pfx_cmd = DASD_ECKD_CCW_PFX_READ; + break; default: DBF_DEV_EVENT(DBF_ERR, basedev, "prepare itcw, unknown opcode 0x%x", cmd); @@ -3085,13 +3557,19 @@ static int prepare_itcw(struct itcw *itcw, } } - lredata->auxiliary.length_valid = 1; - lredata->auxiliary.length_scope = 1; + if (cmd == DASD_ECKD_CCW_READ_COUNT_MT) { + lredata->auxiliary.length_valid = 0; + lredata->auxiliary.length_scope = 0; + lredata->sector = 0xff; + } else { + lredata->auxiliary.length_valid = 1; + lredata->auxiliary.length_scope = 1; + lredata->sector = sector; + } lredata->auxiliary.imbedded_ccw_valid = 1; lredata->length = tlf; lredata->imbedded_ccw = cmd; lredata->count = count; - lredata->sector = sector; set_ch_t(&lredata->seek_addr, begcyl, beghead); lredata->search_arg.cyl = lredata->seek_addr.cyl; lredata->search_arg.head = lredata->seek_addr.head; @@ -4413,10 +4891,34 @@ static void dasd_eckd_dump_sense_tcw(struct dasd_device *device, static void dasd_eckd_dump_sense(struct dasd_device *device, struct dasd_ccw_req *req, struct irb *irb) { - if (scsw_is_tm(&irb->scsw)) + u8 *sense = dasd_get_sense(irb); + + if (scsw_is_tm(&irb->scsw)) { + /* + * In some cases the 'File Protected' or 'Incorrect Length' + * error might be expected and log messages shouldn't be written + * then. Check if the according suppress bit is set. + */ + if (sense && (sense[1] & SNS1_FILE_PROTECTED) && + test_bit(DASD_CQR_SUPPRESS_FP, &req->flags)) + return; + if (scsw_cstat(&irb->scsw) == 0x40 && + test_bit(DASD_CQR_SUPPRESS_IL, &req->flags)) + return; + dasd_eckd_dump_sense_tcw(device, req, irb); - else + } else { + /* + * In some cases the 'No Record Found' error might be expected + * and log messages shouldn't be written then. Check if the + * according suppress bit is set. + */ + if (sense && sense[1] & SNS1_NO_REC_FOUND && + test_bit(DASD_CQR_SUPPRESS_NRF, &req->flags)) + return; + dasd_eckd_dump_sense_ccw(device, req, irb); + } } static int dasd_eckd_pm_freeze(struct dasd_device *device) @@ -5246,6 +5748,7 @@ static struct dasd_discipline dasd_eckd_discipline = { .term_IO = dasd_term_IO, .handle_terminated_request = dasd_eckd_handle_terminated_request, .format_device = dasd_eckd_format_device, + .check_device_format = dasd_eckd_check_device_format, .erp_action = dasd_eckd_erp_action, .erp_postaction = dasd_eckd_erp_postaction, .check_for_device_change = dasd_eckd_check_for_device_change, |