diff --git a/include/boot/s390.h b/include/boot/s390.h index 52cdeaa5..1cc01b99 100644 --- a/include/boot/s390.h +++ b/include/boot/s390.h @@ -25,6 +25,7 @@ #define STACK_FRAME_OVERHEAD _AC(160, U) /* Facilities */ +#define DFLTCC_FACILITY _AC(151, U) #define UNPACK_FACILITY _AC(161, U) #ifndef __ASSEMBLER__ diff --git a/include/dump/s390_dump.h b/include/dump/s390_dump.h index 7f1b9af6..6740c811 100644 --- a/include/dump/s390_dump.h +++ b/include/dump/s390_dump.h @@ -12,6 +12,7 @@ #include +#include "boot/page.h" #include "lib/zt_common.h" /* @@ -57,7 +58,9 @@ struct df_s390_hdr { uint8_t mvdump; /* 0x05c */ uint16_t cpu_cnt; /* 0x05d */ uint16_t real_cpu_cnt; /* 0x05f */ - uint8_t end_pad1[0x200 - 0x061]; /* 0x061 */ + uint8_t zlib_version_s390; /* 0x061 */ + uint32_t zlib_entry_size; /* 0x062 */ + uint8_t end_pad1[0x200 - 0x066]; /* 0x066 */ uint64_t mvdump_sign; /* 0x200 */ uint64_t mvdump_zipl_time; /* 0x208 */ uint8_t end_pad2[0x800 - 0x210]; /* 0x210 */ @@ -79,10 +82,43 @@ struct df_s390_em { * Dump segment header */ struct df_s390_dump_segm_hdr { - uint64_t start; - uint64_t len; - uint64_t stop_marker; - uint8_t reserved[0x1000 - 24]; -} __packed; + union { + struct { + uint64_t start; /* 0x000 */ + uint64_t len; /* 0x008 */ + uint64_t stop_marker; /* 0x010 */ + /* Size in blocks of compressed dump segment written to disk */ + uint32_t size_on_disk; /* 0x018 */ + uint8_t reserved_pad[0x30 - 0x1c]; /* 0x01c */ + /* + * Number of compressed entries in this dump segment (up to + * 1011 entries) + */ + uint32_t entry_count; /* 0x030 */ + /* + * Offsets in blocks to compressed entries written to disk + * from the start of the dump segment. + * High-order bit is set if the entry has been written + * uncompressed. + */ + uint32_t entry_offset[]; /* 0x034 */ + } __packed; + uint8_t padding[PAGE_SIZE]; + }; +}; + +/* Data compression granularity (size of input data chunk for zlib deflate) */ +#define DUMP_SEGM_ZLIB_ENTSIZE (1 * MIB) +/* Maximum number of compressed entries in one dump segment */ +#define DUMP_SEGM_ZLIB_MAXENTS ((sizeof(struct df_s390_dump_segm_hdr) \ + - offsetof(struct df_s390_dump_segm_hdr, entry_offset)) \ + / sizeof(uint32_t)) +/* + * Maximum length of compressed dump segment considering the size of + * a single input chunk + */ +#define DUMP_SEGM_ZLIB_MAXLEN (DUMP_SEGM_ZLIB_MAXENTS * DUMP_SEGM_ZLIB_ENTSIZE) +/* Bitmask to mark uncompressed chunks */ +#define DUMP_SEGM_ENTRY_UNCOMPRESSED 0x80000000 #endif /* S390_DUMP_H */ diff --git a/zipl/boot/eckd2dump_sv.c b/zipl/boot/eckd2dump_sv.c index f69076a8..99c6859f 100644 --- a/zipl/boot/eckd2dump_sv.c +++ b/zipl/boot/eckd2dump_sv.c @@ -15,6 +15,7 @@ #include "dump/s390_dump.h" #include "eckd2dump.h" +#include "eckd2dump_zlib.h" #include "stage2dump.h" /* @@ -47,24 +48,14 @@ void dt_device_enable(void) stage2dump_eckd_init(); } -/* - * Dump all memory to DASD partition - */ -void dt_dump_mem(void) +static unsigned long dt_dump_mem_non_compressed(unsigned long addr, + unsigned long blk) { - unsigned long blk, addr, end, page; struct df_s390_dump_segm_hdr *dump_segm; + unsigned long end; - blk = device.blk_start; dump_segm = (void *)get_zeroed_page(); - - /* Write dump header */ - writeblock(blk, __pa(dump_hdr), m2b(DF_S390_HDR_SIZE), 0); - blk += m2b(DF_S390_HDR_SIZE); - - /* Write memory */ - addr = 0; - total_dump_size = 0; + /* Write memory uncompressed */ end = dump_hdr->mem_size; while (addr < end) { addr = find_dump_segment(addr, end, 0, dump_segm); @@ -77,7 +68,91 @@ void dt_dump_mem(void) } free_page(__pa(dump_segm)); progress_print(addr); + return blk; +} +static unsigned long dt_dump_mem_compressed(unsigned long addr, + unsigned long blk) +{ + struct df_s390_dump_segm_hdr *dump_segm; + unsigned long end; + z_stream strm; + + /* Write memory compressed with zlib deflate */ + dump_segm = (void *)get_zeroed_page(); + end = dump_hdr->mem_size; + /* + * Always write first megabyte of memory uncompressed in + * order to use it as zlib workarea + */ + dump_segm->start = addr; + dump_segm->len = ZLIB_WORKSPACE_LIMIT; + blk = write_dump_segment(blk, dump_segm); + total_dump_size += dump_segm->len; + addr += dump_segm->len; + /* Initialize zlib workarea, return value 0 is expected */ + if (zlib_workarea_init(dump_segm->start, &strm)) { + printf("Zlib workarea initialization failed! Dumping without compression"); + free_page(__pa(dump_segm)); + return dt_dump_mem_non_compressed(addr, blk); + } + /* + * Compress on level 1 (hardware only) using default zlib + * wrapped stream. + */ + if (zlib_deflateInit2(&strm, 1, Z_DEFLATED, MAX_WBITS, + DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY) != Z_OK) { + /* + * Could not allocate or initialyze a workarea for zlib deflate, + * continue dumping without compression. + */ + printf("Deflate initialization failed! Dumping without compression"); + free_page(__pa(dump_segm)); + return dt_dump_mem_non_compressed(addr, blk); + } + while (addr < end) { + /* + * Limit the max size of compressed dump segment in order to + * fit all the compressed chunk entries in the segment header. + */ + addr = find_dump_segment(addr, end, DUMP_SEGM_ZLIB_MAXLEN, + dump_segm); + blk = write_compressed_dump_segment(blk, dump_segm, &strm); + total_dump_size += dump_segm->size_on_disk ? + b2m(dump_segm->size_on_disk) : dump_segm->len; + if (dump_segm->stop_marker) { + addr = end; + break; + } + } + zlib_deflateEnd(&strm); + free_page(__pa(dump_segm)); + progress_print(addr); + return blk; +} + +/* + * Dump all memory to DASD partition. + * Use zlib compression if DFLTCC facility is available. + */ +void dt_dump_mem(void) +{ + unsigned long blk, start, page; + + total_dump_size = 0; + /* Write dump header */ + blk = device.blk_start; + writeblock(blk, __pa(dump_hdr), m2b(DF_S390_HDR_SIZE), 0); + blk += m2b(DF_S390_HDR_SIZE); + /* Write memory starting from zero address */ + start = 0; + /* Check for zlib flag in the dump header */ + if (dump_hdr->zlib_version_s390) { + printf("DFLTCC facility available, using zlib compression"); + blk = dt_dump_mem_compressed(start, blk); + } else { + blk = dt_dump_mem_non_compressed(start, blk); + } /* Write end marker */ page = get_zeroed_page(); df_s390_em_page_init(page); diff --git a/zipl/boot/eckd2dump_zlib.c b/zipl/boot/eckd2dump_zlib.c new file mode 100644 index 00000000..5601d011 --- /dev/null +++ b/zipl/boot/eckd2dump_zlib.c @@ -0,0 +1,182 @@ +/* + * 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 "cio.h" +#include "eckd2dump.h" +#include "eckd2dump_zlib.h" +#include "boot/linux_layout.h" +#include "boot/s390.h" +#include "stage2dump.h" +#include "dump/s390_dump.h" + +/* + * The first megabyte of memory is used for zlib workspace and + * the output buffer for compressed data. + */ +static void *zlib_out_buf; // zlib output buffer +static unsigned long zlib_buf_size; // zlib output buffer size + +/* Zlib workspace memory offset (skipping first 64K for the dumper itself) */ +#define ZLIB_WORKSPACE_OFFSET IMAGE_ENTRY + +/* + * Zlib workspace initialization. + * Return -1 if zlib deflate workspace requires more than half of the + * reserved memory area. Otherwise return 0. + */ +int zlib_workarea_init(unsigned long addr, z_stream *strm) +{ + strm->workspace = (void *)MAX(addr, ZLIB_WORKSPACE_OFFSET); + zlib_out_buf = (void *)ROUND_UP((unsigned long)strm->workspace + + zlib_deflate_workspacesize(MAX_WBITS, MAX_MEM_LEVEL), + PAGE_SIZE); + /* Make sure there is enough space left for zlib output buffer (at least half) */ + if ((unsigned long)(zlib_out_buf - strm->workspace) > ZLIB_WORKSPACE_LIMIT / 2) + return -1; + /* Memory left for zlib output buffer (currently 692K) */ + zlib_buf_size = addr + ZLIB_WORKSPACE_LIMIT - (unsigned long)zlib_out_buf; + return 0; +} + +#define COMPRESSED 0 +#define UNCOMPRESSED 1 +#define COMPRESSION_ERROR -1 + +/* + * Compress chunk of data of given length at the given address using zlib + * deflate compression and write it starting from the given block number. + * Variable *blk is updated to the next free block number. + * Return 0 if memory chunk is successfully compressed and written. + * Return 1 if compression is ineffective and the entire chunk is written + * uncompressed. + * In case of deflate error, write the chunk uncompressed and return -1 + */ +static int compress_write_next_chunk(unsigned long addr, unsigned long len, + unsigned long *blk, z_stream *strm) +{ + unsigned long start_blk; + int rc; + + /* Reset the stream for each new chunk */ + zlib_deflateReset(strm); + strm->next_in = (void *)addr; + strm->avail_in = len; + strm->next_out = zlib_out_buf; + strm->avail_out = zlib_buf_size; + start_blk = *blk; + while (strm->avail_in != 0) { + rc = zlib_deflate(strm, Z_NO_FLUSH); + /* + * Compression error. Write this data chunk uncompressed and + * notify the caller. + */ + if (rc != Z_OK) { + *blk = write_addr_range(start_blk, addr, len); + return COMPRESSION_ERROR; + } + if (strm->avail_out == 0) { + /* + * If compressed output is larger than the input, write this chunk of + * data uncompressed and notify the caller with the return code. + * Do it only if no compressed output has been written yet. + */ + if (strm->total_out >= strm->total_in && + *blk == start_blk) { + *blk = write_addr_range(start_blk, addr, len); + return UNCOMPRESSED; + } + *blk = write_addr_range(*blk, (unsigned long)zlib_out_buf, + zlib_buf_size); + strm->next_out = (void *)zlib_out_buf; + strm->avail_out = zlib_buf_size; + } + } + while (rc == Z_OK) { + strm->next_in = NULL; + strm->avail_in = 0; + rc = zlib_deflate(strm, Z_FINISH); + len = ROUND_UP(zlib_buf_size - strm->avail_out, PAGE_SIZE); + *blk = write_addr_range(*blk, (unsigned long)zlib_out_buf, len); + if (strm->avail_out == 0) { + strm->next_out = zlib_out_buf; + strm->avail_out = zlib_buf_size; + rc = Z_OK; + } else { + break; + } + } + return COMPRESSED; +} + +/* + * Compress and write memory dump segment with the uncompressed header to DASD + * and return the next free block number. + * The compression takes place in chunks of data of equal size (currently 1MB) + * and the offset of each compressed chunk is stored in the dump segment + * header. Due to limited size of header, the maximum size of compressed dump + * segment is limited to DUMP_SEGM_ZLIB_MAXLEN. + * If compression of a memory chunk leads to the data expansion (due to + * incompressible input), the chunk of data is written uncompressed. + * Thus every chunk of data is compressed separately and can be + * decompressed independently. The main reason is to enable zgetdump to make + * fast read seeks. Otherwise, zgetdump would need to uncompress a big dump + * segment in the worst case to extract a single piece of data. + */ +unsigned long write_compressed_dump_segment(unsigned long blk, + struct df_s390_dump_segm_hdr *segm, + z_stream *strm) +{ + unsigned long head_blk, start_blk, zero_page, len, offset = 0; + unsigned long chunk_size; + int rc; + + head_blk = blk; + /* Skip one block for the header (written later on) */ + blk += m2b(sizeof(struct df_s390_dump_segm_hdr)); + /* Compress each data chunk of the dump segment separately */ + chunk_size = dump_hdr->zlib_entry_size; + for (unsigned int i = 0; i <= segm->len / chunk_size; i++) { + /* Save starting block number */ + start_blk = blk; + len = i < segm->len / chunk_size ? + chunk_size : segm->len % chunk_size; + if (len == 0) + break; + rc = compress_write_next_chunk(segm->start + i * chunk_size, + len, &blk, strm); + /* + * Store the offset to the compressed chunk of data written + * to disk in blocks. + */ + segm->entry_count++; + segm->entry_offset[i] = (uint32_t)offset; + /* + * Compression was ineffective or compression error ocurred, + * data chunk has benn written uncompressed. + */ + if (rc == UNCOMPRESSED || rc == COMPRESSION_ERROR) + segm->entry_offset[i] |= DUMP_SEGM_ENTRY_UNCOMPRESSED; + offset += blk - start_blk; + progress_print(segm->start + i * chunk_size); + } + /* Compression successful, store compressed size in the segment header */ + segm->size_on_disk = (uint32_t)offset; + /* + * Write the dump segment header itself (1 page, uncompressed) to + * the predefined location. + */ + zero_page = get_zeroed_page(); + writeblock(head_blk, (unsigned long)segm, + m2b(sizeof(struct df_s390_dump_segm_hdr)), zero_page); + free_page(zero_page); + + return blk; +} diff --git a/zipl/boot/eckd2dump_zlib.h b/zipl/boot/eckd2dump_zlib.h new file mode 100644 index 00000000..ff037c6a --- /dev/null +++ b/zipl/boot/eckd2dump_zlib.h @@ -0,0 +1,25 @@ +/* + * zipl - zSeries Initial Program Loader tool + * + * Common ECKD dump I/O functions for zlib compression support + * + * 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. + */ +#ifndef ECKD2DUMP_ZLIB_H +#define ECKD2DUMP_ZLIB_H + +#include "zlib/zlib.h" +#include "dump/s390_dump.h" + +#define ZLIB_WORKSPACE_LIMIT (1 * MIB) + +/* Compression related functions */ +int zlib_workarea_init(unsigned long addr, z_stream *strm); +unsigned long write_compressed_dump_segment(unsigned long blk, + struct df_s390_dump_segm_hdr *segm, + z_stream *strm); + +#endif /* ECKD2DUMP_ZLIB_H */ diff --git a/zipl/boot/stage2dump.c b/zipl/boot/stage2dump.c index a641d44a..98f18835 100644 --- a/zipl/boot/stage2dump.c +++ b/zipl/boot/stage2dump.c @@ -20,6 +20,7 @@ #include "stage2dump.h" #include "boot/ipl.h" #include "boot/os_info.h" +#include "boot/s390.h" #define CPU_ADDRESS_MAX 1000 #define MACHINE_HAS_VX machine_has_vx @@ -223,6 +224,11 @@ static void df_s390_dump_init(void) get_cpu_id((struct cpuid *) &dh->cpu_id); dh->tod = get_tod_clock(); dh->volnr = 0; + dh->zlib_version_s390 = 0; + if (test_facility(DFLTCC_FACILITY)) { + dh->zlib_version_s390 = 1; /* Indicate interlnal Zlib version */ + dh->zlib_entry_size = DUMP_SEGM_ZLIB_ENTSIZE; + } } /* @@ -445,6 +451,9 @@ void __noreturn start(void) init_early(); dt_device_parm_setup(); sclp_setup(SCLP_INIT); + /* Store facility-list for future checks */ + stfle(S390_lowcore.stfle_fac_list, + ARRAY_SIZE(S390_lowcore.stfle_fac_list)); dt_device_enable(); df_s390_dump_init(); printf("zIPL v%s dump tool (64 bit)", RELEASE_STRING); diff --git a/zipl/boot/stage2dump.h b/zipl/boot/stage2dump.h index 8a92d34e..b039d9c3 100644 --- a/zipl/boot/stage2dump.h +++ b/zipl/boot/stage2dump.h @@ -17,6 +17,7 @@ #define IPL_SC S390_lowcore.tpi_info.schid #define ROUND_DOWN(x, a) ((x) & ~((typeof(x))(a) - 1)) +#define ROUND_UP(x, a) ROUND_DOWN((x) + (typeof(x))(a) - 1, a) #define IS_ALIGNED(x, a) ~((x) & ((typeof(x))(a) - 1)) /* diff --git a/zipl/man/zipl.8.in b/zipl/man/zipl.8.in index 1e85e3fb..463bd117 100644 --- a/zipl/man/zipl.8.in +++ b/zipl/man/zipl.8.in @@ -260,6 +260,11 @@ Supported devices are DASD ECKD or FBA disk partitions, device mapper multipath partitions of FCP attached SCSI disks, partitions of NVMe disks and IBM 3480/3490/3590/3592 tape devices. +For CCW-type DASD dump, zlib compression is used to compress the +dump data before writing it to the DASD ECKD partition. Zlib compression +requires the DFLTCC facility. Compression might increase dumping +performance, thus minimizing system downtime. It also saves DASD space. + With the exception of SCSI/NVMe, an optional decimal SIZE parameter may be specified to determine the maximum dump size in bytes. SIZE can be suffixed by either of the letters K, M or G to signify that the decimal number be