mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
- Adjust progress_print() calls to pass updated address after the set of blocks has been written to disk. - Currently total_dump_size value is updated only after the entire dump segment is written to disk what leads to ambiguos Dump file size values displayed by progress_print(). Change write_addr_range() to re-calculate total_dump_size after each set of blocks has been written to disk thus printing the correct value at the end of each log entry. - Avoid final log entry duplication. Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com> Acked-by: Alexander Egorenkov <egorenar@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
540 lines
14 KiB
C
540 lines
14 KiB
C
/*
|
|
* zipl - zSeries Initial Program Loader tool
|
|
*
|
|
* Common ECKD dump I/O functions
|
|
*
|
|
* Copyright IBM Corp. 2013, 2023
|
|
*
|
|
* s390-tools is free software; you can redistribute it and/or modify
|
|
* it under the terms of the MIT license. See LICENSE for details.
|
|
*/
|
|
|
|
#include "dump/s390_dump.h"
|
|
|
|
#include "cio.h"
|
|
#include "eckd2dump.h"
|
|
#include "boot/error.h"
|
|
#include "boot/s390.h"
|
|
#include "stage2dump.h"
|
|
|
|
#define ECKD_CCW_LOCATE_RECORD 0x47
|
|
#define ECKD_CCW_LOCATE_RECORD_EXT 0x4b
|
|
#define ECKD_CCW_DEFINE_EXTENT 0x63
|
|
#define ECKD_CCW_WRITE_TRACK_DATA 0xa5
|
|
#define ECKD_CCW_READ_TRACK_DATA 0xa6
|
|
#define ECKD_CCW_WRITE_KD_MT 0x8d
|
|
#define ECKD_CCW_READ_KD_MT 0x8e
|
|
|
|
#define ECKD_BLK_PER_TRK 12
|
|
|
|
typedef unsigned long sector_t;
|
|
|
|
/*
|
|
* For track I/O we can write 12 blocks per full track. Because I/O can start
|
|
* at the end of a track with "tr_count" tracks we can guarantee to write:
|
|
*
|
|
* block_count = (tr_count - 1) * 12 + 1
|
|
*
|
|
* Therefore: tr_count >= (block_count + 11) / 12
|
|
*/
|
|
#define ECKD_BLK_PER_IO_TRK 128 /* 128 * 4 KB = up to 512 KB per I/O */
|
|
#define ECKD_CCW_MAX_COUNT_TRK 12 /* (128 + 11) / 12 = 12 track I/O CCWs */
|
|
|
|
/*
|
|
* For record I/O we write one 4 KB block with one write CCW
|
|
*/
|
|
#define ECKD_BLK_PER_IO_REC 64 /* 64 * 4 KB = up to 256 KB per I/O */
|
|
#define ECKD_CCW_MAX_COUNT_REC 64 /* 64 record I/O CCWs */
|
|
|
|
unsigned long eckd_blk_max; /* Maximum number of blocks per I/O */
|
|
struct eckd_device device;
|
|
|
|
static int track_io;
|
|
|
|
#define sector_div(n, b)( \
|
|
{ \
|
|
int _res; \
|
|
_res = (n) % (b); \
|
|
(n) /= (b); \
|
|
_res; \
|
|
} \
|
|
)
|
|
|
|
struct ch_t {
|
|
uint16_t cyl;
|
|
uint16_t head;
|
|
} __packed;
|
|
|
|
struct chr_t {
|
|
uint16_t cyl;
|
|
uint16_t head;
|
|
uint8_t record;
|
|
} __packed;
|
|
|
|
static void set_ch_t(struct ch_t *geo, uint32_t cyl, uint8_t head)
|
|
{
|
|
geo->cyl = (uint16_t) cyl;
|
|
geo->head = cyl >> 16;
|
|
geo->head <<= 4;
|
|
geo->head |= head;
|
|
}
|
|
|
|
/*
|
|
* Data for Locate Record Extended CCW
|
|
*/
|
|
struct LRE_eckd_data {
|
|
struct {
|
|
unsigned char orientation:2;
|
|
unsigned char operation:6;
|
|
} __packed operation;
|
|
struct {
|
|
unsigned char length_valid:1;
|
|
unsigned char length_scope:1;
|
|
unsigned char imbedded_ccw_valid:1;
|
|
unsigned char check_bytes:2;
|
|
unsigned char imbedded_count_valid:1;
|
|
unsigned char reserved:1;
|
|
unsigned char read_count_suffix:1;
|
|
} __packed auxiliary;
|
|
uint8_t imbedded_ccw;
|
|
uint8_t count;
|
|
struct ch_t seek_addr;
|
|
struct chr_t search_arg;
|
|
uint8_t sector;
|
|
uint16_t length;
|
|
uint8_t imbedded_count;
|
|
uint8_t extended_operation;
|
|
uint16_t extended_parameter_length;
|
|
uint8_t extended_parameter[0];
|
|
} __packed;
|
|
|
|
static struct LRE_eckd_data lodata;
|
|
|
|
struct DE_eckd_data {
|
|
struct {
|
|
unsigned char perm:2; /* Permissions on this extent */
|
|
unsigned char reserved:1;
|
|
unsigned char seek:2; /* Seek control */
|
|
unsigned char auth:2; /* Access authorization */
|
|
unsigned char pci:1; /* PCI Fetch mode */
|
|
} __packed mask;
|
|
struct {
|
|
unsigned char mode:2; /* Architecture mode */
|
|
unsigned char ckd:1; /* CKD Conversion */
|
|
unsigned char operation:3; /* Operation mode */
|
|
unsigned char cfw:1; /* Cache fast write */
|
|
unsigned char dfw:1; /* DASD fast write */
|
|
} __packed attributes;
|
|
uint16_t blk_size; /* Blocksize */
|
|
uint16_t fast_write_id;
|
|
uint8_t ga_additional; /* Global Attributes Additional */
|
|
uint8_t ga_extended; /* Global Attributes Extended */
|
|
struct ch_t beg_ext;
|
|
struct ch_t end_ext;
|
|
} __packed;
|
|
|
|
static struct DE_eckd_data dedata;
|
|
|
|
/*
|
|
* ORB /IRB
|
|
*/
|
|
static struct orb orb = {
|
|
.intparm = 0x0049504c, /* Interruption Parameter */
|
|
.fmt = 0x1, /* Use format 1 CCWs */
|
|
.c64 = 0x1, /* Use IDAs */
|
|
.lpm = 0xff, /* Logical path mask */
|
|
};
|
|
|
|
static struct irb irb;
|
|
|
|
/*
|
|
* CCW program and IDA list
|
|
*/
|
|
struct ccw_program_rw_trk {
|
|
struct ccw1 wrtdccw[ECKD_CCW_MAX_COUNT_TRK];
|
|
unsigned long ida_list[ECKD_CCW_MAX_COUNT_TRK * ECKD_BLK_PER_TRK];
|
|
};
|
|
|
|
struct ccw_program_rw_rec {
|
|
struct ccw1 wrtdccw[ECKD_CCW_MAX_COUNT_REC];
|
|
unsigned long ida_list[ECKD_CCW_MAX_COUNT_REC];
|
|
};
|
|
|
|
struct ccw_program {
|
|
struct ccw1 deccw;
|
|
struct ccw1 loccw;
|
|
union {
|
|
struct ccw_program_rw_trk trk;
|
|
struct ccw_program_rw_rec rec;
|
|
} rw;
|
|
} __packed __aligned(8);
|
|
|
|
static struct ccw_program ccw_program;
|
|
|
|
/*
|
|
* Create the list of idal words for an address/length pair
|
|
*/
|
|
static unsigned long *idal_create_words(unsigned long *idaws, void *vaddr,
|
|
unsigned int length,
|
|
unsigned long zero_page)
|
|
{
|
|
unsigned long paddr;
|
|
unsigned int cidaw;
|
|
|
|
paddr = __pa(vaddr);
|
|
cidaw = ((paddr & (PAGE_SIZE-1)) + length + (PAGE_SIZE-1)) >> 12;
|
|
if (zero_page)
|
|
*idaws++ = page_is_valid(paddr) ? paddr : zero_page;
|
|
else
|
|
*idaws++ = paddr;
|
|
paddr &= -PAGE_SIZE;
|
|
while (--cidaw > 0) {
|
|
paddr += PAGE_SIZE;
|
|
if (zero_page)
|
|
*idaws++ = page_is_valid(paddr) ? paddr : zero_page;
|
|
else
|
|
*idaws++ = paddr;
|
|
}
|
|
return idaws;
|
|
}
|
|
|
|
/*
|
|
* Fill Locate Record (Extended) data
|
|
*/
|
|
static void fill_LRE_data(struct LRE_eckd_data *data, unsigned int trk,
|
|
unsigned int rec_on_trk, int count, int cmd,
|
|
unsigned int reclen, unsigned int tlf)
|
|
{
|
|
memset(data, 0, sizeof(*data));
|
|
/*
|
|
* note: meaning of count depends on the operation
|
|
* for record based I/O it's the number of records, but for
|
|
* track based I/O it's the number of tracks
|
|
*/
|
|
data->count = count;
|
|
data->sector = 0xff;
|
|
switch (cmd) {
|
|
case ECKD_CCW_WRITE_KD_MT:
|
|
data->auxiliary.length_valid = 0x1;
|
|
data->length = reclen;
|
|
data->operation.operation = 0x1;
|
|
break;
|
|
case ECKD_CCW_READ_KD_MT:
|
|
data->auxiliary.length_valid = 0x1;
|
|
data->length = reclen;
|
|
data->operation.operation = 0x6;
|
|
break;
|
|
case ECKD_CCW_WRITE_TRACK_DATA:
|
|
data->auxiliary.length_valid = 0x1;
|
|
data->length = reclen; /* not tlf, as one might think */
|
|
data->operation.operation = 0x3f;
|
|
data->extended_operation = 0x23;
|
|
break;
|
|
case ECKD_CCW_READ_TRACK_DATA:
|
|
data->auxiliary.length_valid = 0x1;
|
|
data->length = tlf;
|
|
data->operation.operation = 0xc;
|
|
break;
|
|
}
|
|
set_ch_t(&data->seek_addr, trk / device.num_heads,
|
|
trk % device.num_heads);
|
|
data->search_arg.cyl = data->seek_addr.cyl;
|
|
data->search_arg.head = data->seek_addr.head;
|
|
data->search_arg.record = rec_on_trk;
|
|
}
|
|
|
|
/*
|
|
* Fill Define Extend and Locate Record (Extended) data
|
|
*/
|
|
static void fill_DE_LRE_data(unsigned int trk, unsigned int totrk, int cmd,
|
|
unsigned int rec_on_trk, int count,
|
|
unsigned int blksize, unsigned int tlf)
|
|
{
|
|
uint16_t heads, beghead, endhead;
|
|
uint32_t begcyl, endcyl;
|
|
|
|
memset(&dedata, 0, sizeof(dedata));
|
|
switch (cmd) {
|
|
case ECKD_CCW_READ_KD_MT:
|
|
dedata.mask.perm = 0x1;
|
|
break;
|
|
case ECKD_CCW_WRITE_KD_MT:
|
|
dedata.mask.perm = 0x2;
|
|
break;
|
|
case ECKD_CCW_READ_TRACK_DATA:
|
|
dedata.mask.perm = 0x1;
|
|
dedata.blk_size = 0;
|
|
break;
|
|
case ECKD_CCW_WRITE_TRACK_DATA:
|
|
dedata.mask.perm = 0x2;
|
|
dedata.blk_size = device.blk_size;
|
|
break;
|
|
}
|
|
dedata.attributes.mode = 0x3; /* ECKD */
|
|
dedata.ga_extended |= 0x40; /* Regular Data Format Mode */
|
|
|
|
heads = device.num_heads;
|
|
begcyl = trk / heads;
|
|
beghead = trk % heads;
|
|
endcyl = totrk / heads;
|
|
endhead = totrk % heads;
|
|
|
|
set_ch_t(&dedata.beg_ext, begcyl, beghead);
|
|
set_ch_t(&dedata.end_ext, endcyl, endhead);
|
|
|
|
fill_LRE_data(&lodata, trk, rec_on_trk, count, cmd, blksize, tlf);
|
|
}
|
|
|
|
/*
|
|
* Read or write ECKD blocks using record I/O
|
|
*/
|
|
static void io_block_rec(int cmd, unsigned long addr,
|
|
unsigned long zero_page,
|
|
sector_t first_rec, sector_t last_rec,
|
|
sector_t first_trk, sector_t last_trk,
|
|
unsigned int first_offs)
|
|
{
|
|
unsigned int i, rec_count;
|
|
unsigned long *idaws;
|
|
struct ccw1 *ccw;
|
|
|
|
rec_count = last_rec - first_rec + 1;
|
|
idaws = ccw_program.rw.rec.ida_list;
|
|
|
|
fill_DE_LRE_data(first_trk, last_trk, cmd, first_offs + 1, rec_count,
|
|
device.blk_size, 0);
|
|
ccw = ccw_program.rw.rec.wrtdccw;
|
|
for (i = 0; i < rec_count; i++) {
|
|
/* Read/write ccw. */
|
|
ccw[-1].flags |= CCW_FLAG_CC;
|
|
ccw->cmd_code = cmd;
|
|
ccw->count = device.blk_size;
|
|
ccw->cda = __pa32(idaws);
|
|
ccw->flags = CCW_FLAG_IDA;
|
|
idaws = idal_create_words(idaws, (void *) addr, device.blk_size,
|
|
zero_page);
|
|
ccw++;
|
|
addr += device.blk_size;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Read or write ECKD blocks using track I/O
|
|
*/
|
|
static void io_block_trk(uint8_t cmd, unsigned long addr,
|
|
unsigned long zero_page,
|
|
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 recoffs, count_to_trk_end, len_to_track_end, count;
|
|
unsigned int blk_count, recid, trkid, trk_count, tlf;
|
|
unsigned int idaw_len, seg_len, part_len;
|
|
unsigned char new_track, end_idaw;
|
|
unsigned long *idaws;
|
|
char *dst, *idaw_dst;
|
|
struct ccw1 *ccw;
|
|
|
|
blk_count = last_rec - first_rec + 1;
|
|
trk_count = last_trk - first_trk + 1;
|
|
idaws = ccw_program.rw.trk.ida_list;
|
|
recid = first_rec;
|
|
dst = (char *) addr;
|
|
new_track = 1;
|
|
end_idaw = 0;
|
|
len_to_track_end = 0;
|
|
idaw_dst = NULL;
|
|
idaw_len = 0;
|
|
|
|
if (first_trk == last_trk)
|
|
tlf = device.blk_size * (last_offs - first_offs + 1);
|
|
else
|
|
tlf = device.blk_size * (last_offs + 1);
|
|
fill_DE_LRE_data(first_trk, last_trk, cmd, first_offs + 1, trk_count,
|
|
device.blk_size, tlf);
|
|
|
|
ccw = ccw_program.rw.trk.wrtdccw;
|
|
seg_len = blk_count * device.blk_size;
|
|
while (seg_len) {
|
|
if (new_track) {
|
|
trkid = recid;
|
|
recoffs = trkid % device.bpt;
|
|
count_to_trk_end = device.bpt - recoffs;
|
|
count = MIN((last_rec - recid + 1), count_to_trk_end);
|
|
len_to_track_end = count * device.blk_size;
|
|
ccw[-1].flags |= CCW_FLAG_CC;
|
|
ccw->cmd_code = cmd;
|
|
ccw->count = len_to_track_end;
|
|
ccw->cda = __pa32(idaws);
|
|
ccw->flags = CCW_FLAG_IDA;
|
|
ccw++;
|
|
recid += count;
|
|
new_track = 0;
|
|
if (!idaw_dst)
|
|
idaw_dst = dst;
|
|
}
|
|
if (!idaw_dst)
|
|
idaw_dst = dst;
|
|
part_len = MIN(seg_len, len_to_track_end);
|
|
seg_len -= part_len;
|
|
dst += part_len;
|
|
idaw_len += part_len;
|
|
len_to_track_end -= part_len;
|
|
if (!(__pa(idaw_dst + idaw_len) & (PAGE_SIZE - 1)))
|
|
end_idaw = 1;
|
|
if (!len_to_track_end) {
|
|
new_track = 1;
|
|
end_idaw = 1;
|
|
}
|
|
if (end_idaw) {
|
|
idaws = idal_create_words(idaws, idaw_dst,
|
|
idaw_len, zero_page);
|
|
idaw_dst = NULL;
|
|
idaw_len = 0;
|
|
end_idaw = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Read or write ECKD blocks
|
|
*/
|
|
static int io_block(uint8_t cmd, unsigned long first_blk, unsigned long addr,
|
|
unsigned long blk_count, unsigned long zero_page, int panic)
|
|
{
|
|
sector_t first_trk, last_trk, first_rec, last_rec;
|
|
unsigned int first_offs, last_offs;
|
|
|
|
if (first_blk + blk_count - 1 > device.blk_end)
|
|
panic(EMEM, "%s", "Device too small");
|
|
|
|
/* Compute start track and end block on track */
|
|
first_rec = first_trk = first_blk;
|
|
first_offs = sector_div(first_trk, device.bpt);
|
|
last_rec = last_trk = first_blk + blk_count - 1;
|
|
last_offs = sector_div(last_trk, device.bpt);
|
|
memset(&ccw_program.rw.trk.wrtdccw, 0,
|
|
sizeof(ccw_program.rw.trk.wrtdccw));
|
|
|
|
if (track_io)
|
|
io_block_trk(cmd, addr, zero_page, first_rec, last_rec,
|
|
first_trk, last_trk, first_offs, last_offs);
|
|
else
|
|
io_block_rec(cmd, addr, zero_page, first_rec, last_rec,
|
|
first_trk, last_trk, first_offs);
|
|
return start_io(device.sid, &irb, &orb, panic);
|
|
}
|
|
|
|
/*
|
|
* Write data to given block address
|
|
*/
|
|
void writeblock(unsigned long blk, unsigned long addr, unsigned long blk_count,
|
|
unsigned long zero_page)
|
|
{
|
|
int cmd = track_io ? ECKD_CCW_WRITE_TRACK_DATA : ECKD_CCW_WRITE_KD_MT;
|
|
|
|
io_block(cmd, blk, addr, blk_count, zero_page, 1);
|
|
}
|
|
|
|
/*
|
|
* Read data from given block address
|
|
*/
|
|
void readblock(unsigned long blk, unsigned long addr, unsigned long blk_count)
|
|
{
|
|
int cmd = track_io ? ECKD_CCW_READ_TRACK_DATA : ECKD_CCW_READ_KD_MT;
|
|
|
|
io_block(cmd, blk, addr, blk_count, 0, 1);
|
|
}
|
|
|
|
/*
|
|
* Write len/device.blk_size blocks of data at start address starting from
|
|
* given block and return the next free block number. The len value must be
|
|
* a multiple of a block size (4096 bytes).
|
|
*/
|
|
unsigned long write_addr_range(unsigned long blk, unsigned long start,
|
|
unsigned long len, int print_progress)
|
|
{
|
|
unsigned long addr, start_blk, blk_count, zero_page;
|
|
|
|
/* Verify the range is a multiple of a block size */
|
|
if (len % device.blk_size)
|
|
panic(EINTERNAL, "Invalid address range: [%016x, %016x]",
|
|
start, start + len);
|
|
addr = start;
|
|
start_blk = blk;
|
|
while (addr < start + len) {
|
|
/* Remaining blocks to write */
|
|
blk_count = m2b(len) - (blk - start_blk);
|
|
blk_count = MIN(blk_count, eckd_blk_max);
|
|
zero_page = get_zeroed_page();
|
|
writeblock(blk, addr, blk_count, zero_page);
|
|
free_page(zero_page);
|
|
blk += blk_count;
|
|
addr += b2m(blk_count);
|
|
total_dump_size += b2m(blk_count);
|
|
if (print_progress)
|
|
progress_print(addr);
|
|
}
|
|
return blk;
|
|
}
|
|
|
|
/*
|
|
* Write dump segment with the header to DASD and return the next free
|
|
* block number
|
|
*/
|
|
unsigned long write_dump_segment(unsigned long blk,
|
|
struct df_s390_dump_segm_hdr *segm)
|
|
{
|
|
unsigned long zero_page;
|
|
|
|
/* Write the dump segment header itself (1 page) */
|
|
zero_page = get_zeroed_page();
|
|
writeblock(blk, (unsigned long)segm,
|
|
m2b(sizeof(struct df_s390_dump_segm_hdr)), zero_page);
|
|
free_page(zero_page);
|
|
blk += m2b(PAGE_SIZE);
|
|
/* Write the dump segment */
|
|
return write_addr_range(blk, segm->start, segm->len, PRINT_PROGRESS);
|
|
}
|
|
|
|
/*
|
|
* Init ECKD common
|
|
*/
|
|
void stage2dump_eckd_init(void)
|
|
{
|
|
unsigned long addr;
|
|
int rc;
|
|
|
|
ccw_program.deccw.cmd_code = ECKD_CCW_DEFINE_EXTENT;
|
|
ccw_program.deccw.flags = CCW_FLAG_CC;
|
|
ccw_program.deccw.count = sizeof(dedata);
|
|
ccw_program.deccw.cda = __pa32(&dedata);
|
|
|
|
ccw_program.loccw.cmd_code = ECKD_CCW_LOCATE_RECORD_EXT;
|
|
ccw_program.loccw.flags = CCW_FLAG_CC;
|
|
ccw_program.loccw.count = sizeof(lodata);
|
|
ccw_program.loccw.cda = __pa32(&lodata);
|
|
|
|
orb.cpa = __pa32(&ccw_program);
|
|
track_io = 1;
|
|
eckd_blk_max = ECKD_BLK_PER_IO_TRK;
|
|
|
|
/* Probe Track I/O - Read first block of device */
|
|
addr = get_zeroed_page();
|
|
rc = io_block(ECKD_CCW_READ_TRACK_DATA, device.blk_start,
|
|
addr, 1, 0, 0);
|
|
free_page(addr);
|
|
|
|
/*
|
|
* If storage server does not support track I/O use record I/O fallback.
|
|
* Use Locate Record instead of Locate Record Extended in that case.
|
|
*/
|
|
if (rc) {
|
|
printf("Using Record I/O");
|
|
track_io = 0;
|
|
eckd_blk_max = ECKD_BLK_PER_IO_REC;
|
|
ccw_program.loccw.cmd_code = ECKD_CCW_LOCATE_RECORD;
|
|
ccw_program.loccw.count = 0x10;
|
|
}
|
|
}
|