Commit 5e2b17e7 authored by Jan Höppner's avatar Jan Höppner Committed by Vasily Gorbik

s390/dasd: Add dynamic formatting support for ESE volumes

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: default avatarJan Höppner <hoeppner@linux.ibm.com>
Reviewed-by: default avatarStefan Haberland <sth@linux.ibm.com>
Signed-off-by: default avatarVasily Gorbik <gor@linux.ibm.com>
parent c729696b
...@@ -120,9 +120,18 @@ struct dasd_device *dasd_alloc_device(void) ...@@ -120,9 +120,18 @@ struct dasd_device *dasd_alloc_device(void)
kfree(device); kfree(device);
return ERR_PTR(-ENOMEM); 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->ccw_chunks, device->ccw_mem, PAGE_SIZE*2);
dasd_init_chunklist(&device->erp_chunks, device->erp_mem, PAGE_SIZE); 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); spin_lock_init(&device->mem_lock);
atomic_set(&device->tasklet_scheduled, 0); atomic_set(&device->tasklet_scheduled, 0);
tasklet_init(&device->tasklet, dasd_device_tasklet, tasklet_init(&device->tasklet, dasd_device_tasklet,
...@@ -146,6 +155,7 @@ struct dasd_device *dasd_alloc_device(void) ...@@ -146,6 +155,7 @@ struct dasd_device *dasd_alloc_device(void)
void dasd_free_device(struct dasd_device *device) void dasd_free_device(struct dasd_device *device)
{ {
kfree(device->private); kfree(device->private);
free_pages((unsigned long) device->ese_mem, 1);
free_page((unsigned long) device->erp_mem); free_page((unsigned long) device->erp_mem);
free_pages((unsigned long) device->ccw_mem, 1); free_pages((unsigned long) device->ccw_mem, 1);
kfree(device); kfree(device);
...@@ -1258,6 +1268,49 @@ struct dasd_ccw_req *dasd_smalloc_request(int magic, int cplength, int datasize, ...@@ -1258,6 +1268,49 @@ struct dasd_ccw_req *dasd_smalloc_request(int magic, int cplength, int datasize,
} }
EXPORT_SYMBOL(dasd_smalloc_request); 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) void dasd_sfree_request(struct dasd_ccw_req *cqr, struct dasd_device *device)
{ {
unsigned long flags; unsigned long flags;
...@@ -1269,6 +1322,17 @@ void dasd_sfree_request(struct dasd_ccw_req *cqr, struct dasd_device *device) ...@@ -1269,6 +1322,17 @@ void dasd_sfree_request(struct dasd_ccw_req *cqr, struct dasd_device *device)
} }
EXPORT_SYMBOL(dasd_sfree_request); 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. * Check discipline magic in cqr.
*/ */
...@@ -1573,13 +1637,35 @@ static int dasd_check_hpf_error(struct irb *irb) ...@@ -1573,13 +1637,35 @@ static int dasd_check_hpf_error(struct irb *irb)
irb->scsw.tm.sesq == SCSW_SESQ_PATH_NOFCX)); 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. * Interrupt handler for "normal" ssch-io based dasd devices.
*/ */
void dasd_int_handler(struct ccw_device *cdev, unsigned long intparm, void dasd_int_handler(struct ccw_device *cdev, unsigned long intparm,
struct irb *irb) struct irb *irb)
{ {
struct dasd_ccw_req *cqr, *next; struct dasd_ccw_req *cqr, *next, *fcqr;
struct dasd_device *device; struct dasd_device *device;
unsigned long now; unsigned long now;
int nrf_suppressed = 0; int nrf_suppressed = 0;
...@@ -1672,6 +1758,31 @@ void dasd_int_handler(struct ccw_device *cdev, unsigned long intparm, ...@@ -1672,6 +1758,31 @@ void dasd_int_handler(struct ccw_device *cdev, unsigned long intparm,
return; 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 */ /* Check for clear pending */
if (cqr->status == DASD_CQR_CLEAR_PENDING && if (cqr->status == DASD_CQR_CLEAR_PENDING &&
scsw_fctl(&irb->scsw) & SCSW_FCTL_CLEAR_FUNC) { scsw_fctl(&irb->scsw) & SCSW_FCTL_CLEAR_FUNC) {
......
...@@ -2335,8 +2335,7 @@ dasd_eckd_build_check_tcw(struct dasd_device *base, struct format_data_t *fdata, ...@@ -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); itcw_size = itcw_calc_size(0, count, 0);
cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 0, itcw_size, startdev, cqr = dasd_fmalloc_request(DASD_ECKD_MAGIC, 0, itcw_size, startdev);
NULL);
if (IS_ERR(cqr)) if (IS_ERR(cqr))
return cqr; return cqr;
...@@ -2429,8 +2428,7 @@ dasd_eckd_build_check(struct dasd_device *base, struct format_data_t *fdata, ...@@ -2429,8 +2428,7 @@ dasd_eckd_build_check(struct dasd_device *base, struct format_data_t *fdata,
} }
cplength += count; cplength += count;
cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, cplength, datasize, cqr = dasd_fmalloc_request(DASD_ECKD_MAGIC, cplength, datasize, startdev);
startdev, NULL);
if (IS_ERR(cqr)) if (IS_ERR(cqr))
return cqr; return cqr;
...@@ -2477,13 +2475,11 @@ dasd_eckd_build_check(struct dasd_device *base, struct format_data_t *fdata, ...@@ -2477,13 +2475,11 @@ dasd_eckd_build_check(struct dasd_device *base, struct format_data_t *fdata,
} }
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 dasd_device *startdev,
struct format_data_t *fdata, struct format_data_t *fdata, int enable_pav)
int enable_pav)
{ {
struct dasd_eckd_private *base_priv; struct dasd_eckd_private *base_priv;
struct dasd_eckd_private *start_priv; struct dasd_eckd_private *start_priv;
struct dasd_device *startdev = NULL;
struct dasd_ccw_req *fcp; struct dasd_ccw_req *fcp;
struct eckd_count *ect; struct eckd_count *ect;
struct ch_t address; struct ch_t address;
...@@ -2574,9 +2570,8 @@ dasd_eckd_build_format(struct dasd_device *base, ...@@ -2574,9 +2570,8 @@ dasd_eckd_build_format(struct dasd_device *base,
fdata->intensity); fdata->intensity);
return ERR_PTR(-EINVAL); return ERR_PTR(-EINVAL);
} }
/* Allocate the format ccw request. */
fcp = dasd_smalloc_request(DASD_ECKD_MAGIC, cplength, fcp = dasd_fmalloc_request(DASD_ECKD_MAGIC, cplength, datasize, startdev);
datasize, startdev, NULL);
if (IS_ERR(fcp)) if (IS_ERR(fcp))
return fcp; return fcp;
...@@ -2749,7 +2744,7 @@ dasd_eckd_format_build_ccw_req(struct dasd_device *base, ...@@ -2749,7 +2744,7 @@ dasd_eckd_format_build_ccw_req(struct dasd_device *base,
struct dasd_ccw_req *ccw_req; struct dasd_ccw_req *ccw_req;
if (!fmt_buffer) { 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 { } else {
if (tpm) if (tpm)
ccw_req = dasd_eckd_build_check_tcw(base, fdata, ccw_req = dasd_eckd_build_check_tcw(base, fdata,
...@@ -2895,7 +2890,7 @@ static int dasd_eckd_format_process_data(struct dasd_device *base, ...@@ -2895,7 +2890,7 @@ static int dasd_eckd_format_process_data(struct dasd_device *base,
rc = -EIO; rc = -EIO;
} }
list_del_init(&cqr->blocklist); list_del_init(&cqr->blocklist);
dasd_sfree_request(cqr, device); dasd_ffree_request(cqr, device);
private->count--; private->count--;
} }
...@@ -2934,6 +2929,96 @@ static int dasd_eckd_format_device(struct dasd_device *base, ...@@ -2934,6 +2929,96 @@ static int dasd_eckd_format_device(struct dasd_device *base,
0, NULL); 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. * Helper function to count consecutive records of a single track.
*/ */
...@@ -3450,6 +3535,14 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_cmd_single( ...@@ -3450,6 +3535,14 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_cmd_single(
cqr->retries = startdev->default_retries; cqr->retries = startdev->default_retries;
cqr->buildclk = get_tod_clock(); cqr->buildclk = get_tod_clock();
cqr->status = DASD_CQR_FILLED; 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; return cqr;
} }
...@@ -3621,6 +3714,11 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_cmd_track( ...@@ -3621,6 +3714,11 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_cmd_track(
cqr->retries = startdev->default_retries; cqr->retries = startdev->default_retries;
cqr->buildclk = get_tod_clock(); cqr->buildclk = get_tod_clock();
cqr->status = DASD_CQR_FILLED; 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; return cqr;
} }
...@@ -3940,6 +4038,14 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_tpm_track( ...@@ -3940,6 +4038,14 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_tpm_track(
cqr->retries = startdev->default_retries; cqr->retries = startdev->default_retries;
cqr->buildclk = get_tod_clock(); cqr->buildclk = get_tod_clock();
cqr->status = DASD_CQR_FILLED; 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; return cqr;
out_error: out_error:
dasd_sfree_request(cqr, startdev); dasd_sfree_request(cqr, startdev);
...@@ -6061,6 +6167,8 @@ static struct dasd_discipline dasd_eckd_discipline = { ...@@ -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_cap_at_warnlevel = dasd_eckd_ext_pool_cap_at_warnlevel,
.ext_pool_warn_thrshld = dasd_eckd_ext_pool_warn_thrshld, .ext_pool_warn_thrshld = dasd_eckd_ext_pool_warn_thrshld,
.ext_pool_oos = dasd_eckd_ext_pool_oos, .ext_pool_oos = dasd_eckd_ext_pool_oos,
.ese_format = dasd_eckd_ese_format,
.ese_read = dasd_eckd_ese_read,
}; };
static int __init static int __init
......
...@@ -382,6 +382,8 @@ struct dasd_discipline { ...@@ -382,6 +382,8 @@ struct dasd_discipline {
int (*ext_pool_cap_at_warnlevel)(struct dasd_device *); int (*ext_pool_cap_at_warnlevel)(struct dasd_device *);
int (*ext_pool_warn_thrshld)(struct dasd_device *); int (*ext_pool_warn_thrshld)(struct dasd_device *);
int (*ext_pool_oos)(struct dasd_device *); int (*ext_pool_oos)(struct dasd_device *);
struct dasd_ccw_req *(*ese_format)(struct dasd_device *, struct dasd_ccw_req *);
void (*ese_read)(struct dasd_ccw_req *);
}; };
extern struct dasd_discipline *dasd_diag_discipline_pointer; extern struct dasd_discipline *dasd_diag_discipline_pointer;
...@@ -497,8 +499,10 @@ struct dasd_device { ...@@ -497,8 +499,10 @@ struct dasd_device {
spinlock_t mem_lock; spinlock_t mem_lock;
void *ccw_mem; void *ccw_mem;
void *erp_mem; void *erp_mem;
void *ese_mem;
struct list_head ccw_chunks; struct list_head ccw_chunks;
struct list_head erp_chunks; struct list_head erp_chunks;
struct list_head ese_chunks;
atomic_t tasklet_scheduled; atomic_t tasklet_scheduled;
struct tasklet_struct tasklet; struct tasklet_struct tasklet;
...@@ -715,7 +719,9 @@ extern struct kmem_cache *dasd_page_cache; ...@@ -715,7 +719,9 @@ extern struct kmem_cache *dasd_page_cache;
struct dasd_ccw_req * struct dasd_ccw_req *
dasd_smalloc_request(int, int, int, struct dasd_device *, struct dasd_ccw_req *); dasd_smalloc_request(int, int, int, struct dasd_device *, struct dasd_ccw_req *);
struct dasd_ccw_req *dasd_fmalloc_request(int, int, int, struct dasd_device *);
void dasd_sfree_request(struct dasd_ccw_req *, struct dasd_device *); void dasd_sfree_request(struct dasd_ccw_req *, struct dasd_device *);
void dasd_ffree_request(struct dasd_ccw_req *, struct dasd_device *);
void dasd_wakeup_cb(struct dasd_ccw_req *, void *); void dasd_wakeup_cb(struct dasd_ccw_req *, void *);
struct dasd_device *dasd_alloc_device(void); struct dasd_device *dasd_alloc_device(void);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment