s390/dasd: Use WRITE_FULL_TRACK in ESE format handler

Wire dasd_eckd_build_cp_tpm_writefulltrack() into the ESE unformated
track handler.

dasd_eckd_ese_format() now returns void (matching the revised discipline
hook): it computes the failing track/record range, trims a partially
covered last track when several tracks are involved (the block layer
re-issues the remainder), claims the range with
test_and_set_format_track(), builds a writefulltrack CQR, copies
callback_data/proc_bytes from the origin, and stages it on
block->ese_staging. The origin CQR is set to DASD_CQR_ABORT so
__dasd_process_cqr() retires it without the normal completion.

Drop dasd_eckd_ese_format_cb(); the format-entry slot is now released by
dasd_eckd_free_alias_cp() via clear_format_track() when the CQR is freed.

dasd_int_handler() calls the void hook directly and, for writefulltrack
CQRs (cqr->filldata set), returns DASD_CQR_ERROR instead of looping on
the NRF.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Stefan Haberland <sth@linux.ibm.com>
Link: https://patch.msgid.link/20260805111612.1285190-13-sth@linux.ibm.com
Signed-off-by: Jens Axboe <axboe@kernel.dk>
This commit is contained in:
Stefan Haberland 2026-08-05 13:16:05 +02:00 committed by Jens Axboe
parent 123ec1e9cb
commit 791d257a21
5 changed files with 193 additions and 80 deletions

View File

@ -1617,7 +1617,7 @@ static int dasd_ese_oos_cond(u8 *sense)
void dasd_int_handler(struct ccw_device *cdev, unsigned long intparm,
struct irb *irb)
{
struct dasd_ccw_req *cqr, *next, *fcqr;
struct dasd_ccw_req *cqr, *next;
struct dasd_device *device;
unsigned long now;
int nrf_suppressed = 0;
@ -1739,26 +1739,23 @@ void dasd_int_handler(struct ccw_device *cdev, unsigned long intparm,
dasd_schedule_device_bh(device);
return;
}
fcqr = device->discipline->ese_format(device, cqr, irb);
if (IS_ERR(fcqr)) {
if (PTR_ERR(fcqr) == -EINVAL) {
cqr->status = DASD_CQR_ERROR;
return;
}
if (cqr->filldata) {
/*
* If we can't format now, let the request go
* one extra round. Maybe we can format later.
* A WRITE_FULL_TRACK cqr carries the complete
* track image; INV_TRACK_FORMAT here means the
* generated image or the media itself is bad, not
* that the track still needs formatting - retrying
* via ese_format() would just resubmit the same
* write. Let it fail instead.
*/
cqr->status = DASD_CQR_QUEUED;
dasd_schedule_device_bh(device);
return;
} else {
fcqr->status = DASD_CQR_QUEUED;
cqr->status = DASD_CQR_QUEUED;
list_add(&fcqr->devlist, &device->ccw_queue);
cqr->status = DASD_CQR_ERROR;
cqr->stopclk = now;
dasd_device_clear_timer(device);
dasd_schedule_device_bh(device);
return;
}
device->discipline->ese_format(device, cqr, irb);
return;
}
/* Check for clear pending */
@ -2721,6 +2718,13 @@ static void __dasd_process_erp(struct dasd_device *device,
if (cqr->status == DASD_CQR_DONE)
DBF_DEV_EVENT(DBF_NOTICE, device, "%s", "ERP successful");
else if (cqr->status == DASD_CQR_ABORTED)
/*
* ESE format aborts the request and replaces it with a format
* CQR - this is not an ERP failure.
*/
DBF_DEV_EVENT(DBF_NOTICE, device, "%s",
"ERP request aborted, replaced by ESE format");
else
dev_err(&device->cdev->dev, "ERP failed for the DASD\n");
erp_fn = device->discipline->erp_postaction(cqr);
@ -2767,6 +2771,9 @@ static void __dasd_cleanup_cqr(struct dasd_ccw_req *cqr)
error = BLK_STS_IOERR;
break;
}
} else if (status == DASD_CQR_ABORTED) {
/* aborted requests are replaced with a new one so do not complete this */
return;
}
/*
@ -3173,6 +3180,13 @@ static blk_status_t do_dasd_request(struct blk_mq_hw_ctx *hctx,
}
goto out;
}
if (!cqr) {
/* build_cp may collapse a non-transient build error to NULL */
DBF_DEV_EVENT(DBF_ERR, basedev,
"CCW creation returned NULL on request %p", req);
rc = BLK_STS_IOERR;
goto out;
}
/*
* Note: callback is set to dasd_return_cqr_cb in
* __dasd_block_start_head to cover erp requests as well
@ -3966,6 +3980,19 @@ int dasd_generic_requeue_all_requests(struct dasd_device *device)
*/
goto restart_cb;
}
/*
* An aborted request was replaced by a full-track write and is
* retired by that replacement; do not requeue it, just release
* it (mirrors the DASD_CQR_ABORTED handling in
* __dasd_cleanup_cqr()).
*/
if (cqr->status == DASD_CQR_ABORTED) {
struct request *req = cqr->callback_data;
list_del_init(&cqr->blocklist);
cqr->block->base->discipline->free_cp(cqr, req);
continue;
}
_dasd_requeue_request(cqr);
list_del_init(&cqr->blocklist);
cqr->block->base->discipline->free_cp(

View File

@ -2400,6 +2400,7 @@ static struct dasd_ccw_req *dasd_3990_erp_add_erp(struct dasd_ccw_req *cqr)
erp->startdev = device;
erp->memdev = device;
erp->block = cqr->block;
erp->filldata = cqr->filldata;
erp->magic = cqr->magic;
erp->expires = cqr->expires;
erp->retries = device->default_retries;

View File

@ -3213,36 +3213,24 @@ static void clear_format_track(struct dasd_format_entry *format,
spin_unlock_irqrestore(&block->format_lock, flags);
}
/*
* 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;
struct dasd_format_entry *format = data;
clear_format_track(format, cqr->basedev->block);
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 irb *irb)
static void dasd_eckd_ese_format(struct dasd_device *startdev, struct dasd_ccw_req *cqr,
struct irb *irb)
{
struct dasd_format_entry *format = NULL;
unsigned int first_offs, last_offs;
struct dasd_eckd_private *private;
struct format_data_t fdata;
unsigned int recs_per_trk;
struct dasd_ccw_req *base_cqr;
sector_t first_rec, last_rec;
sector_t first_trk, last_trk;
unsigned int proc_bytes = 0;
struct dasd_ccw_req *fcqr;
unsigned int recs_per_trk;
struct dasd_device *base;
struct dasd_block *block;
unsigned int blksize;
struct request *req;
sector_t first_trk;
sector_t last_trk;
sector_t curr_trk;
unsigned int diff;
int rc;
req = dasd_get_callback_data(cqr);
@ -3252,50 +3240,94 @@ dasd_eckd_ese_format(struct dasd_device *startdev, struct dasd_ccw_req *cqr,
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);
rc = dasd_eckd_track_from_irb(irb, base, &curr_trk);
if (rc)
return ERR_PTR(rc);
/* Calculate record id of first and last block. */
first_rec = blk_rq_pos(req) >> block->s2b_shift;
first_trk = first_rec;
first_offs = sector_div(first_trk, recs_per_trk);
last_rec = (blk_rq_pos(req) + blk_rq_sectors(req) - 1) >> block->s2b_shift;
last_trk = last_rec;
last_offs = sector_div(last_trk, recs_per_trk);
/*
* detect if some data has already been processed and the unformatted track is
* within the request.
* If so, finish the request first with the already processed bytes and let the
* blocklayer only redrive unformatted part.
* With this we ensure that there is no overlap of existing data with unformatted
* zero blocks
*/
rc = dasd_eckd_track_from_irb(irb, base, &curr_trk);
if (rc) {
/* sense data could not be parsed - this will not resolve by retrying */
cqr->status = DASD_CQR_ERROR;
goto out;
}
if (curr_trk >= (sector_t)private->real_cyl * private->rdc_data.trk_per_cyl) {
DBF_DEV_EVENT(DBF_WARNING, startdev,
"ESE error track %llu exceeds device geometry\n",
curr_trk);
cqr->status = DASD_CQR_ERROR;
goto out;
}
if (curr_trk < first_trk || curr_trk > last_trk) {
DBF_DEV_EVENT(DBF_WARNING, startdev,
"ESE error track %llu not within range %llu - %llu\n",
curr_trk, first_trk, last_trk);
return ERR_PTR(-EINVAL);
cqr->status = DASD_CQR_ERROR;
goto out;
}
/* test if track is already in formatting by another thread */
if (test_and_set_format_track(curr_trk, curr_trk, cqr, block, startdev, &format)) {
/* this is no real error so do not count down retries */
cqr->retries++;
return ERR_PTR(-EEXIST);
}
fdata.start_unit = curr_trk;
fdata.stop_unit = curr_trk;
fdata.blksize = blksize;
fdata.intensity = private->uses_cdl ? DASD_FMT_INT_COMPAT : 0;
rc = dasd_eckd_format_sanity_checks(base, &fdata);
if (rc) {
if (format)
clear_format_track(format, block);
return ERR_PTR(-EINVAL);
if (curr_trk != first_trk) {
proc_bytes = ((curr_trk - first_trk) * recs_per_trk - first_offs) * blksize;
cqr->proc_bytes = proc_bytes;
cqr->status = DASD_CQR_SUCCESS;
cqr->stopclk = get_tod_clock();
goto out;
}
/*
* We're building the request with PAV disabled as we're reusing
* the former startdev.
* If there are multiple tracks to be format-written, we can not write
* the partial last track since we do not know if it is already formatted
* or not so skip the partial last track for now. Return the partial
* completion to blocklayer and let it redo the remainder
*/
fcqr = dasd_eckd_build_format(base, startdev, &fdata, 0);
if (first_trk != last_trk && last_offs + 1 < recs_per_trk) {
diff = last_offs + 1;
last_rec = last_rec - diff;
last_trk = last_rec;
last_offs = sector_div(last_trk, recs_per_trk);
proc_bytes = (last_rec - first_rec + 1) * blksize;
}
if (first_offs > 0 || last_offs + 1 < recs_per_trk) {
/* test if tracks are already in formatting by another thread */
if (test_and_set_format_track(first_trk, last_trk, cqr,
cqr->block, cqr->startdev, &format)) {
/* this is no real error so do not count down retries */
cqr->retries++;
goto out_retry;
}
}
fcqr = dasd_eckd_build_cp_tpm_writefulltrack(startdev, block, req,
first_rec, last_rec,
first_trk, last_trk,
first_offs, last_offs,
recs_per_trk, blksize, cqr);
if (IS_ERR(fcqr)) {
if (format)
clear_format_track(format, block);
return fcqr;
clear_format_track(format, cqr->block);
if (PTR_ERR(fcqr) == -EINVAL) {
/* permanent build failure - fail instead of retrying */
cqr->status = DASD_CQR_ERROR;
goto out;
}
/*
* Transient conditions - the XRC clock is not in sync (-EAGAIN)
* or the format request pool is momentarily exhausted under load
* (-ENOMEM). Retry the origin without counting down its retries.
*/
if (PTR_ERR(fcqr) == -EAGAIN || PTR_ERR(fcqr) == -ENOMEM)
cqr->retries++;
goto out_retry;
}
if (format) {
@ -3303,10 +3335,44 @@ dasd_eckd_ese_format(struct dasd_device *startdev, struct dasd_ccw_req *cqr,
WRITE_ONCE(format->cqr, fcqr);
fcqr->format = format;
}
fcqr->callback = dasd_eckd_ese_format_cb;
fcqr->callback_data = (void *) format;
return fcqr;
/*
* cqr may be an ERP request; dq and the owning request are only set on
* the base request at the end of the ERP chain, so copy from there.
*/
base_cqr = cqr;
while (base_cqr->refers)
base_cqr = base_cqr->refers;
fcqr->dq = base_cqr->dq;
fcqr->callback_data = base_cqr->callback_data;
if (proc_bytes)
fcqr->proc_bytes = proc_bytes;
fcqr->status = DASD_CQR_FILLED;
((struct dasd_eckd_private *)fcqr->memdev->private)->count++;
/*
* stage under ese_lock; dasd_block_tasklet splices it into ccw_queue.
* Direct enqueue here would invert queue_lock / ccwdev_lock.
*/
spin_lock(&block->ese_lock);
list_add(&fcqr->blocklist, &block->ese_staging);
spin_unlock(&block->ese_lock);
/* mark origin CQR as aborted; ccwdev_lock is held by the IRQ handler */
cqr->status = DASD_CQR_ABORT;
goto out;
out_retry:
/*
* If we can't format now, let the request go
* one extra round. Maybe we can format later.
* re-queue at the end to let potential format collision finish first
*/
list_move_tail(&cqr->devlist, &cqr->startdev->ccw_queue);
cqr->status = DASD_CQR_QUEUED;
out:
dasd_device_clear_timer(startdev);
dasd_schedule_block_bh(block);
dasd_schedule_device_bh(startdev);
return;
}
/*
@ -4828,7 +4894,7 @@ static struct tidaw *add_track_end(struct itcw *itcw, char **fill,
return IS_ERR_OR_NULL(tidaw) ? NULL : tidaw;
}
static __maybe_unused struct dasd_ccw_req *
static struct dasd_ccw_req *
dasd_eckd_build_cp_tpm_writefulltrack(struct dasd_device *startdev,
struct dasd_block *block,
struct request *req,
@ -5119,7 +5185,7 @@ static struct dasd_ccw_req *dasd_eckd_build_cp(struct dasd_device *startdev,
fcx_multitrack = private->features.feature[40] & 0x20;
data_size = blk_rq_bytes(req);
if (data_size % blksize)
if (data_size % blksize || data_size == 0)
return ERR_PTR(-EINVAL);
/* tpm write request add CBC data on each track boundary */
if (rq_data_dir(req) == WRITE)
@ -5161,6 +5227,11 @@ static struct dasd_ccw_req *dasd_eckd_build_cp(struct dasd_device *startdev,
first_trk, last_trk,
first_offs, last_offs,
blk_per_trk, blksize);
if (!IS_ERR(cqr)) {
cqr->start_trk = first_trk;
cqr->end_trk = last_trk;
}
return cqr;
}
@ -5328,7 +5399,17 @@ dasd_eckd_free_cp(struct dasd_ccw_req *cqr, struct request *req)
sector_t recid;
int status;
if (!dasd_page_cache)
/*
* A format-aborted request finished nothing - its replacement
* completes the block request - so report ABORTED instead of DONE,
* but still release its bounce buffers like any other request.
*/
if (cqr->status == DASD_CQR_ABORTED)
status = DASD_CQR_ABORTED;
else
status = cqr->status == DASD_CQR_DONE;
/* transport mode has no dasd_page_cache bounce buffers to release */
if (!dasd_page_cache || cqr->cpmode)
goto out;
private = cqr->block->base->private;
blksize = cqr->block->bp_block;
@ -5363,7 +5444,6 @@ dasd_eckd_free_cp(struct dasd_ccw_req *cqr, struct request *req)
}
}
out:
status = cqr->status == DASD_CQR_DONE;
dasd_sfree_request(cqr, cqr->memdev);
return status;
}
@ -5440,6 +5520,8 @@ static int dasd_eckd_free_alias_cp(struct dasd_ccw_req *cqr,
private = cqr->memdev->private;
private->count--;
spin_unlock_irqrestore(get_ccwdev_lock(cqr->memdev->cdev), flags);
if (cqr->format)
clear_format_track(cqr->format, cqr->block);
return dasd_eckd_free_cp(cqr, req);
}

View File

@ -120,7 +120,7 @@ dasd_default_erp_action(struct dasd_ccw_req *cqr)
*/
struct dasd_ccw_req *dasd_default_erp_postaction(struct dasd_ccw_req *cqr)
{
int success;
int success, aborted;
unsigned long startclk, stopclk;
struct dasd_device *startdev;
unsigned int proc_bytes;
@ -128,6 +128,7 @@ struct dasd_ccw_req *dasd_default_erp_postaction(struct dasd_ccw_req *cqr)
BUG_ON(cqr->refers == NULL || cqr->function == NULL);
success = cqr->status == DASD_CQR_DONE;
aborted = cqr->status == DASD_CQR_ABORTED;
startclk = cqr->startclk;
stopclk = cqr->stopclk;
startdev = cqr->startdev;
@ -150,7 +151,10 @@ struct dasd_ccw_req *dasd_default_erp_postaction(struct dasd_ccw_req *cqr)
cqr->stopclk = stopclk;
cqr->startdev = startdev;
cqr->proc_bytes = proc_bytes;
if (success)
if (aborted)
/* base request is owned by the ESE format replacement CQR */
cqr->status = DASD_CQR_ABORTED;
else if (success)
cqr->status = DASD_CQR_DONE;
else {
cqr->status = DASD_CQR_FAILED;

View File

@ -413,8 +413,7 @@ struct dasd_discipline {
int (*ext_pool_warn_thrshld)(struct dasd_device *);
int (*ext_pool_oos)(struct dasd_device *);
int (*ext_pool_exhaust)(struct dasd_device *, struct dasd_ccw_req *);
struct dasd_ccw_req *(*ese_format)(struct dasd_device *,
struct dasd_ccw_req *, struct irb *);
void (*ese_format)(struct dasd_device *, struct dasd_ccw_req *, struct irb *);
int (*ese_read)(struct dasd_ccw_req *, struct irb *);
int (*pprc_status)(struct dasd_device *, struct dasd_pprc_data_sc4 *);
bool (*pprc_enabled)(struct dasd_device *);