Files
s390-tools/zipl/boot/eckd2dump_mv.c
Marc Hartmayer 35e539a428 zipl/boot: Declare 'magic' variable as static
Reviewed-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Stefan Haberland <sth@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2025-11-07 14:34:48 +01:00

324 lines
7.6 KiB
C

/*
* zipl - zSeries Initial Program Loader tool
*
* Multi-volume ECKD DASD dump tool
*
* 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 "lib/zt_common.h"
#include "boot/error.h"
#include "boot/loaders_layout.h"
#include "dump/s390_dump.h"
#include "eckd2dump.h"
#include "stage2dump.h"
#define MAX_DUMP_VOLUMES 32 /* Up to 32 dump volumes possible */
/*
* Magic number at start of dump record
*/
static const uint64_t __used __section(.stage2.head) magic = 0x584d554c54363402ULL; /* XMULT64, version 2 */
/*
* Parameter format for ECKD MV dumper (13 bytes):
*
* DDSS SSEE EEBN O
*
* - DD : Devno of dump target
* - SSSS: Start block number of dump target
* - EEEE: End block number of dump target
* - B : Blocksize of dump target (needs to be left-shifted by 8 bits)
* - N : End Record Number
* - O : Number of Heads of DASD
*
* We assume that the End Record Number is at track boundary.
* This allows us to determine the number of Blocks Per Track.
*/
struct mvdump_param {
uint16_t devno;
uint32_t blk_start;
uint32_t blk_end;
uint8_t blk_size;
uint8_t bpt;
uint8_t num_heads;
} __packed;
/*
* Provide storage for parameter table: 10 + 32*PTE_LENGTH = 426 bytes
* We take 512 bytes to match with struct mvdump_parm_table in include/boot.h
*/
struct mvdump_parm_table {
uint64_t tod;
uint16_t num_param;
struct mvdump_param param[MAX_DUMP_VOLUMES];
uint8_t ssid[MAX_DUMP_VOLUMES];
unsigned char reserved[512 - sizeof(uint64_t) - sizeof(uint16_t) -
(MAX_DUMP_VOLUMES * (sizeof(struct mvdump_param) + 1))];
} __packed;
static struct mvdump_parm_table __section(.eckd2dump_mv.tail) mvdump_table;
static int volnr_current;
/*
* Get device characteristics for current DASD device from zipl parameter block
*/
void dt_device_parm_setup(void)
{
struct mvdump_param *param = &mvdump_table.param[volnr_current];
device.devno = param->devno;
device.blk_start = param->blk_start;
device.blk_end = param->blk_end;
device.blk_size = ((unsigned int) param->blk_size) << 8;
device.bpt = param->bpt;
device.num_heads = param->num_heads;
}
/*
* Get subchannel ID for MV dump
*/
static int set_sid_from_devno(uint8_t ssid, uint16_t devno)
{
struct subchannel_id sid;
struct schib schib;
memset(&sid, 0, sizeof(sid));
sid.one = 1;
sid.sch_no = 0;
sid.ssid = ssid;
do {
if (store_subchannel(sid, &schib) == 0) {
if (schib.pmcw.dev == devno) {
device.sid = sid;
break;
}
}
if (sid.sch_no == 0xffff)
return -1;
sid.sch_no++;
} while (1);
return 0;
}
/*
* CHSC data structures
*/
struct chsc_header {
uint16_t length;
uint16_t code;
} __packed;
struct chsc_sda_area {
struct chsc_header request;
uint8_t :4;
uint8_t format:4;
uint8_t :8;
uint16_t operation_code;
uint32_t :32;
uint32_t :32;
uint32_t operation_data_area[252];
struct chsc_header response;
uint32_t :4;
uint32_t format2:4;
uint32_t :24;
} __packed __aligned(PAGE_SIZE);
#define CHSC_SDA_OC_MSS 0x2
/*
* Issue synchronous CHSC
*/
static inline int chsc(void *chsc_area)
{
typedef struct { char _[4096]; } addr_type;
int cc;
asm volatile(
" .insn rre,0xb25f0000,%2,0\n"
" ipm %0\n"
" srl %0,28\n"
: "=d" (cc), "=m" (*(addr_type *) chsc_area)
: "d" (chsc_area), "m" (*(addr_type *) chsc_area)
: "cc");
return cc;
}
/*
* Enable multiple subchannel set facility
*/
static void enable_mss_facility(void)
{
struct chsc_sda_area *sda_area = (void *) get_zeroed_page();
sda_area->request.length = 0x0400;
sda_area->request.code = 0x0031;
sda_area->operation_code = CHSC_SDA_OC_MSS;
if (chsc(sda_area) || (sda_area->response.code != 1)) {
free_page((unsigned long) sda_area);
panic(ENOMSS, "Could not enable MSS");
}
free_page((unsigned long) sda_area);
}
/*
* Enable current DASD device
*/
void dt_device_enable(void)
{
struct mvdump_param *param = &mvdump_table.param[volnr_current];
int ssid = mvdump_table.ssid[volnr_current];
static int first = 1;
if (first && ssid) {
enable_mss_facility();
first = 0;
}
if (set_sid_from_devno(ssid, param->devno) != 0)
panic(ENODEVNO, "%04x is undefined", param->devno);
io_irq_enable();
set_device(device.sid, ENABLED);
stage2dump_eckd_init();
}
/*
* Check for the volume timestamp and validate the dump signature
* before writing a dump.
*/
static void check_volume(void)
{
struct mvdump_parm_table *mvdump_table_new;
struct df_s390_hdr *hdr_new;
unsigned long page;
page = get_zeroed_page();
/*
* Check whether parameter table on dump device has a valid
* time stamp. The parameter table is located right behind
* the dump tool, the corresponding block number is:
* MAGIC_BLK_ECKD + (STAGE2_DUMPER_SIZE_MV / blocksize)
*/
mvdump_table_new = (void *) page;
readblock(DF_S390_MAGIC_BLK_ECKD + m2b(STAGE2_DUMPER_SIZE_MV),
__pa(mvdump_table_new), 1);
/*
* Check if time stamps match
*/
if (mvdump_table.tod != mvdump_table_new->tod)
panic(ENOTIME, "Inconsistent time stamps");
/*
* Check if dump partition has a valid dump signature.
* Bypass signature check if "--force" had been specified during
* zipl -M.
*/
hdr_new = (void *) page;
if (!parm_tail.mvdump_force) {
readblock(device.blk_start, __pa(hdr_new), 1);
if (dump_hdr->magic != hdr_new->mvdump_sign)
panic(ENOSIGN, "Wrong signature");
}
free_page(page);
}
/*
* Write the dump header and memory to the current volume and return the next
* address to write for the next volume or memory size if the dump ended
* on this volume
*/
static unsigned long write_volume(unsigned long addr,
struct df_s390_dump_segm_hdr *dump_segm)
{
unsigned long free_space, blk, page;
/*
* Write dump header
*/
blk = device.blk_start;
writeblock(blk, __pa(dump_hdr), m2b(DF_S390_HDR_SIZE), 0);
blk += m2b(DF_S390_HDR_SIZE);
/*
* The free space left on the volume minus 2 blocks (for the segment
* header and the end marker)
*/
free_space = b2m(device.blk_end - blk + 1) - b2m(2);
/*
* Write dump data
*/
while (addr < dump_hdr->mem_size) {
/*
* Find the next non-zero dump segment with the limit
* of segment length set to the amount of free space left
*/
addr = find_dump_segment(addr, dump_hdr->mem_size,
ROUND_DOWN(free_space, MIB),
dump_segm);
blk = write_dump_segment(blk, dump_segm);
/* Update free space left on vol */
free_space -= dump_segm->len;
/* Reserve one block for the next segment header */
if (free_space)
free_space -= b2m(1);
/* Check if no more dump segments follow */
if (dump_segm->stop_marker) {
/* Write end marker */
page = get_zeroed_page();
df_s390_em_page_init(page);
writeblock(blk, page, 1, 0);
free_page(page);
return dump_hdr->mem_size;
}
/*
* Go to the new volume if not enough space
*/
if (free_space < MIB)
break;
}
return addr;
}
/*
* Dump all memory to multiple DASD partitions
*/
void dt_dump_mem(void)
{
struct df_s390_dump_segm_hdr *dump_segm;
unsigned long addr;
dump_hdr->mvdump_sign = DF_S390_MAGIC_EXT;
dump_hdr->mvdump = 1;
addr = 0;
total_dump_size = 0;
dump_segm = (void *)get_zeroed_page();
while (1) {
printf("Dumping to: 0.%x.%04x", device.sid.ssid, device.devno);
check_volume();
addr = write_volume(addr, dump_segm);
if (addr == dump_hdr->mem_size)
break;
/*
* Switch to next volume if available
*/
dump_hdr->volnr += 1;
volnr_current++;
if (dump_hdr->volnr >= mvdump_table.num_param)
panic(EMEM, "Device too small");
dt_device_parm_setup();
set_device(device.sid, DISABLED);
dt_device_enable();
}
free_page(__pa(dump_segm));
progress_print(addr);
}