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>
This commit is contained in:
committed by
Martin Schwidefsky
parent
3fa7ee8844
commit
8fd575200d
@@ -187,6 +187,36 @@ typedef struct format_data_t {
|
|||||||
#define DASD_FMT_INT_INVAL 4 /* invalidate tracks */
|
#define DASD_FMT_INT_INVAL 4 /* invalidate tracks */
|
||||||
#define DASD_FMT_INT_COMPAT 8 /* use OS/390 compatible disk layout */
|
#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
|
* struct attrib_data_t
|
||||||
@@ -288,6 +318,8 @@ struct dasd_snid_ioctl_data {
|
|||||||
|
|
||||||
/* Get Sense Path Group ID (SNID) data */
|
/* Get Sense Path Group ID (SNID) data */
|
||||||
#define BIODASDSNID _IOWR(DASD_IOCTL_LETTER, 1, struct dasd_snid_ioctl_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)
|
#define BIODASDSYMMIO _IOWR(DASD_IOCTL_LETTER, 240, dasd_symmio_parms_t)
|
||||||
|
|
||||||
|
|||||||
@@ -1638,6 +1638,9 @@ void dasd_int_handler(struct ccw_device *cdev, unsigned long intparm,
|
|||||||
struct dasd_ccw_req *cqr, *next;
|
struct dasd_ccw_req *cqr, *next;
|
||||||
struct dasd_device *device;
|
struct dasd_device *device;
|
||||||
unsigned long long now;
|
unsigned long long now;
|
||||||
|
int nrf_suppressed = 0;
|
||||||
|
int fp_suppressed = 0;
|
||||||
|
u8 *sense = NULL;
|
||||||
int expires;
|
int expires;
|
||||||
|
|
||||||
if (IS_ERR(irb)) {
|
if (IS_ERR(irb)) {
|
||||||
@@ -1673,7 +1676,23 @@ void dasd_int_handler(struct ccw_device *cdev, unsigned long intparm,
|
|||||||
dasd_put_device(device);
|
dasd_put_device(device);
|
||||||
return;
|
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");
|
device->discipline->dump_sense_dbf(device, irb, "int");
|
||||||
|
|
||||||
if (device->features & DASD_FEATURE_ERPLOG)
|
if (device->features & DASD_FEATURE_ERPLOG)
|
||||||
device->discipline->dump_sense(device, cqr, irb);
|
device->discipline->dump_sense(device, cqr, irb);
|
||||||
device->discipline->check_for_device_change(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_device *device;
|
||||||
struct dasd_ccw_req *cqr, *n;
|
struct dasd_ccw_req *cqr, *n;
|
||||||
|
u8 *sense = NULL;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
retry:
|
retry:
|
||||||
@@ -2357,6 +2377,20 @@ retry:
|
|||||||
|
|
||||||
rc = 0;
|
rc = 0;
|
||||||
list_for_each_entry_safe(cqr, n, ccw_queue, blocklist) {
|
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
|
* for alias devices simplify error recovery and
|
||||||
* return to upper layer
|
* return to upper layer
|
||||||
|
|||||||
@@ -1367,6 +1367,12 @@ dasd_3990_erp_no_rec(struct dasd_ccw_req * default_erp, char *sense)
|
|||||||
|
|
||||||
struct dasd_device *device = default_erp->startdev;
|
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,
|
dev_err(&device->cdev->dev,
|
||||||
"The specified record was not found\n");
|
"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;
|
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);
|
return dasd_3990_erp_cleanup(erp, DASD_CQR_FAILED);
|
||||||
|
|
||||||
|
|||||||
@@ -121,6 +121,11 @@ struct check_attention_work_data {
|
|||||||
__u8 lpum;
|
__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
|
/* initial attempt at a probe function. this can be simplified once
|
||||||
* the other detection code is gone */
|
* the other detection code is gone */
|
||||||
static int
|
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_CKD_MT:
|
||||||
case DASD_ECKD_CCW_READ_KD:
|
case DASD_ECKD_CCW_READ_KD:
|
||||||
case DASD_ECKD_CCW_READ_KD_MT:
|
case DASD_ECKD_CCW_READ_KD_MT:
|
||||||
case DASD_ECKD_CCW_READ_COUNT:
|
|
||||||
data->mask.perm = 0x1;
|
data->mask.perm = 0x1;
|
||||||
data->attributes.operation = private->attrib.operation;
|
data->attributes.operation = private->attrib.operation;
|
||||||
break;
|
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:
|
||||||
case DASD_ECKD_CCW_WRITE_MT:
|
case DASD_ECKD_CCW_WRITE_MT:
|
||||||
case DASD_ECKD_CCW_WRITE_KD:
|
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_CKD_MT:
|
||||||
case DASD_ECKD_CCW_READ_KD:
|
case DASD_ECKD_CCW_READ_KD:
|
||||||
case DASD_ECKD_CCW_READ_KD_MT:
|
case DASD_ECKD_CCW_READ_KD_MT:
|
||||||
case DASD_ECKD_CCW_READ_COUNT:
|
|
||||||
dedata->mask.perm = 0x1;
|
dedata->mask.perm = 0x1;
|
||||||
dedata->attributes.operation = basepriv->attrib.operation;
|
dedata->attributes.operation = basepriv->attrib.operation;
|
||||||
break;
|
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:
|
||||||
case DASD_ECKD_CCW_READ_TRACK_DATA:
|
case DASD_ECKD_CCW_READ_TRACK_DATA:
|
||||||
dedata->mask.perm = 0x1;
|
dedata->mask.perm = 0x1;
|
||||||
@@ -2096,6 +2107,180 @@ dasd_eckd_fill_geometry(struct dasd_block *block, struct hd_geometry *geo)
|
|||||||
return 0;
|
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 *
|
static struct dasd_ccw_req *
|
||||||
dasd_eckd_build_format(struct dasd_device *base,
|
dasd_eckd_build_format(struct dasd_device *base,
|
||||||
struct format_data_t *fdata,
|
struct format_data_t *fdata,
|
||||||
@@ -2363,9 +2548,24 @@ dasd_eckd_build_format(struct dasd_device *base,
|
|||||||
*/
|
*/
|
||||||
static struct dasd_ccw_req *
|
static struct dasd_ccw_req *
|
||||||
dasd_eckd_format_build_ccw_req(struct dasd_device *base,
|
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,
|
static int dasd_eckd_format_process_data(struct dasd_device *base,
|
||||||
struct format_data_t *fdata,
|
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_eckd_private *private = base->private;
|
||||||
struct dasd_ccw_req *cqr, *n;
|
struct dasd_ccw_req *cqr, *n;
|
||||||
struct list_head format_queue;
|
struct list_head format_queue;
|
||||||
struct dasd_device *device;
|
struct dasd_device *device;
|
||||||
|
char *sense = NULL;
|
||||||
int old_start, old_stop, format_step;
|
int old_start, old_stop, format_step;
|
||||||
int step, retry;
|
int step, retry;
|
||||||
int rc;
|
int rc;
|
||||||
@@ -2429,8 +2632,18 @@ static int dasd_eckd_format_process_data(struct dasd_device *base,
|
|||||||
old_start = fdata->start_unit;
|
old_start = fdata->start_unit;
|
||||||
old_stop = fdata->stop_unit;
|
old_stop = fdata->stop_unit;
|
||||||
|
|
||||||
format_step = DASD_CQR_MAX_CCW / recs_per_track(&private->rdc_data, 0,
|
if (!tpm && fmt_buffer != NULL) {
|
||||||
fdata->blksize);
|
/* 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 {
|
do {
|
||||||
retry = 0;
|
retry = 0;
|
||||||
while (fdata->start_unit <= old_stop) {
|
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,
|
cqr = dasd_eckd_format_build_ccw_req(base, fdata,
|
||||||
enable_pav);
|
enable_pav, tpm,
|
||||||
|
fmt_buffer, rpt);
|
||||||
if (IS_ERR(cqr)) {
|
if (IS_ERR(cqr)) {
|
||||||
rc = PTR_ERR(cqr);
|
rc = PTR_ERR(cqr);
|
||||||
if (rc == -ENOMEM) {
|
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);
|
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->start_unit = fdata->stop_unit + 1;
|
||||||
fdata->stop_unit = old_stop;
|
fdata->stop_unit = old_stop;
|
||||||
}
|
}
|
||||||
@@ -2469,15 +2687,41 @@ out_err:
|
|||||||
list_for_each_entry_safe(cqr, n, &format_queue, blocklist) {
|
list_for_each_entry_safe(cqr, n, &format_queue, blocklist) {
|
||||||
device = cqr->startdev;
|
device = cqr->startdev;
|
||||||
private = device->private;
|
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;
|
rc = -EIO;
|
||||||
|
}
|
||||||
list_del_init(&cqr->blocklist);
|
list_del_init(&cqr->blocklist);
|
||||||
dasd_sfree_request(cqr, device);
|
dasd_sfree_request(cqr, device);
|
||||||
private->count--;
|
private->count--;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rc)
|
if (rc && rc != -EIO)
|
||||||
goto out;
|
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);
|
} while (retry);
|
||||||
|
|
||||||
@@ -2491,7 +2735,225 @@ out:
|
|||||||
static int dasd_eckd_format_device(struct dasd_device *base,
|
static int dasd_eckd_format_device(struct dasd_device *base,
|
||||||
struct format_data_t *fdata, int enable_pav)
|
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)
|
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;
|
lredata->auxiliary.check_bytes = 0x2;
|
||||||
pfx_cmd = DASD_ECKD_CCW_PFX;
|
pfx_cmd = DASD_ECKD_CCW_PFX;
|
||||||
break;
|
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:
|
default:
|
||||||
DBF_DEV_EVENT(DBF_ERR, basedev,
|
DBF_DEV_EVENT(DBF_ERR, basedev,
|
||||||
"prepare itcw, unknown opcode 0x%x", cmd);
|
"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_valid = 1;
|
||||||
lredata->auxiliary.length_scope = 1;
|
lredata->auxiliary.length_scope = 1;
|
||||||
|
lredata->sector = sector;
|
||||||
|
}
|
||||||
lredata->auxiliary.imbedded_ccw_valid = 1;
|
lredata->auxiliary.imbedded_ccw_valid = 1;
|
||||||
lredata->length = tlf;
|
lredata->length = tlf;
|
||||||
lredata->imbedded_ccw = cmd;
|
lredata->imbedded_ccw = cmd;
|
||||||
lredata->count = count;
|
lredata->count = count;
|
||||||
lredata->sector = sector;
|
|
||||||
set_ch_t(&lredata->seek_addr, begcyl, beghead);
|
set_ch_t(&lredata->seek_addr, begcyl, beghead);
|
||||||
lredata->search_arg.cyl = lredata->seek_addr.cyl;
|
lredata->search_arg.cyl = lredata->seek_addr.cyl;
|
||||||
lredata->search_arg.head = lredata->seek_addr.head;
|
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,
|
static void dasd_eckd_dump_sense(struct dasd_device *device,
|
||||||
struct dasd_ccw_req *req, struct irb *irb)
|
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);
|
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);
|
dasd_eckd_dump_sense_ccw(device, req, irb);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static int dasd_eckd_pm_freeze(struct dasd_device *device)
|
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,
|
.term_IO = dasd_term_IO,
|
||||||
.handle_terminated_request = dasd_eckd_handle_terminated_request,
|
.handle_terminated_request = dasd_eckd_handle_terminated_request,
|
||||||
.format_device = dasd_eckd_format_device,
|
.format_device = dasd_eckd_format_device,
|
||||||
|
.check_device_format = dasd_eckd_check_device_format,
|
||||||
.erp_action = dasd_eckd_erp_action,
|
.erp_action = dasd_eckd_erp_action,
|
||||||
.erp_postaction = dasd_eckd_erp_postaction,
|
.erp_postaction = dasd_eckd_erp_postaction,
|
||||||
.check_for_device_change = dasd_eckd_check_for_device_change,
|
.check_for_device_change = dasd_eckd_check_for_device_change,
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
#define DASD_ECKD_CCW_READ_MT 0x86
|
#define DASD_ECKD_CCW_READ_MT 0x86
|
||||||
#define DASD_ECKD_CCW_WRITE_KD_MT 0x8d
|
#define DASD_ECKD_CCW_WRITE_KD_MT 0x8d
|
||||||
#define DASD_ECKD_CCW_READ_KD_MT 0x8e
|
#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_RELEASE 0x94
|
||||||
#define DASD_ECKD_CCW_WRITE_FULL_TRACK 0x95
|
#define DASD_ECKD_CCW_WRITE_FULL_TRACK 0x95
|
||||||
#define DASD_ECKD_CCW_READ_CKD_MT 0x9e
|
#define DASD_ECKD_CCW_READ_CKD_MT 0x9e
|
||||||
|
|||||||
@@ -236,6 +236,13 @@ struct dasd_ccw_req {
|
|||||||
* stolen. Should not be combined with
|
* stolen. Should not be combined with
|
||||||
* DASD_CQR_FLAGS_USE_ERP
|
* 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. */
|
/* Signature for error recovery functions. */
|
||||||
typedef struct dasd_ccw_req *(*dasd_erp_fn_t) (struct dasd_ccw_req *);
|
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
|
* Device operation functions. build_cp creates a ccw chain for
|
||||||
* a block device request, start_io starts the request and
|
* a block device request, start_io starts the request and
|
||||||
* term_IO cancels it (e.g. in case of a timeout). format_device
|
* 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
|
* handle_terminated_request allows to examine a cqr and prepare
|
||||||
* it for retry.
|
* it for retry.
|
||||||
*/
|
*/
|
||||||
@@ -329,7 +337,9 @@ struct dasd_discipline {
|
|||||||
int (*term_IO) (struct dasd_ccw_req *);
|
int (*term_IO) (struct dasd_ccw_req *);
|
||||||
void (*handle_terminated_request) (struct dasd_ccw_req *);
|
void (*handle_terminated_request) (struct dasd_ccw_req *);
|
||||||
int (*format_device) (struct dasd_device *,
|
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 *);
|
int (*free_cp) (struct dasd_ccw_req *, struct request *);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -238,6 +238,23 @@ dasd_format(struct dasd_block *block, struct format_data_t *fdata)
|
|||||||
return rc;
|
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.
|
* Format device.
|
||||||
*/
|
*/
|
||||||
@@ -272,6 +289,47 @@ dasd_ioctl_format(struct block_device *bdev, void __user *argp)
|
|||||||
}
|
}
|
||||||
rc = dasd_format(base->block, &fdata);
|
rc = dasd_format(base->block, &fdata);
|
||||||
dasd_put_device(base);
|
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;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -519,6 +577,9 @@ int dasd_ioctl(struct block_device *bdev, fmode_t mode,
|
|||||||
case BIODASDFMT:
|
case BIODASDFMT:
|
||||||
rc = dasd_ioctl_format(bdev, argp);
|
rc = dasd_ioctl_format(bdev, argp);
|
||||||
break;
|
break;
|
||||||
|
case BIODASDCHECKFMT:
|
||||||
|
rc = dasd_ioctl_check_format(bdev, argp);
|
||||||
|
break;
|
||||||
case BIODASDINFO:
|
case BIODASDINFO:
|
||||||
rc = dasd_ioctl_information(block, cmd, argp);
|
rc = dasd_ioctl_information(block, cmd, argp);
|
||||||
break;
|
break;
|
||||||
|
|||||||
Reference in New Issue
Block a user