s390/dasd: Add new ioctl BIODASDCHECKFMT
authorJan Höppner <hoeppner@linux.vnet.ibm.com>
Wed, 19 Aug 2015 11:41:20 +0000 (13:41 +0200)
committerMartin Schwidefsky <schwidefsky@de.ibm.com>
Fri, 15 Apr 2016 16:16:39 +0000 (18:16 +0200)
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>
arch/s390/include/uapi/asm/dasd.h
drivers/s390/block/dasd.c
drivers/s390/block/dasd_3990_erp.c
drivers/s390/block/dasd_eckd.c
drivers/s390/block/dasd_eckd.h
drivers/s390/block/dasd_int.h
drivers/s390/block/dasd_ioctl.c

index 5812a3b2df9e29ff1f639e59f748a686298ad0eb..1340311dab77db4cb0766d3ffaab5370dd883193 100644 (file)
@@ -187,6 +187,36 @@ typedef struct format_data_t {
 #define DASD_FMT_INT_INVAL  4 /* invalidate tracks */
 #define DASD_FMT_INT_COMPAT 8 /* use OS/390 compatible disk layout */
 
+/*
+ * struct format_check_t
+ * represents all data necessary to evaluate the format of
+ * different tracks of a dasd
+ */
+typedef struct format_check_t {
+       /* Input */
+       struct format_data_t expect;
+
+       /* Output */
+       unsigned int result;            /* Error indication (DASD_FMT_ERR_*) */
+       unsigned int unit;              /* Track that is in error */
+       unsigned int rec;               /* Record that is in error */
+       unsigned int num_records;       /* Records in the track in error */
+       unsigned int blksize;           /* Blocksize of first record in error */
+       unsigned int key_length;        /* Key length of first record in error */
+} format_check_t;
+
+/* Values returned in format_check_t when a format error is detected: */
+/* Too few records were found on a single track */
+#define DASD_FMT_ERR_TOO_FEW_RECORDS   1
+/* Too many records were found on a single track */
+#define DASD_FMT_ERR_TOO_MANY_RECORDS  2
+/* Blocksize/data-length of a record was wrong */
+#define DASD_FMT_ERR_BLKSIZE           3
+/* A record ID is defined by cylinder, head, and record number (CHR). */
+/* On mismatch, this error is set */
+#define DASD_FMT_ERR_RECORD_ID         4
+/* If key-length was != 0 */
+#define DASD_FMT_ERR_KEY_LENGTH                5
 
 /* 
  * struct attrib_data_t
@@ -288,6 +318,8 @@ struct dasd_snid_ioctl_data {
 
 /* Get Sense Path Group ID (SNID) data */
 #define BIODASDSNID    _IOWR(DASD_IOCTL_LETTER, 1, struct dasd_snid_ioctl_data)
+/* Check device format according to format_check_t */
+#define BIODASDCHECKFMT _IOWR(DASD_IOCTL_LETTER, 2, format_check_t)
 
 #define BIODASDSYMMIO  _IOWR(DASD_IOCTL_LETTER, 240, dasd_symmio_parms_t)
 
index 4adb6d14d56265e0e606a14f289a38c3a66dd6be..8973d34ce5ba0f786afbe228e201fa36befe9f37 100644 (file)
@@ -1638,6 +1638,9 @@ void dasd_int_handler(struct ccw_device *cdev, unsigned long intparm,
        struct dasd_ccw_req *cqr, *next;
        struct dasd_device *device;
        unsigned long long now;
+       int nrf_suppressed = 0;
+       int fp_suppressed = 0;
+       u8 *sense = NULL;
        int expires;
 
        if (IS_ERR(irb)) {
@@ -1673,7 +1676,23 @@ void dasd_int_handler(struct ccw_device *cdev, unsigned long intparm,
                        dasd_put_device(device);
                        return;
                }
-               device->discipline->dump_sense_dbf(device, irb, "int");
+
+               /*
+                * In some cases 'File Protected' or 'No Record Found' errors
+                * might be expected and debug log messages for the
+                * corresponding interrupts shouldn't be written then.
+                * Check if either of the according suppress bits is set.
+                */
+               sense = dasd_get_sense(irb);
+               if (sense) {
+                       fp_suppressed = (sense[1] & SNS1_FILE_PROTECTED) &&
+                               test_bit(DASD_CQR_SUPPRESS_FP, &cqr->flags);
+                       nrf_suppressed = (sense[1] & SNS1_NO_REC_FOUND) &&
+                               test_bit(DASD_CQR_SUPPRESS_NRF, &cqr->flags);
+               }
+               if (!(fp_suppressed || nrf_suppressed))
+                       device->discipline->dump_sense_dbf(device, irb, "int");
+
                if (device->features & DASD_FEATURE_ERPLOG)
                        device->discipline->dump_sense(device, cqr, irb);
                device->discipline->check_for_device_change(device, cqr, irb);
@@ -2312,6 +2331,7 @@ static int _dasd_sleep_on_queue(struct list_head *ccw_queue, int interruptible)
 {
        struct dasd_device *device;
        struct dasd_ccw_req *cqr, *n;
+       u8 *sense = NULL;
        int rc;
 
 retry:
@@ -2357,6 +2377,20 @@ retry:
 
        rc = 0;
        list_for_each_entry_safe(cqr, n, ccw_queue, blocklist) {
+               /*
+                * In some cases the 'File Protected' or 'Incorrect Length'
+                * error might be expected and error recovery would be
+                * unnecessary in these cases.  Check if the according suppress
+                * bit is set.
+                */
+               sense = dasd_get_sense(&cqr->irb);
+               if (sense && sense[1] & SNS1_FILE_PROTECTED &&
+                   test_bit(DASD_CQR_SUPPRESS_FP, &cqr->flags))
+                       continue;
+               if (scsw_cstat(&cqr->irb.scsw) == 0x40 &&
+                   test_bit(DASD_CQR_SUPPRESS_IL, &cqr->flags))
+                       continue;
+
                /*
                 * for alias devices simplify error recovery and
                 * return to upper layer
index d261347136820a76957db8b8c9b9654ead81961b..8305ab688d5766b5eaf9b694bd10b52cfc7ec150 100644 (file)
@@ -1367,8 +1367,14 @@ dasd_3990_erp_no_rec(struct dasd_ccw_req * default_erp, char *sense)
 
        struct dasd_device *device = default_erp->startdev;
 
-       dev_err(&device->cdev->dev,
-                   "The specified record was not found\n");
+       /*
+        * 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 (!test_bit(DASD_CQR_SUPPRESS_NRF, &default_erp->flags))
+               dev_err(&device->cdev->dev,
+                       "The specified record was not found\n");
 
        return dasd_3990_erp_cleanup(default_erp, DASD_CQR_FAILED);
 
@@ -1393,8 +1399,14 @@ dasd_3990_erp_file_prot(struct dasd_ccw_req * erp)
 
        struct dasd_device *device = erp->startdev;
 
-       dev_err(&device->cdev->dev, "Accessing the DASD failed because of "
-               "a hardware error\n");
+       /*
+        * In some cases the 'File Protected' error might be expected and
+        * log messages shouldn't be written then.
+        * Check if the according suppress bit is set.
+        */
+       if (!test_bit(DASD_CQR_SUPPRESS_FP, &erp->flags))
+               dev_err(&device->cdev->dev,
+                       "Accessing the DASD failed because of a hardware error\n");
 
        return dasd_3990_erp_cleanup(erp, DASD_CQR_FAILED);
 
index 3b70d378d1c124587c7fd2d2f1bb43b12377722d..42b34cd1f0026125bce9c50a751917d3e0b8b459 100644 (file)
@@ -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,
index 862ee4291abd85ad5ecad9bde388ce337663a6a5..59803626ea36cd9662c7d208a27d59b238aa836d 100644 (file)
@@ -35,6 +35,7 @@
 #define DASD_ECKD_CCW_READ_MT           0x86
 #define DASD_ECKD_CCW_WRITE_KD_MT       0x8d
 #define DASD_ECKD_CCW_READ_KD_MT        0x8e
+#define DASD_ECKD_CCW_READ_COUNT_MT     0x92
 #define DASD_ECKD_CCW_RELEASE           0x94
 #define DASD_ECKD_CCW_WRITE_FULL_TRACK  0x95
 #define DASD_ECKD_CCW_READ_CKD_MT       0x9e
index 6132733bcd950e26c3f60d21132eca00009f8b7c..ac7027e6d52b0eb508be47cc0aa90bd161ad0913 100644 (file)
@@ -236,6 +236,13 @@ struct dasd_ccw_req {
                                         * stolen. Should not be combined with
                                         * DASD_CQR_FLAGS_USE_ERP
                                         */
+/*
+ * The following flags are used to suppress output of certain errors.
+ * These flags should only be used for format checks!
+ */
+#define DASD_CQR_SUPPRESS_NRF  4       /* Suppress 'No Record Found' error */
+#define DASD_CQR_SUPPRESS_FP   5       /* Suppress 'File Protected' error*/
+#define DASD_CQR_SUPPRESS_IL   6       /* Suppress 'Incorrect Length' error */
 
 /* Signature for error recovery functions. */
 typedef struct dasd_ccw_req *(*dasd_erp_fn_t) (struct dasd_ccw_req *);
@@ -318,7 +325,8 @@ struct dasd_discipline {
         * Device operation functions. build_cp creates a ccw chain for
         * a block device request, start_io starts the request and
         * term_IO cancels it (e.g. in case of a timeout). format_device
-        * returns a ccw chain to be used to format the device.
+        * formats the device and check_device_format compares the format of
+        * a device with the expected format_data.
         * handle_terminated_request allows to examine a cqr and prepare
         * it for retry.
         */
@@ -329,7 +337,9 @@ struct dasd_discipline {
        int (*term_IO) (struct dasd_ccw_req *);
        void (*handle_terminated_request) (struct dasd_ccw_req *);
        int (*format_device) (struct dasd_device *,
-                             struct format_data_t *, int enable_pav);
+                             struct format_data_t *, int);
+       int (*check_device_format)(struct dasd_device *,
+                                  struct format_check_t *, int);
        int (*free_cp) (struct dasd_ccw_req *, struct request *);
 
        /*
index 90f30cc31561d359b29f5557ac88a63518c0b9e6..9dfbd972f844ef44cc237ce36236b993dc6baa4d 100644 (file)
@@ -238,6 +238,23 @@ dasd_format(struct dasd_block *block, struct format_data_t *fdata)
        return rc;
 }
 
+static int dasd_check_format(struct dasd_block *block,
+                            struct format_check_t *cdata)
+{
+       struct dasd_device *base;
+       int rc;
+
+       base = block->base;
+       if (!base->discipline->check_device_format)
+               return -ENOTTY;
+
+       rc = base->discipline->check_device_format(base, cdata, 1);
+       if (rc == -EAGAIN)
+               rc = base->discipline->check_device_format(base, cdata, 0);
+
+       return rc;
+}
+
 /*
  * Format device.
  */
@@ -272,6 +289,47 @@ dasd_ioctl_format(struct block_device *bdev, void __user *argp)
        }
        rc = dasd_format(base->block, &fdata);
        dasd_put_device(base);
+
+       return rc;
+}
+
+/*
+ * Check device format
+ */
+static int dasd_ioctl_check_format(struct block_device *bdev, void __user *argp)
+{
+       struct format_check_t cdata;
+       struct dasd_device *base;
+       int rc = 0;
+
+       if (!argp)
+               return -EINVAL;
+
+       base = dasd_device_from_gendisk(bdev->bd_disk);
+       if (!base)
+               return -ENODEV;
+       if (bdev != bdev->bd_contains) {
+               pr_warn("%s: The specified DASD is a partition and cannot be checked\n",
+                       dev_name(&base->cdev->dev));
+               rc = -EINVAL;
+               goto out_err;
+       }
+
+       if (copy_from_user(&cdata, argp, sizeof(cdata))) {
+               rc = -EFAULT;
+               goto out_err;
+       }
+
+       rc = dasd_check_format(base->block, &cdata);
+       if (rc)
+               goto out_err;
+
+       if (copy_to_user(argp, &cdata, sizeof(cdata)))
+               rc = -EFAULT;
+
+out_err:
+       dasd_put_device(base);
+
        return rc;
 }
 
@@ -519,6 +577,9 @@ int dasd_ioctl(struct block_device *bdev, fmode_t mode,
        case BIODASDFMT:
                rc = dasd_ioctl_format(bdev, argp);
                break;
+       case BIODASDCHECKFMT:
+               rc = dasd_ioctl_check_format(bdev, argp);
+               break;
        case BIODASDINFO:
                rc = dasd_ioctl_information(block, cmd, argp);
                break;