mirror of
https://github.com/torvalds/linux.git
synced 2026-09-23 22:14:03 +02:00
s390/dasd: Add dasd_eckd_build_cp_tpm_writefulltrack()
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. 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-12-sth@linux.ibm.com Signed-off-by: Jens Axboe <axboe@kernel.dk>
This commit is contained in:
parent
44f9bf4798
commit
123ec1e9cb
|
|
@ -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 */
|
||||
|
|
@ -204,7 +212,7 @@ 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)
|
||||
static void set_chr_t(void *addr, __u32 cyl, __u8 head, __u8 record)
|
||||
{
|
||||
struct chr_t *geo = addr;
|
||||
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user