mirror of
https://github.com/torvalds/linux.git
synced 2026-09-24 06:24:02 +02:00
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:
parent
1edac73fa2
commit
9cebfced13
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user