Files
s390-tools/zipl/src/boot.c
Mikhail Zaslonko c61783546b zipl: Add --no-compress option to zipl command
Add --no-compress option to explicitly omit compression for single-volume
DASD dumper. Used primarily for test purposes.

Since only the lowest byte of mvdump_force field (struct
stage2dump_parm_tail) has been used, split it in two byte fields and use
one for the new no_compress attribute.

Update zipl help and zipl man page with the new parameter info.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-09-27 18:29:06 +02:00

648 lines
18 KiB
C

/*
* zipl - zSeries Initial Program Loader tool
*
* Functions to handle the boot loader data
*
* Copyright IBM Corp. 2001, 2017
*
* 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 <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include "lib/util_libc.h"
#include "boot/loaders_layout.h"
#include "stage2dump.h"
#include "stage3.h"
#include "boot.h"
#include "bootmap.h"
#include "error.h"
#include "misc.h"
/* Import a binary file */
/* clang-format off */
#define DATA_NAME(SYM, SUFFIX) _binary_##SYM##_bin##SUFFIX
#define DATA_SIZE(SYM) ((size_t)(&DATA_NAME(SYM, _end) - &DATA_NAME(SYM, _start)))
#define DATA_ADDR(SYM) (&DATA_NAME(SYM, _start))
#define BIN_FILE_PATH(FILE_NAME) STRINGIFY(BUILD_PATH) "/" STRINGIFY(FILE_NAME) ".bin"
#define IMPORT_DATA(SYM) \
extern const uint8_t DATA_NAME(SYM, _start); \
extern const uint8_t DATA_NAME(SYM, _end); \
asm(".section \".rodata\", \"a\", @progbits\n" \
".balign 4\n" \
".global " STRINGIFY(DATA_NAME(SYM, _start)) "\n" \
STRINGIFY(DATA_NAME(SYM, _start)) ":\n" \
".incbin \"" BIN_FILE_PATH(SYM) "\"\n" \
".global " STRINGIFY(DATA_NAME(SYM, _end)) "\n" \
STRINGIFY(DATA_NAME(SYM, _end)) ":\n" \
".balign 4\n" \
".previous\n")
/* clang-format on */
/* Stage 0 Loader */
IMPORT_DATA(eckd0_cdl);
IMPORT_DATA(eckd0_ldl);
IMPORT_DATA(fba0);
IMPORT_DATA(tape0);
/* Stage 1 Loader */
IMPORT_DATA(eckd1);
/* Stage 1b Loader */
IMPORT_DATA(eckd1b);
IMPORT_DATA(fba1b);
/* Stage 2 Loader */
IMPORT_DATA(eckd2);
IMPORT_DATA(fba2);
/* Stage 2 Dump Loader */
IMPORT_DATA(eckd2dump_mv);
IMPORT_DATA(eckd2dump_sv);
IMPORT_DATA(fba2dump);
IMPORT_DATA(tape2dump);
#define CCW_FLAG_CC 0x40
#define CCW_FLAG_SLI 0x20
#define FBA_BLK_SIZE 512
static struct boot_ccw0 tic_to_stage1b = {
.cmd = 0x08, /* tic */
.address_lo = STAGE1B_LOAD_ADDRESS,
};
/* Check sizes of internal objects. Return 0 if everything is correct,
* non-zero otherwise. */
int
boot_check_data(void)
{
if (DATA_SIZE(fba0) != sizeof(struct boot_fba_stage0)) {
error_reason("Size mismatch of FBA stage 0 loader");
return -1;
}
if (DATA_SIZE(fba1b) != sizeof(struct boot_fba_stage1b)) {
error_reason("Size mismatch of FBA stage 1b loader");
return -1;
}
if (DATA_SIZE(eckd0_ldl) !=
sizeof(struct boot_eckd_ldl_stage0)) {
error_reason("Size mismatch of ECKD LDL stage 0 loader");
return -1;
}
if (DATA_SIZE(eckd0_cdl) != sizeof(struct boot_eckd_cdl_stage0)) {
error_reason("Size mismatch of ECKD CDL stage 0 loader");
return -1;
}
if (DATA_SIZE(eckd1) != sizeof(struct boot_eckd_stage1)) {
error_reason("Size mismatch of ECKD stage 1 loader");
return -1;
}
if (DATA_SIZE(eckd1b) != sizeof(struct boot_eckd_stage1b)) {
error_reason("Size mismatch of ECKD stage 1b loader");
return -1;
}
return 0;
}
/*
* Create a stage 3 parameter block in memory.
* Upon success, return 0 and set BUFFER to point to the data buffer and set
* BYTECOUNT to contain the parameter block size in bytes.
* Return non-zero otherwise.
*/
int
boot_get_stage3_parms(void **buffer, size_t *bytecount, address_t parm_addr,
address_t initrd_addr, size_t initrd_len,
address_t entry, int extra_parm, uint64_t flags,
address_t image_addr, size_t image_len,
address_t envblk_addr, size_t envblk_len)
{
struct stage3_parms params;
void* data;
if (entry != (entry & PSW32_ADDR_MASK)) {
error_reason("Kernel image entry point to high (31 bit "
"addressing mode)");
return -1;
}
/* Get memory */
data = misc_malloc(sizeof(params));
if (data == NULL)
return -1;
memset(data, 0, sizeof(params));
/* Prepare params section */
params.parm_addr = (uint64_t) parm_addr;
params.initrd_addr = (uint64_t) initrd_addr;
params.initrd_len = (uint64_t) initrd_len;
params.load_psw = (uint64_t)(entry | PSW_LOAD);
params.extra_parm = (uint64_t) extra_parm;
params.flags = flags;
params.image_len = (uint64_t) image_len;
params.image_addr = (uint64_t) image_addr;
params.envblk_addr = (uint64_t) envblk_addr;
params.envblk_len = (uint64_t) envblk_len;
/* Initialize buffer */
memcpy(data, &params, sizeof(params));
*buffer = data;
*bytecount = sizeof(params);
return 0;
}
int
boot_init_fba_stage0(struct boot_fba_stage0 *stage0,
disk_blockptr_t *stage1b_list, blocknum_t stage1b_count)
{
blocknum_t i;
/* Initialize stage 0 data */
memcpy(stage0, DATA_ADDR(fba0), DATA_SIZE(fba0));
/* Fill in blocklist for stage 2 loader */
if (stage1b_count > STAGE1B_BLK_CNT_MAX) {
error_reason("Not enough room for FBA stage 1b loader");
return -1;
}
for (i = 0; i < stage1b_count; i++) {
stage0->locdata[i].blocknr =
(uint32_t) stage1b_list[i].linear.block;
stage0->locread[i].read.address_lo = STAGE1B_LOAD_ADDRESS + i * FBA_BLK_SIZE;
}
/* Terminate CCW chain: Tic to stage 1b */
memcpy(&stage0->locread[i], &tic_to_stage1b, sizeof(tic_to_stage1b));
return 0;
}
void
boot_init_eckd_ldl_stage0(struct boot_eckd_ldl_stage0 *stage0)
{
memcpy(stage0, DATA_ADDR(eckd0_ldl), DATA_SIZE(eckd0_ldl));
/* Fill in size of stage 1 plus stage 0 loader */
stage0->read_r1.count = sizeof(struct boot_eckd_stage1) +
sizeof(struct boot_eckd_ldl_stage0);
}
void
boot_init_eckd_cdl_stage0(struct boot_eckd_cdl_stage0 *stage0)
{
memcpy(stage0, DATA_ADDR(eckd0_cdl), DATA_SIZE(eckd0_cdl));
/* Fill in size of stage 1 loader */
stage0->read.count = sizeof(struct boot_eckd_stage1);
}
int
boot_init_eckd_stage1(struct boot_eckd_stage1 *stage1,
disk_blockptr_t *stage1b_list, blocknum_t stage1b_count)
{
blocknum_t i;
memcpy(stage1, DATA_ADDR(eckd1), DATA_SIZE(eckd1));
/* Fill in blocklist for stage 1b loader */
if (stage1b_count > STAGE1B_BLK_CNT_MAX) {
error_reason("Not enough room for ECKD stage 1b loader "
"(try larger block size)");
return -1;
}
for (i = 0; i < stage1b_count; i++) {
stage1->ssrt[i].read.count = stage1b_list[i].chs.size;
stage1->seek[i].cyl = stage1b_list[i].chs.cyl;
stage1->seek[i].head = stage1b_list[i].chs.head |
((stage1b_list[i].chs.cyl >> 12) & 0xfff0);
stage1->seek[i].sec = stage1b_list[i].chs.sec;
stage1->ssrt[i].read.address_lo =
STAGE1B_LOAD_ADDRESS + i * stage1b_list[i].chs.size;
stage1->ssrt[i].read.flags = CCW_FLAG_CC | CCW_FLAG_SLI;
}
/* Terminate CCW chain: Tic to stage 1b */
memcpy(&stage1->ssrt[i], &tic_to_stage1b, sizeof(tic_to_stage1b));
return 0;
}
int
boot_init_fba_stage1b(struct boot_fba_stage1b *stage1b,
disk_blockptr_t *stage2_list, blocknum_t stage2_count)
{
blocknum_t i;
memcpy(stage1b, DATA_ADDR(fba1b), DATA_SIZE(fba1b));
if (stage2_count > STAGE2_BLK_CNT_MAX) {
error_reason("Not enough room for FBA stage 2 loader");
return -1;
}
for (i = 0; i < stage2_count; i++) {
stage1b->locdata[i].blocknr =
(uint32_t) stage2_list[i].linear.block;
stage1b->locread[i].read.address_lo =
STAGE2_LOAD_ADDRESS + i * FBA_BLK_SIZE;
}
/* Terminate CCW chain */
stage1b->locread[i - 1].read.flags &= ~CCW_FLAG_CC;
return 0;
}
int
boot_init_eckd_stage1b(struct boot_eckd_stage1b *stage1b,
disk_blockptr_t *stage2_list, blocknum_t stage2_count)
{
blocknum_t i;
memcpy(stage1b, DATA_ADDR(eckd1b), DATA_SIZE(eckd1b));
if (stage2_count > STAGE2_BLK_CNT_MAX) {
error_reason("Not enough room for ECKD stage 2 loader "
"(try larger block size)");
return -1;
}
for (i = 0; i < stage2_count; i++) {
stage1b->ssrt[i].read.count = stage2_list[i].chs.size;
stage1b->seek[i].cyl = stage2_list[i].chs.cyl;
stage1b->seek[i].head = stage2_list[i].chs.head |
((stage2_list[i].chs.cyl >> 12) & 0xfff0);
stage1b->seek[i].sec = stage2_list[i].chs.sec;
stage1b->ssrt[i].read.address_lo = STAGE2_LOAD_ADDRESS +
i * stage2_list[i].chs.size;
stage1b->ssrt[i].read.flags = CCW_FLAG_CC | CCW_FLAG_SLI;
}
/* Terminate CCW chain */
stage1b->ssrt[i - 1].read.flags &= ~CCW_FLAG_CC;
return 0;
}
int
boot_get_tape_ipl(void** data, size_t* size, address_t parm_addr,
address_t initrd_addr, address_t image_addr)
{
struct boot_tape_ipl_params params;
void* buffer;
if (image_addr != (image_addr & PSW32_ADDR_MASK)) {
error_reason("Kernel image load address to high (31 bit "
"addressing mode)");
return -1;
}
buffer = misc_malloc(DATA_SIZE(tape0));
if (buffer == NULL)
return -1;
/* Prepare params section */
params.parm_addr = (uint64_t) parm_addr;
params.initrd_addr = (uint64_t) initrd_addr;
params.load_psw = (uint64_t) (image_addr | PSW_LOAD);
/* Initialize buffer */
memcpy(buffer, DATA_ADDR(tape0), DATA_SIZE(tape0));
memcpy(VOID_ADD(buffer, BOOT_TAPE_IPL_PARAMS_OFFSET), &params,
sizeof(struct boot_tape_ipl_params));
*data = buffer;
*size = DATA_SIZE(tape0);
return 0;
}
struct menu_buffer {
void *start;
size_t size;
size_t off;
};
static void
mb_init(struct menu_buffer *buffer, void *data, size_t size)
{
buffer-> start = data;
buffer->size = size;
buffer->off = 0;
memset(data, 0, size);
}
static void *
mb_alloc(struct menu_buffer *buffer, size_t len)
{
void *result;
if (buffer->off + len > buffer->size)
return NULL;
result = VOID_ADD(buffer->start, buffer->off);
buffer->off += len;
return result;
}
static void*
mb_sprintf(struct menu_buffer *buffer, const char *fmt, ...)
{
va_list args;
int size;
int len;
char *str;
str = VOID_ADD(buffer->start, buffer->off);
size = buffer->size - buffer->off;
va_start(args, fmt);
len = vsnprintf(str, size, fmt, args);
va_end(args);
if (len < 0 || len >= size)
return NULL;
misc_ascii_to_ebcdic((unsigned char *) str,
(unsigned char *) str + len);
mb_alloc(buffer, len + 1);
return str;
}
static int
store_stage2_menu(void* data, size_t size, struct job_data* job)
{
struct boot_stage2_params* params;
char* name;
int flag;
int timeout;
int i;
struct menu_buffer mb;
void *str;
uint64_t config_kdump = 0;
mb_init(&mb, data, size);
if (job->id == job_menu) {
name = "-";
for (i = 0; i < job->data.menu.num; i++) {
if (job->data.menu.entry[i].id == job_ipl &&
job->data.menu.entry[i].data.ipl.is_kdump)
/* we start with 2nd bit */
config_kdump |= (0x1 << (i + 1));
if (job->data.menu.entry[i].pos ==
job->data.menu.default_pos) {
if (job->data.menu.entry[i].id == job_ipl &&
job->data.menu.entry[i].data.ipl.is_kdump)
/* default entry is first bit */
config_kdump |= 0x1;
name = job->data.menu.entry[i].name;
}
}
flag = (job->data.menu.prompt != 0);
timeout = job->data.menu.timeout;
/* Be verbose */
if (verbose) {
printf("Preparing boot menu\n");
printf(" Interactive prompt......: %s\n",
job->data.menu.prompt ? "enabled" : "disabled");
if (job->data.menu.timeout == 0)
printf(" Menu timeout............: "
"disabled\n");
else
printf(" Menu timeout............: %d "
"seconds\n", job->data.menu.timeout);
printf(" Default configuration...: '%s'\n", name);
}
} else {
if (job->id == job_ipl && job->data.ipl.is_kdump)
config_kdump |= 0x1;
name = job->name;
flag = 0;
timeout = 0;
}
/* Header */
params = mb_alloc(&mb, sizeof(struct boot_stage2_params));
if (!params)
goto err_nospace;
params->flag = flag;
params->config_kdump = config_kdump;
params->timeout = timeout;
/* Banner text */
str = mb_sprintf(&mb, "zIPL v%s interactive boot menu\n ",
RELEASE_STRING);
if (!str)
goto err_nospace;
params->banner = (uint16_t) ((unsigned long) str -
(unsigned long) data);
/* Default config text */
if (name != NULL)
str = mb_sprintf(&mb, " 0. default (%s)", name);
else
str = mb_sprintf(&mb, " 0. default");
if (!str)
goto err_nospace;
params->config[0] = (uint16_t) ((unsigned long) str -
(unsigned long) data);
/* Skip rest if job is not an actual menu */
if (job->id != job_menu)
return 0;
/* Config texts */
for (i = 0; i < job->data.menu.num; i++) {
if (job->data.menu.entry[i].data.ipl.common.ignore) {
params->config[job->data.menu.entry[i].pos] = 0;
continue;
}
const char *kdump_str = "";
if (job->data.menu.entry[i].data.ipl.is_kdump)
kdump_str = " (kdump)";
str = mb_sprintf(&mb, "%2d. %s%s",
job->data.menu.entry[i].pos,
job->data.menu.entry[i].name,
kdump_str);
if (!str)
goto err_nospace_user;
params->config[job->data.menu.entry[i].pos] = (uint16_t)
((unsigned long) str - (unsigned long) data);
}
return 0;
err_nospace:
error_reason("Not enough room for menu data");
return -1;
err_nospace_user:
error_reason("Not enough room for menu data (try fewer sections or "
"shorter names)");
return -1;
}
int
boot_get_fba_stage2(void** data, size_t* size, struct job_data* job)
{
void* buffer;
int rc;
buffer = misc_malloc(STAGE2_MAX_SIZE);
if (buffer == NULL)
return -1;
memcpy(buffer, DATA_ADDR(fba2), DATA_SIZE(fba2));
rc = store_stage2_menu(VOID_ADD(buffer, DATA_SIZE(fba2)),
STAGE2_MAX_SIZE - DATA_SIZE(fba2),
job);
if (rc) {
free(buffer);
return rc;
}
*data = buffer;
*size = STAGE2_MAX_SIZE;
return 0;
}
int
boot_get_eckd_stage2(void** data, size_t* size, struct job_data* job)
{
void* buffer;
int rc;
buffer = misc_malloc(STAGE2_MAX_SIZE);
if (buffer == NULL)
return -1;
memcpy(buffer, DATA_ADDR(eckd2), DATA_SIZE(eckd2));
rc = store_stage2_menu(VOID_ADD(buffer, DATA_SIZE(eckd2)),
STAGE2_MAX_SIZE - DATA_SIZE(eckd2),
job);
if (rc) {
free(buffer);
return rc;
}
*data = buffer;
*size = STAGE2_MAX_SIZE;
return 0;
}
int
boot_get_tape_dump(void **data, size_t *size,
const struct stage2dump_parm_tail *stage2dump_parms)
{
void *buffer;
size_t offset;
buffer = misc_malloc(DATA_SIZE(tape2dump));
if (buffer == NULL)
return -1;
memcpy(buffer, DATA_ADDR(tape2dump), DATA_SIZE(tape2dump));
/* Write mem size to end of dump record */
offset = DATA_SIZE(tape2dump) - sizeof(struct stage2dump_parm_tail);
memcpy(VOID_ADD(buffer, offset), stage2dump_parms, sizeof(struct stage2dump_parm_tail));
*data = buffer;
*size = DATA_SIZE(tape2dump);
return 0;
}
int
boot_get_eckd_dump_stage2(void **data, size_t *size,
const struct stage2dump_parm_tail *stage2dump_parms)
{
void *buffer;
size_t offset;
buffer = misc_malloc(DATA_SIZE(eckd2dump_sv));
if (buffer == NULL)
return -1;
memcpy(buffer, DATA_ADDR(eckd2dump_sv), DATA_SIZE(eckd2dump_sv));
/* Write mem size and no-compression indicator to end of dump record */
offset = DATA_SIZE(eckd2dump_sv) - sizeof(struct stage2dump_parm_tail);
memcpy(VOID_ADD(buffer, offset), stage2dump_parms,
sizeof(struct stage2dump_parm_tail));
*data = buffer;
*size = DATA_SIZE(eckd2dump_sv);
return 0;
}
int
boot_get_eckd_mvdump_stage2(void **data, size_t *size,
const struct stage2dump_parm_tail *stage2dump_parms,
const struct mvdump_parm_table *mv_parm_table)
{
void *buffer;
size_t offset;
buffer = misc_malloc(DATA_SIZE(eckd2dump_mv));
if (buffer == NULL)
return -1;
memcpy(buffer, DATA_ADDR(eckd2dump_mv), DATA_SIZE(eckd2dump_mv));
/*
* Write stage2 dump parameters to end of dump record, right before
* 512-byte parameter table
*/
offset = DATA_SIZE(eckd2dump_mv) - sizeof(struct mvdump_parm_table);
memcpy(VOID_ADD(buffer, offset), mv_parm_table,
sizeof(struct mvdump_parm_table));
offset = offset - sizeof(struct stage2dump_parm_tail);
memcpy(VOID_ADD(buffer, offset), stage2dump_parms,
sizeof(struct stage2dump_parm_tail));
*data = buffer;
*size = DATA_SIZE(eckd2dump_mv);
return 0;
}
int
boot_get_fba_dump_stage2(void **data, size_t *size,
const struct stage2dump_parm_tail *stage2dump_parms)
{
void *buffer;
size_t offset;
buffer = misc_malloc(DATA_SIZE(fba2dump));
if (buffer == NULL)
return -1;
memcpy(buffer, DATA_ADDR(fba2dump), DATA_SIZE(fba2dump));
/* Write mem size to end of dump record */
offset = DATA_SIZE(fba2dump) - sizeof(struct stage2dump_parm_tail);
memcpy(VOID_ADD(buffer, offset), stage2dump_parms,
sizeof(struct stage2dump_parm_tail));
*data = buffer;
*size = DATA_SIZE(fba2dump);
return 0;
}
void
boot_get_dump_info(struct boot_info *boot_info, uint8_t dev_type, void *param)
{
memset(boot_info, 0, sizeof(*boot_info));
memcpy(&boot_info->magic, BOOT_INFO_MAGIC, sizeof(boot_info->magic));
boot_info->flags |= BOOT_INFO_FLAGS_ARCH;
boot_info->dev_type = dev_type;
boot_info->bp_type = BOOT_INFO_BP_TYPE_DUMP;
boot_info->version = BOOT_INFO_VERSION;
memcpy(&boot_info->bp.dump.param, param,
sizeof(boot_info->bp.dump.param));
}
/*
* Helper function to install a boot record for CCW-type IPL from DASD
*/
void
boot_get_ipl_info_ccw(struct boot_info *boot_info, uint8_t dev_type,
disk_blockptr_t *bm_ptr, struct disk_info *info)
{
memset(boot_info, 0, sizeof(*boot_info));
memcpy(&boot_info->magic, BOOT_INFO_MAGIC, sizeof(boot_info->magic));
boot_info->flags |= BOOT_INFO_FLAGS_ARCH;
boot_info->dev_type = dev_type;
boot_info->bp_type = BOOT_INFO_BP_TYPE_IPL;
boot_info->version = BOOT_INFO_VERSION;
bootmap_store_blockptr(&boot_info->bp, bm_ptr, info,
LEGACY_BLKPTR_FORMAT_ID);
}
/**
* Allocate and initialize a boot record for List-Directed IPL from DASD
*
* TABLE: address of an actual program table
*/
int boot_get_eckd_ld_ipl_br(void **br_ptr, size_t *size_ptr,
disk_blockptr_t *table, struct disk_info *info)
{
struct eckd_boot_record *br;
br = util_zalloc(info->phy_block_size);
if (!br)
return -1;
memcpy(&br->magic, ZIPL_MAGIC, ZIPL_MAGIC_SIZE);
br->version_id = DISK_LAYOUT_ID;
bootmap_store_blockptr(&br->program_table_pointer, table, info,
BLKPTR_FORMAT_ID);
br->os_id = 0x55aa; /* LINUX */
*br_ptr = br;
*size_ptr = info->phy_block_size;
return 0;
}