Skip to content

Commit

Permalink
s390/dasd: Add dynamic formatting support for ESE volumes
Browse files Browse the repository at this point in the history
A dynamic formatting is issued whenever a write request returns with
either a No Record Found error (Command Mode), Incorrect Length error
(Transport Mode), or File Protected error (Transport Mode). All three
cases mean that the tracks in question haven't been initialized in a
desired format yet.

The part of the volume that was tried to be written on is then formatted
and the original request is re-queued.

As the formatting will happen during normal I/O operations, it is quite
likely that there won't be any memory available to build the respective
request. Another two pages of memory are allocated per volume
specifically for the dynamic formatting.

The dasd_eckd_build_format() function is extended to make sure that the
original startdev is reused. Also, all formatting and format check
functions use the new memory pool exclusively now to reduce complexity.

Read operations will always return zero data when unformatted areas are
read.

Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
Reviewed-by: Stefan Haberland <sth@linux.ibm.com>
Signed-off-by: Vasily Gorbik <gor@linux.ibm.com>
  • Loading branch information
hoeppnerj authored and Vasily Gorbik committed Jul 11, 2019
1 parent c729696 commit 5e2b17e
Show file tree
Hide file tree
Showing 3 changed files with 239 additions and 14 deletions.
113 changes: 112 additions & 1 deletion drivers/s390/block/dasd.c
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,18 @@ struct dasd_device *dasd_alloc_device(void)
kfree(device);
return ERR_PTR(-ENOMEM);
}
/* Get two pages for ese format. */
device->ese_mem = (void *)__get_free_pages(GFP_ATOMIC | GFP_DMA, 1);
if (!device->ese_mem) {
free_page((unsigned long) device->erp_mem);
free_pages((unsigned long) device->ccw_mem, 1);
kfree(device);
return ERR_PTR(-ENOMEM);
}

dasd_init_chunklist(&device->ccw_chunks, device->ccw_mem, PAGE_SIZE*2);
dasd_init_chunklist(&device->erp_chunks, device->erp_mem, PAGE_SIZE);
dasd_init_chunklist(&device->ese_chunks, device->ese_mem, PAGE_SIZE * 2);
spin_lock_init(&device->mem_lock);
atomic_set(&device->tasklet_scheduled, 0);
tasklet_init(&device->tasklet, dasd_device_tasklet,
Expand All @@ -146,6 +155,7 @@ struct dasd_device *dasd_alloc_device(void)
void dasd_free_device(struct dasd_device *device)
{
kfree(device->private);
free_pages((unsigned long) device->ese_mem, 1);
free_page((unsigned long) device->erp_mem);
free_pages((unsigned long) device->ccw_mem, 1);
kfree(device);
Expand Down Expand Up @@ -1258,6 +1268,49 @@ struct dasd_ccw_req *dasd_smalloc_request(int magic, int cplength, int datasize,
}
EXPORT_SYMBOL(dasd_smalloc_request);

struct dasd_ccw_req *dasd_fmalloc_request(int magic, int cplength,
int datasize,
struct dasd_device *device)
{
struct dasd_ccw_req *cqr;
unsigned long flags;
int size, cqr_size;
char *data;

cqr_size = (sizeof(*cqr) + 7L) & -8L;
size = cqr_size;
if (cplength > 0)
size += cplength * sizeof(struct ccw1);
if (datasize > 0)
size += datasize;

spin_lock_irqsave(&device->mem_lock, flags);
cqr = dasd_alloc_chunk(&device->ese_chunks, size);
spin_unlock_irqrestore(&device->mem_lock, flags);
if (!cqr)
return ERR_PTR(-ENOMEM);
memset(cqr, 0, sizeof(*cqr));
data = (char *)cqr + cqr_size;
cqr->cpaddr = NULL;
if (cplength > 0) {
cqr->cpaddr = data;
data += cplength * sizeof(struct ccw1);
memset(cqr->cpaddr, 0, cplength * sizeof(struct ccw1));
}
cqr->data = NULL;
if (datasize > 0) {
cqr->data = data;
memset(cqr->data, 0, datasize);
}

cqr->magic = magic;
set_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags);
dasd_get_device(device);

return cqr;
}
EXPORT_SYMBOL(dasd_fmalloc_request);

void dasd_sfree_request(struct dasd_ccw_req *cqr, struct dasd_device *device)
{
unsigned long flags;
Expand All @@ -1269,6 +1322,17 @@ void dasd_sfree_request(struct dasd_ccw_req *cqr, struct dasd_device *device)
}
EXPORT_SYMBOL(dasd_sfree_request);

void dasd_ffree_request(struct dasd_ccw_req *cqr, struct dasd_device *device)
{
unsigned long flags;

spin_lock_irqsave(&device->mem_lock, flags);
dasd_free_chunk(&device->ese_chunks, cqr);
spin_unlock_irqrestore(&device->mem_lock, flags);
dasd_put_device(device);
}
EXPORT_SYMBOL(dasd_ffree_request);

/*
* Check discipline magic in cqr.
*/
Expand Down Expand Up @@ -1573,13 +1637,35 @@ static int dasd_check_hpf_error(struct irb *irb)
irb->scsw.tm.sesq == SCSW_SESQ_PATH_NOFCX));
}

static int dasd_ese_needs_format(struct dasd_block *block, struct irb *irb)
{
struct dasd_device *device = NULL;
u8 *sense = NULL;

if (!block)
return 0;
device = block->base;
if (!device || !device->discipline->is_ese)
return 0;
if (!device->discipline->is_ese(device))
return 0;

sense = dasd_get_sense(irb);
if (!sense)
return 0;

return !!(sense[1] & SNS1_NO_REC_FOUND) ||
!!(sense[1] & SNS1_FILE_PROTECTED) ||
scsw_cstat(&irb->scsw) == SCHN_STAT_INCORR_LEN;
}

/*
* Interrupt handler for "normal" ssch-io based dasd devices.
*/
void dasd_int_handler(struct ccw_device *cdev, unsigned long intparm,
struct irb *irb)
{
struct dasd_ccw_req *cqr, *next;
struct dasd_ccw_req *cqr, *next, *fcqr;
struct dasd_device *device;
unsigned long now;
int nrf_suppressed = 0;
Expand Down Expand Up @@ -1672,6 +1758,31 @@ void dasd_int_handler(struct ccw_device *cdev, unsigned long intparm,
return;
}

if (dasd_ese_needs_format(cqr->block, irb)) {
if (rq_data_dir((struct request *)cqr->callback_data) == READ) {
device->discipline->ese_read(cqr);
cqr->status = DASD_CQR_SUCCESS;
cqr->stopclk = now;
dasd_device_clear_timer(device);
dasd_schedule_device_bh(device);
return;
}
fcqr = device->discipline->ese_format(device, cqr);
if (IS_ERR(fcqr)) {
/*
* If we can't format now, let the request go
* one extra round. Maybe we can format later.
*/
cqr->status = DASD_CQR_QUEUED;
} else {
fcqr->status = DASD_CQR_QUEUED;
cqr->status = DASD_CQR_QUEUED;
list_add(&fcqr->devlist, &device->ccw_queue);
dasd_schedule_device_bh(device);
return;
}
}

/* Check for clear pending */
if (cqr->status == DASD_CQR_CLEAR_PENDING &&
scsw_fctl(&irb->scsw) & SCSW_FCTL_CLEAR_FUNC) {
Expand Down
134 changes: 121 additions & 13 deletions drivers/s390/block/dasd_eckd.c
Original file line number Diff line number Diff line change
Expand Up @@ -2335,8 +2335,7 @@ dasd_eckd_build_check_tcw(struct dasd_device *base, struct format_data_t *fdata,
*/
itcw_size = itcw_calc_size(0, count, 0);

cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 0, itcw_size, startdev,
NULL);
cqr = dasd_fmalloc_request(DASD_ECKD_MAGIC, 0, itcw_size, startdev);
if (IS_ERR(cqr))
return cqr;

Expand Down Expand Up @@ -2429,8 +2428,7 @@ dasd_eckd_build_check(struct dasd_device *base, struct format_data_t *fdata,
}
cplength += count;

cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, cplength, datasize,
startdev, NULL);
cqr = dasd_fmalloc_request(DASD_ECKD_MAGIC, cplength, datasize, startdev);
if (IS_ERR(cqr))
return cqr;

Expand Down Expand Up @@ -2477,13 +2475,11 @@ dasd_eckd_build_check(struct dasd_device *base, struct format_data_t *fdata,
}

static struct dasd_ccw_req *
dasd_eckd_build_format(struct dasd_device *base,
struct format_data_t *fdata,
int enable_pav)
dasd_eckd_build_format(struct dasd_device *base, struct dasd_device *startdev,
struct format_data_t *fdata, int enable_pav)
{
struct dasd_eckd_private *base_priv;
struct dasd_eckd_private *start_priv;
struct dasd_device *startdev = NULL;
struct dasd_ccw_req *fcp;
struct eckd_count *ect;
struct ch_t address;
Expand Down Expand Up @@ -2574,9 +2570,8 @@ dasd_eckd_build_format(struct dasd_device *base,
fdata->intensity);
return ERR_PTR(-EINVAL);
}
/* Allocate the format ccw request. */
fcp = dasd_smalloc_request(DASD_ECKD_MAGIC, cplength,
datasize, startdev, NULL);

fcp = dasd_fmalloc_request(DASD_ECKD_MAGIC, cplength, datasize, startdev);
if (IS_ERR(fcp))
return fcp;

Expand Down Expand Up @@ -2749,7 +2744,7 @@ dasd_eckd_format_build_ccw_req(struct dasd_device *base,
struct dasd_ccw_req *ccw_req;

if (!fmt_buffer) {
ccw_req = dasd_eckd_build_format(base, fdata, enable_pav);
ccw_req = dasd_eckd_build_format(base, NULL, fdata, enable_pav);
} else {
if (tpm)
ccw_req = dasd_eckd_build_check_tcw(base, fdata,
Expand Down Expand Up @@ -2895,7 +2890,7 @@ static int dasd_eckd_format_process_data(struct dasd_device *base,
rc = -EIO;
}
list_del_init(&cqr->blocklist);
dasd_sfree_request(cqr, device);
dasd_ffree_request(cqr, device);
private->count--;
}

Expand Down Expand Up @@ -2934,6 +2929,96 @@ static int dasd_eckd_format_device(struct dasd_device *base,
0, NULL);
}

/*
* Callback function to free ESE format requests.
*/
static void dasd_eckd_ese_format_cb(struct dasd_ccw_req *cqr, void *data)
{
struct dasd_device *device = cqr->startdev;
struct dasd_eckd_private *private = device->private;

private->count--;
dasd_ffree_request(cqr, device);
}

static struct dasd_ccw_req *
dasd_eckd_ese_format(struct dasd_device *startdev, struct dasd_ccw_req *cqr)
{
struct dasd_eckd_private *private;
struct format_data_t fdata;
unsigned int recs_per_trk;
struct dasd_ccw_req *fcqr;
struct dasd_device *base;
struct dasd_block *block;
unsigned int blksize;
struct request *req;
sector_t first_trk;
sector_t last_trk;
int rc;

req = cqr->callback_data;
base = cqr->block->base;
private = base->private;
block = base->block;
blksize = block->bp_block;
recs_per_trk = recs_per_track(&private->rdc_data, 0, blksize);

first_trk = blk_rq_pos(req) >> block->s2b_shift;
sector_div(first_trk, recs_per_trk);
last_trk =
(blk_rq_pos(req) + blk_rq_sectors(req) - 1) >> block->s2b_shift;
sector_div(last_trk, recs_per_trk);

fdata.start_unit = first_trk;
fdata.stop_unit = last_trk;
fdata.blksize = blksize;
fdata.intensity = private->uses_cdl ? DASD_FMT_INT_COMPAT : 0;

rc = dasd_eckd_format_sanity_checks(base, &fdata);
if (rc)
return ERR_PTR(-EINVAL);

/*
* We're building the request with PAV disabled as we're reusing
* the former startdev.
*/
fcqr = dasd_eckd_build_format(base, startdev, &fdata, 0);
if (IS_ERR(fcqr))
return fcqr;

fcqr->callback = dasd_eckd_ese_format_cb;

return fcqr;
}

/*
* When data is read from an unformatted area of an ESE volume, this function
* returns zeroed data and thereby mimics a read of zero data.
*/
static void dasd_eckd_ese_read(struct dasd_ccw_req *cqr)
{
unsigned int blksize, off;
struct dasd_device *base;
struct req_iterator iter;
struct request *req;
struct bio_vec bv;
char *dst;

req = (struct request *) cqr->callback_data;
base = cqr->block->base;
blksize = base->block->bp_block;

rq_for_each_segment(bv, req, iter) {
dst = page_address(bv.bv_page) + bv.bv_offset;
for (off = 0; off < bv.bv_len; off += blksize) {
if (dst && rq_data_dir(req) == READ) {
dst += off;
memset(dst, 0, blksize);
}
}
}
}

/*
* Helper function to count consecutive records of a single track.
*/
Expand Down Expand Up @@ -3450,6 +3535,14 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_cmd_single(
cqr->retries = startdev->default_retries;
cqr->buildclk = get_tod_clock();
cqr->status = DASD_CQR_FILLED;

/* Set flags to suppress output for expected errors */
if (dasd_eckd_is_ese(basedev)) {
set_bit(DASD_CQR_SUPPRESS_FP, &cqr->flags);
set_bit(DASD_CQR_SUPPRESS_IL, &cqr->flags);
set_bit(DASD_CQR_SUPPRESS_NRF, &cqr->flags);
}

return cqr;
}

Expand Down Expand Up @@ -3621,6 +3714,11 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_cmd_track(
cqr->retries = startdev->default_retries;
cqr->buildclk = get_tod_clock();
cqr->status = DASD_CQR_FILLED;

/* Set flags to suppress output for expected errors */
if (dasd_eckd_is_ese(basedev))
set_bit(DASD_CQR_SUPPRESS_NRF, &cqr->flags);

return cqr;
}

Expand Down Expand Up @@ -3940,6 +4038,14 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_tpm_track(
cqr->retries = startdev->default_retries;
cqr->buildclk = get_tod_clock();
cqr->status = DASD_CQR_FILLED;

/* Set flags to suppress output for expected errors */
if (dasd_eckd_is_ese(basedev)) {
set_bit(DASD_CQR_SUPPRESS_FP, &cqr->flags);
set_bit(DASD_CQR_SUPPRESS_IL, &cqr->flags);
set_bit(DASD_CQR_SUPPRESS_NRF, &cqr->flags);
}

return cqr;
out_error:
dasd_sfree_request(cqr, startdev);
Expand Down Expand Up @@ -6061,6 +6167,8 @@ static struct dasd_discipline dasd_eckd_discipline = {
.ext_pool_cap_at_warnlevel = dasd_eckd_ext_pool_cap_at_warnlevel,
.ext_pool_warn_thrshld = dasd_eckd_ext_pool_warn_thrshld,
.ext_pool_oos = dasd_eckd_ext_pool_oos,
.ese_format = dasd_eckd_ese_format,
.ese_read = dasd_eckd_ese_read,
};

static int __init
Expand Down

0 comments on commit 5e2b17e

Please sign in to comment.