Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8fd57520 authored by Jan Höppner's avatar Jan Höppner Committed by Martin Schwidefsky
Browse files

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: default avatarJan Höppner <hoeppner@linux.vnet.ibm.com>
Signed-off-by: default avatarMartin Schwidefsky <schwidefsky@de.ibm.com>
parent 3fa7ee88
Loading
Loading
Loading
Loading
+32 −0
Original line number Diff line number Diff line
@@ -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)

+35 −1
Original line number Diff line number Diff line
@@ -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;
		}

		/*
		 * 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 @@ static int _dasd_sleep_on_queue(struct list_head *ccw_queue, int interruptible)

	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
+16 −4
Original line number Diff line number Diff line
@@ -1367,6 +1367,12 @@ dasd_3990_erp_no_rec(struct dasd_ccw_req * default_erp, char *sense)

	struct dasd_device *device = default_erp->startdev;

	/*
	 * 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");

@@ -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);

+519 −16
Original line number Diff line number Diff line
@@ -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 @@ static int dasd_eckd_format_process_data(struct dasd_device *base,
		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 @@ static int dasd_eckd_format_process_data(struct dasd_device *base,
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,
		}
	}

	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,11 +4891,35 @@ 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,
+1 −0
Original line number Diff line number Diff line
@@ -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
Loading