From d53bfb9201925c92a54564d7b4da2d8e6c797afc Mon Sep 17 00:00:00 2001 From: Mikhail Zaslonko Date: Fri, 2 Dec 2022 10:42:45 +0100 Subject: [PATCH] zipl/boot: Integrate zlib compression to single volume DASD dumper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Integrate zlib DFLTCC deflate compression to single volume dasd dumper using the existing s390 extended dump format. Compression takes place only if DFLTCC facility is available, otherwise dump is written uncompressed as before. First megabyte of memory is always written uncompressed and afterwards this area is used for zlib workspace and for the compression output buffer. 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. Since existing dump segment headers of 1 page size are used, we need to limit the maximum size of compressed dump segments. Chunk is written uncompressed in case of compression error or if deflate compression only makes it bigger. Thus every chunk of data is compressed separately and can be decompressed independently. The main reason for that is to enable zgetdump to make fast read seeks. Otherwise, zgetdump would need to decompress a big dump segment in the worst case to extract a single piece of data. Put compression related functions and structures to eckd2dump_zlib.c and eckd2dump_zlib.h Update zipl man page with the general information of zlib compression support. Signed-off-by: Mikhail Zaslonko Reviewed-by: Alexander Egorenkov Signed-off-by: Jan Höppner --- include/boot/s390.h | 1 + include/dump/s390_dump.h | 48 ++++++++-- zipl/boot/eckd2dump_sv.c | 103 ++++++++++++++++++--- zipl/boot/eckd2dump_zlib.c | 182 +++++++++++++++++++++++++++++++++++++ zipl/boot/eckd2dump_zlib.h | 25 +++++ zipl/boot/stage2dump.c | 9 ++ zipl/boot/stage2dump.h | 1 + zipl/man/zipl.8.in | 5 + 8 files changed, 354 insertions(+), 20 deletions(-) create mode 100644 zipl/boot/eckd2dump_zlib.c create mode 100644 zipl/boot/eckd2dump_zlib.h 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