[PATCH v7 11/19] s390/dasd: Add dasd_eckd_build_cp_tpm_writefulltrack()
Stefan Haberland <[email protected]> Sat, 1 Aug 2026 20:00:25 +0200
| Newsgroups | org.kernel.vger.linux-s390 |
|---|---|
| Message-ID | <[email protected]> |
Add the channel program builder for WRITE_FULL_TRACK requests, used by dasd_eckd_ese_format() (next patch) to format and write a set of tracks atomically and avoid the format cycle on ESE devices. The program is an ITCW with a TIDAW list. Per track it emits an eckd_r0 header, an eckd_count + data pair for every record (pad records before and after the caller's data window use device->nulldata, records in the window point into the bio payload), and a terminating 0xFF pseudo-count with TIDAW_FLAGS_INSERT_CBC. The descriptors come from the per-device fill_chunks pool so they can be freed in bulk in __dasd_cleanup_cqr(). Add inline helpers crosses_page() and reserve_nocross(), to keep each descriptor within one page since TIDAW addressing must not cross a page boundary. Signed-off-by: Stefan Haberland <[email protected]> --- drivers/s390/block/dasd_eckd.c | 342 +++++++++++++++++++++++++++++++++ 1 file changed, 342 insertions(+) diff --git a/drivers/s390/block/dasd_eckd.c b/drivers/s390/block/dasd_eckd.c index 8b0c32395c8b..8495686b1ac2 100644 --- a/drivers/s390/block/dasd_eckd.c +++ b/drivers/s390/block/dasd_eckd.c @@ -123,6 +123,14 @@ static int prepare_itcw(struct itcw *, unsigned int, unsigned int, int, unsigned int, unsigned int); static int dasd_eckd_query_pprc_status(struct dasd_device *, struct dasd_pprc_data_sc4 *); +static struct dasd_ccw_req *dasd_eckd_build_cp_tpm_writefulltrack(struct dasd_device *, + struct dasd_block *, + struct request *, + sector_t, sector_t, + sector_t, sector_t, + unsigned int, unsigned int, + unsigned int, unsigned int, + struct dasd_ccw_req *); /* initial attempt at a probe function. this can be simplified once * the other detection code is gone */ @@ -4742,6 +4750,340 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_tpm_track( return ERR_PTR(ret); } +static __always_inline bool crosses_page(const void *addr, size_t len) +{ + return len && (offset_in_page(addr) + len > PAGE_SIZE); +} + +static __always_inline void *reserve_nocross(char **p, size_t *space, size_t len) +{ + size_t pad = crosses_page(*p, len) ? PAGE_SIZE - offset_in_page(*p) : 0; + void *ret; + + if (*space < pad + len) + return NULL; /* out of space */ + + *p += pad; + *space -= pad; + ret = *p; + *p += len; + *space -= len; + return ret; +} + +/* + * Helpers for dasd_eckd_build_cp_tpm_writefulltrack(): append the TIDAWs for + * one track-image element (R0 header, a count + data record, or the trailing + * pseudo track end count) to the itcw. Return the last TIDAW, or NULL on failure. + */ +static struct tidaw *add_track_r0(struct itcw *itcw, char **fill, + size_t *fillsize, u32 cyl, u16 head) +{ + struct tidaw *tidaw; + struct eckd_r0 *r0; + + r0 = reserve_nocross(fill, fillsize, sizeof(*r0)); + if (WARN_ON_ONCE(!r0)) + return NULL; + set_chr_t(r0, cyl, head, 0); + r0->count.dl = 8; + tidaw = itcw_add_tidaw(itcw, 0, r0, sizeof(*r0)); + return IS_ERR_OR_NULL(tidaw) ? NULL : tidaw; +} + +static struct tidaw *add_track_record(struct itcw *itcw, char **fill, + size_t *fillsize, u32 cyl, u16 head, + u8 rec, void *data, u32 dl) +{ + struct eckd_count *count; + struct tidaw *tidaw; + + count = reserve_nocross(fill, fillsize, sizeof(*count)); + if (WARN_ON_ONCE(!count)) + return NULL; + set_chr_t(count, cyl, head, rec); + count->dl = dl; + tidaw = itcw_add_tidaw(itcw, 0, count, sizeof(*count)); + if (IS_ERR_OR_NULL(tidaw)) + return NULL; + tidaw = itcw_add_tidaw(itcw, 0, data, dl); + return IS_ERR_OR_NULL(tidaw) ? NULL : tidaw; +} + +static struct tidaw *add_track_end(struct itcw *itcw, char **fill, + size_t *fillsize) +{ + struct eckd_count *count; + struct tidaw *tidaw; + + count = reserve_nocross(fill, fillsize, sizeof(*count)); + if (WARN_ON_ONCE(!count)) + return NULL; + count->cyl = 0xffff; + count->head = 0xffff; + count->dl = 0xffff; + count->record = 0xff; + count->kl = 0xff; + tidaw = itcw_add_tidaw(itcw, TIDAW_FLAGS_INSERT_CBC, count, sizeof(*count)); + return IS_ERR_OR_NULL(tidaw) ? NULL : tidaw; +} + +static __maybe_unused struct dasd_ccw_req * +dasd_eckd_build_cp_tpm_writefulltrack(struct dasd_device *startdev, + struct dasd_block *block, + struct request *req, + sector_t first_rec, + sector_t last_rec, + sector_t first_trk, + sector_t last_trk, + unsigned int first_offs, + unsigned int last_offs, + unsigned int blk_per_trk, + unsigned int blksize, + struct dasd_ccw_req *ocqr) +{ + struct dasd_eckd_private *private = block->base->private; + unsigned int seg_len, part_len, len_to_track_end; + unsigned int count, count_to_trk_end, offs; + unsigned int trkcount, ctidaw, tlf; + int itcw_op, rec_count, datasize; + struct tidaw *last_tidaw = NULL; + sector_t recid, trkid, curr_trk; + unsigned char cmd, new_track; + struct dasd_device *basedev; + size_t itcw_size, fillsize; + struct dasd_ccw_req *cqr; + struct req_iterator iter; + char *dst, *filldata; + unsigned long flags; + struct itcw *itcw; + struct bio_vec bv; + int ret = -EINVAL; + void *nullrecord; + u16 heads, head; + u32 cyl; + u8 rec; + + basedev = block->base; + cmd = DASD_ECKD_CCW_WRITE_FULL_TRACK; + itcw_op = ITCW_OP_WRITE; + + /* + * trackbased I/O needs address all memory via TIDAWs, + * not just for 64 bit addresses. This allows us to map + * each segment directly to one tidaw. + * In the case of write requests, additional tidaws may + * be needed when a segment crosses a track boundary. + * Per track we emit one R0 tidaw, two tidaws per record (count field + * plus data - a record never crosses a track or page boundary, as + * part_len is clamped to both blksize and the track end), and one track + * end tidaw: 2 * blk_per_trk + 2. + * Round the +2 up to blk_per_trk-independent headroom via 2 * (blk_per_trk + 2). + */ + trkcount = last_trk - first_trk + 1; + ctidaw = trkcount * 2 * (blk_per_trk + 2); + + /* + * build_cp (ocqr == NULL): the request owns its CCW program - block in + * the pdu, ITCW in ccw_chunks. ese_format (ocqr != NULL): the failing + * origin still owns its pdu, so take the replacement from ese_chunks. + */ + itcw_size = itcw_calc_size(0, ctidaw, 0); + if (ocqr) + cqr = dasd_fmalloc_request(DASD_ECKD_MAGIC, 0, itcw_size, startdev); + else + cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 0, itcw_size, startdev, + blk_mq_rq_to_pdu(req)); + if (IS_ERR(cqr)) + return cqr; + fillsize = trkcount * (sizeof(struct eckd_r0) + + (sizeof(struct eckd_count) * (blk_per_trk + 2))); + /* + * reserve_nocross() pads elements away from page boundaries and draws + * that padding from fillsize; budget one element per page the buffer + * may span so it never runs short. + */ + fillsize += (fillsize / PAGE_SIZE + 1) * sizeof(struct eckd_r0); + spin_lock_irqsave(&startdev->mem_lock, flags); + filldata = dasd_alloc_chunk(&startdev->fill_chunks, fillsize); + spin_unlock_irqrestore(&startdev->mem_lock, flags); + if (!filldata) { + ret = -ENOMEM; + goto out_error; + } + memset(filldata, 0, fillsize); + cqr->filldata = filldata; + + nullrecord = startdev->nulldata; + + /* count + data for each record, plus r0 and the pseudo count */ + tlf = blk_per_trk * (blksize + sizeof(struct eckd_count)); + tlf += sizeof(struct eckd_r0) + sizeof(struct eckd_count); + + itcw = itcw_init(cqr->data, itcw_size, itcw_op, 0, ctidaw, 0); + if (IS_ERR(itcw)) { + ret = -EINVAL; + goto out_error; + } + cqr->cpaddr = itcw_get_tcw(itcw); + datasize = trkcount * tlf; + if (prepare_itcw(itcw, first_trk, last_trk, + cmd, basedev, startdev, + 0, + trkcount, blksize, + datasize, + tlf, + blk_per_trk) == -EAGAIN) { + /* Clock not in sync and XRC is enabled. + * Try again later. + */ + ret = -EAGAIN; + goto out_error; + } + heads = private->rdc_data.trk_per_cyl; + /* + * A tidaw can address 4k of memory, but must not cross page boundaries + * We can let the block layer handle this by setting seg_boundary_mask + * to page boundaries and max_segment_size to page size when setting up + * the request queue. + */ + curr_trk = first_trk; + recid = first_rec; + trkid = recid; + offs = sector_div(trkid, blk_per_trk); + count = blk_per_trk; + len_to_track_end = count * blksize; + recid += count - first_offs; + new_track = 0; + + /* the R0 header of the first track */ + cyl = curr_trk / heads; + head = curr_trk % heads; + last_tidaw = add_track_r0(itcw, &filldata, &fillsize, cyl, head); + if (!last_tidaw) + goto out_error; + + /* empty records before the first data record */ + for (int i = 1; i <= first_offs; i++) { + len_to_track_end -= blksize; + last_tidaw = add_track_record(itcw, &filldata, &fillsize, + cyl, head, i, nullrecord, blksize); + if (!last_tidaw) + goto out_error; + } + + /* process data records */ + rec = first_offs + 1; + rec_count = 0; + rq_for_each_segment(bv, req, iter) { + dst = bvec_virt(&bv); + seg_len = bv.bv_len; + while (seg_len) { + if (new_track) { + trkid = recid; + offs = sector_div(trkid, blk_per_trk); + count_to_trk_end = blk_per_trk - offs; + count = min((last_rec - recid + 1), + (sector_t)count_to_trk_end); + /* + * Size to the physical track end: a short last + * track is padded in out_skip, so the track-end + * marker must not be emitted early here. + */ + len_to_track_end = count_to_trk_end * blksize; + recid += count; + new_track = 0; + /* the R0 header of the next track */ + cyl = curr_trk / heads; + head = curr_trk % heads; + last_tidaw = add_track_r0(itcw, &filldata, + &fillsize, cyl, head); + if (!last_tidaw) + goto out_error; + rec = 1; + } + /* + * One count + data record per block: a bvec segment can + * be up to a page, so clamp to blksize - otherwise the + * count field would describe one oversized record instead + * of several blksize ones for sub-page block sizes. + */ + part_len = min(seg_len, len_to_track_end); + part_len = min(part_len, blksize); + seg_len -= part_len; + len_to_track_end -= part_len; + /* + * This block ends the track; the next one starts a new + * track. The track-end marker emitted below carries the + * CBC flag. + */ + if (!len_to_track_end) + new_track = 1; + + last_tidaw = add_track_record(itcw, &filldata, &fillsize, + cyl, head, rec, dst, part_len); + if (!last_tidaw) + goto out_error; + + if (new_track) { + /* add track end marker */ + last_tidaw = add_track_end(itcw, &filldata, + &fillsize); + if (!last_tidaw) + goto out_error; + curr_trk++; + } + rec++; + dst += part_len; + rec_count++; + if (rec_count >= (last_rec - first_rec + 1)) + goto out_skip; + } + } + +out_skip: + new_track = 0; + /* empty records after the last data record */ + for (int i = last_offs + 2; i <= blk_per_trk; i++) { + len_to_track_end -= blksize; + last_tidaw = add_track_record(itcw, &filldata, &fillsize, + cyl, head, i, nullrecord, blksize); + if (!last_tidaw) + goto out_error; + new_track = 1; + } + + /* add track end marker */ + if (new_track) { + last_tidaw = add_track_end(itcw, &filldata, &fillsize); + if (!last_tidaw) + goto out_error; + } + + last_tidaw->flags |= TIDAW_FLAGS_LAST; + last_tidaw->flags &= ~TIDAW_FLAGS_INSERT_CBC; + itcw_finalize(itcw); + + if (blk_noretry_request(req) || + block->base->features & DASD_FEATURE_FAILFAST) + set_bit(DASD_CQR_FLAGS_FAILFAST, &cqr->flags); + cqr->cpmode = 1; + cqr->startdev = startdev; + cqr->memdev = startdev; + cqr->block = block; + cqr->expires = startdev->default_expires * HZ; /* default 5 minutes */ + cqr->lpm = dasd_path_get_ppm(startdev); + cqr->retries = startdev->default_retries; + cqr->buildclk = get_tod_clock(); + cqr->status = DASD_CQR_FILLED; + + return cqr; +out_error: + /* dasd_sfree_request frees from the right pool via cqr->mem_chunk */ + dasd_sfree_request(cqr, startdev); + return ERR_PTR(ret); +} + static struct dasd_ccw_req *dasd_eckd_build_cp(struct dasd_device *startdev, struct dasd_block *block, struct request *req) -- 2.53.0