s390/dasd: Add infrastructure for ESE full-track write

Add the driver internals to build WRITE_FULL_TRACK FCX channel programs
in response to unformatted tracks on ESE devices.

struct dasd_ccw_req: filldata, a pointer to the per-track metadata (an R0
record and the count records) that the WRITE_FULL_TRACK TIDAWs point at,
and format/start_trk/end_trk/collision that link a request to its
format-track guard entry so an overlapping format request can be detected.

struct dasd_device: fill_mem/fill_chunks pool for those buffers and a
zeroed nulldata page used as the data source for pad records.

struct dasd_block: ese_staging/ese_lock, a hardirq-safe staging list. An
ESE format CQR is created in the interrupt handler but has to be enqueued
on ccw_queue under queue_lock; taking queue_lock while the ccwdev_lock is
held there would invert the lock order, so the CQR is staged under ese_lock
and dasd_block_tasklet splices it onto ccw_queue. Existing locking is
unchanged.

Add CQR states DASD_CQR_ABORT/ABORTED to retire the origin CQR of a
replaced write without completing it to the block layer, and struct
eckd_r0 for the track header record.

The CCW and ESE format pools are enlarged (a full-track ITCW is roughly
twice a plain track-mode one) to keep two maximum-size requests in flight.

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-9-sth@linux.ibm.com
Signed-off-by: Jens Axboe <axboe@kernel.dk>
This commit is contained in:
Stefan Haberland 2026-08-05 13:16:01 +02:00 committed by Jens Axboe
parent 1edac73fa2
commit 9cebfced13
4 changed files with 120 additions and 15 deletions

View File

@ -90,31 +90,53 @@ struct dasd_device *dasd_alloc_device(void)
if (!device)
return ERR_PTR(-ENOMEM);
/* Get two pages for normal block device operations. */
device->ccw_mem = (void *)__get_free_pages(GFP_KERNEL | GFP_DMA, 1);
/*
* Four pages: a full-track ITCW is roughly twice the size of a plain
* track-mode one, so this keeps two maximum-size requests in flight.
*/
device->ccw_mem = (void *)__get_free_pages(GFP_KERNEL | GFP_DMA, 2);
if (!device->ccw_mem) {
kfree(device);
return ERR_PTR(-ENOMEM);
}
/* per-request track-filler buffers (R0 + count records) */
device->fill_mem = (void *)__get_free_pages(GFP_KERNEL | GFP_DMA, 1);
if (!device->fill_mem) {
free_pages((unsigned long)device->ccw_mem, 2);
kfree(device);
return ERR_PTR(-ENOMEM);
}
/* Get one page for error recovery. */
device->erp_mem = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
if (!device->erp_mem) {
free_pages((unsigned long) device->ccw_mem, 1);
free_pages((unsigned long)device->fill_mem, 1);
free_pages((unsigned long)device->ccw_mem, 2);
kfree(device);
return ERR_PTR(-ENOMEM);
}
/* Get two pages for ese format. */
device->ese_mem = (void *)__get_free_pages(GFP_KERNEL | GFP_DMA, 1);
/* sized like ccw_chunks: two max-size NRF format requests in flight */
device->ese_mem = (void *)__get_free_pages(GFP_KERNEL | GFP_DMA, 2);
if (!device->ese_mem) {
free_page((unsigned long) device->erp_mem);
free_pages((unsigned long) device->ccw_mem, 1);
free_page((unsigned long)device->erp_mem);
free_pages((unsigned long)device->fill_mem, 1);
free_pages((unsigned long)device->ccw_mem, 2);
kfree(device);
return ERR_PTR(-ENOMEM);
}
device->nulldata = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
if (!device->nulldata) {
free_page((unsigned long)device->erp_mem);
free_pages((unsigned long)device->fill_mem, 1);
free_pages((unsigned long)device->ccw_mem, 2);
free_pages((unsigned long)device->ese_mem, 2);
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 * 4);
dasd_init_chunklist(&device->fill_chunks, device->fill_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);
dasd_init_chunklist(&device->ese_chunks, device->ese_mem, PAGE_SIZE * 4);
spin_lock_init(&device->mem_lock);
atomic_set(&device->tasklet_scheduled, 0);
tasklet_init(&device->tasklet, dasd_device_tasklet,
@ -137,9 +159,11 @@ 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);
free_pages((unsigned long)device->ese_mem, 2);
free_page((unsigned long)device->erp_mem);
free_pages((unsigned long)device->fill_mem, 1);
free_pages((unsigned long)device->ccw_mem, 2);
free_page((unsigned long)device->nulldata);
kfree(device);
}
@ -163,6 +187,8 @@ struct dasd_block *dasd_alloc_block(void)
spin_lock_init(&block->queue_lock);
INIT_LIST_HEAD(&block->format_list);
spin_lock_init(&block->format_lock);
INIT_LIST_HEAD(&block->ese_staging);
spin_lock_init(&block->ese_lock);
timer_setup(&block->timer, dasd_block_timeout, 0);
spin_lock_init(&block->profile.lock);
@ -363,7 +389,8 @@ int _wait_for_empty_queues(struct dasd_device *device)
{
if (device->block)
return list_empty(&device->ccw_queue) &&
list_empty(&device->block->ccw_queue);
list_empty(&device->block->ccw_queue) &&
list_empty(&device->block->ese_staging);
else
return list_empty(&device->ccw_queue);
}
@ -1223,7 +1250,18 @@ void dasd_sfree_request(struct dasd_ccw_req *cqr, struct dasd_device *device)
unsigned long flags;
spin_lock_irqsave(&device->mem_lock, flags);
dasd_free_chunk(&device->ccw_chunks, cqr->mem_chunk);
/*
* Free the request block from the pool it came from: smalloc() sets
* mem_chunk (ccw_chunks), fmalloc() leaves it NULL (ese_chunks). A
* full-track request also frees its track-filler buffer.
*/
if (cqr->filldata)
dasd_free_chunk(&device->fill_chunks, cqr->filldata);
if (cqr->mem_chunk)
dasd_free_chunk(&device->ccw_chunks, cqr->mem_chunk);
else
dasd_free_chunk(&device->ese_chunks, cqr);
spin_unlock_irqrestore(&device->mem_lock, flags);
dasd_put_device(device);
}
@ -1234,6 +1272,8 @@ void dasd_ffree_request(struct dasd_ccw_req *cqr, struct dasd_device *device)
unsigned long flags;
spin_lock_irqsave(&device->mem_lock, flags);
if (cqr->filldata)
dasd_free_chunk(&device->fill_chunks, cqr->filldata);
dasd_free_chunk(&device->ese_chunks, cqr);
spin_unlock_irqrestore(&device->mem_lock, flags);
dasd_put_device(device);
@ -1884,6 +1924,17 @@ static void __dasd_process_cqr(struct dasd_device *device,
case DASD_CQR_CLEARED:
cqr->status = DASD_CQR_TERMINATED;
break;
case DASD_CQR_ABORT:
cqr->status = DASD_CQR_ABORTED;
/*
* ABORT is only set on the block-layer origin write that a
* full-track format replaces. Clear the callback so the request
* is not completed here - the replacement completes it. Internal
* requests never take this path, so no sleep_on waiter is left
* without its wakeup.
*/
cqr->callback = NULL;
break;
default:
dev_err(&device->cdev->dev,
"Unexpected CQR status %02x", cqr->status);
@ -2211,6 +2262,7 @@ EXPORT_SYMBOL(dasd_add_request_tail);
void dasd_wakeup_cb(struct dasd_ccw_req *cqr, void *data)
{
spin_lock_irq(get_ccwdev_lock(cqr->startdev->cdev));
cqr->endclk = get_tod_clock();
cqr->callback_data = DASD_SLEEPON_END_TAG;
spin_unlock_irq(get_ccwdev_lock(cqr->startdev->cdev));
wake_up(&generic_waitq);
@ -2767,7 +2819,8 @@ static void __dasd_process_block_ccw_queue(struct dasd_block *block,
if (cqr->status != DASD_CQR_DONE &&
cqr->status != DASD_CQR_FAILED &&
cqr->status != DASD_CQR_NEED_ERP &&
cqr->status != DASD_CQR_TERMINATED)
cqr->status != DASD_CQR_TERMINATED &&
cqr->status != DASD_CQR_ABORTED)
continue;
if (cqr->status == DASD_CQR_TERMINATED) {
@ -2878,6 +2931,14 @@ static void dasd_block_tasklet(unsigned long data)
atomic_set(&block->tasklet_scheduled, 0);
INIT_LIST_HEAD(&final_queue);
spin_lock_irq(&block->queue_lock);
/*
* Splice the hardirq-staged ESE format CQRs onto ccw_queue. Splice to
* the tail so an aborted origin request (already on ccw_queue) is
* retired before its format-CQR replacement completes and requeues it.
*/
spin_lock(&block->ese_lock);
list_splice_tail_init(&block->ese_staging, &block->ccw_queue);
spin_unlock(&block->ese_lock);
/* Finish off requests on ccw queue */
__dasd_process_block_ccw_queue(block, &final_queue);
spin_unlock_irq(&block->queue_lock);
@ -2937,6 +2998,15 @@ static int _dasd_requests_to_flushqueue(struct dasd_block *block,
int rc, i;
spin_lock_irqsave(&block->queue_lock, flags);
/*
* Splice any hardirq-staged ESE format CQRs onto ccw_queue first so
* they are seen and canceled by the walk below instead of being
* orphaned across this flush / state transition. Mirrors the splice
* in dasd_block_tasklet().
*/
spin_lock(&block->ese_lock);
list_splice_tail_init(&block->ese_staging, &block->ccw_queue);
spin_unlock(&block->ese_lock);
rc = 0;
restart:
list_for_each_entry_safe(cqr, n, &block->ccw_queue, blocklist) {

View File

@ -204,6 +204,17 @@ static void set_ch_t(struct ch_t *geo, __u32 cyl, __u8 head)
geo->head |= head;
}
static __maybe_unused void set_chr_t(void *addr, __u32 cyl, __u8 head, __u8 record)
{
struct chr_t *geo = addr;
geo->cyl = (__u16)cyl;
geo->head = cyl >> DASD_EAV_CYL_HI_SHIFT;
geo->head <<= DASD_EAV_HEAD_HI_SHIFT;
geo->head |= head;
geo->record = record;
}
/*
* calculate failing track from sense data depending if
* it is an EAV device or not

View File

@ -146,6 +146,11 @@ struct eckd_count {
__u16 dl;
} __attribute__ ((packed));
struct eckd_r0 {
struct eckd_count count;
__u8 data[8];
} __packed;
/*
* Extended Address Volume track address: the head field carries the actual
* head in its low-order 4 bits; the cylinder bits that do not fit the 16-bit

View File

@ -159,6 +159,11 @@ struct dasd_ccw_req {
void *callback_data;
unsigned int proc_bytes; /* bytes for partial completion */
unsigned int trkcount; /* count formatted tracks */
void *filldata; /* address of filler data */
struct dasd_format_entry *format;
sector_t start_trk;
sector_t end_trk;
bool collision;
};
/*
@ -170,6 +175,7 @@ struct dasd_ccw_req {
#define DASD_CQR_IN_ERP 0x03 /* request is in recovery */
#define DASD_CQR_FAILED 0x04 /* request is finally failed */
#define DASD_CQR_TERMINATED 0x05 /* request was stopped by driver */
#define DASD_CQR_ABORTED 0x06 /* request was replaced and will be deleted */
#define DASD_CQR_QUEUED 0x80 /* request is queued to be processed */
#define DASD_CQR_IN_IO 0x81 /* request is currently in IO */
@ -177,6 +183,7 @@ struct dasd_ccw_req {
#define DASD_CQR_CLEAR_PENDING 0x83 /* request is clear pending */
#define DASD_CQR_CLEARED 0x84 /* request was cleared */
#define DASD_CQR_SUCCESS 0x85 /* request was successful */
#define DASD_CQR_ABORT 0x86 /* request was replaced and will not be handled */
/* default expiration time*/
#define DASD_EXPIRES 300
@ -573,9 +580,12 @@ struct dasd_device {
struct list_head ccw_queue;
spinlock_t mem_lock;
void *ccw_mem;
void *fill_mem;
void *erp_mem;
void *ese_mem;
void *nulldata;
struct list_head ccw_chunks;
struct list_head fill_chunks;
struct list_head erp_chunks;
struct list_head ese_chunks;
@ -640,6 +650,15 @@ struct dasd_block {
struct list_head format_list;
spinlock_t format_lock;
atomic_t trkcount;
/*
* ESE format CQRs staged from hardirq, spliced into
* ccw_queue in dasd_block_tasklet under queue_lock. Direct enqueue from
* the IRQ handler would invert the queue_lock / ccwdev_lock order.
*/
struct list_head ese_staging;
/* lock for ese_staging */
spinlock_t ese_lock;
};
struct dasd_attention_data {