mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
- Move zipl/boot/error.h to include/boot - Adjust include statements in zipb/boot and genprotimg/boot - Remove error.h from tunedasd/src/tunedasd.c as not needed - Fix tunedasd/src/Makefile Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com> Reviewed-by: Marc Hartmayer <mhartmay@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
|
|
*
|
|
* Main program for stage3 bootloader
|
|
*
|
|
* Copyright IBM Corp. 2013, 2018
|
|
*
|
|
* 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 "libc.h"
|
|
#include "sclp.h"
|
|
#include "menu.h"
|
|
#include "boot/error.h"
|
|
#include "boot/sigp.h"
|
|
#include "boot/s390.h"
|
|
#include "boot/sigp.h"
|
|
#include "boot/linux_layout.h"
|
|
#include "boot/loaders_layout.h"
|
|
|
|
#include "stage3.h"
|
|
#include "ebcdic.h"
|
|
#include "ebcdic_conv.h"
|
|
|
|
#define for_each_rb_entry(entry, rb) \
|
|
for (entry = rb->entries; \
|
|
(void *) entry + sizeof(*entry) <= (void *) rb + rb->len; \
|
|
entry++)
|
|
|
|
static const char *msg_sipl_inval = "Secure boot failure: invalid load address";
|
|
static const char *msg_sipl_unverified = "Secure boot failure: unverified load address";
|
|
static const char *msg_sipl_noparm = "Secure boot failure: unable to load ipl parameter";
|
|
|
|
static inline void __noreturn start_kernel(void)
|
|
{
|
|
struct psw_t *psw = &S390_lowcore.program_new_psw;
|
|
unsigned long addr, code;
|
|
|
|
/* Setup program check handler */
|
|
psw->mask = 0x000000180000000ULL;
|
|
code = 1;
|
|
|
|
asm volatile(
|
|
/* Setup program check handler */
|
|
" larl %[addr],.no_diag308\n"
|
|
" stg %[addr],8(%[psw])\n"
|
|
" diag %[code],%[code],0x308\n"
|
|
".no_diag308:\n"
|
|
" sam31\n"
|
|
" sr %r1,%r1\n"
|
|
" sr %r2,%r2\n"
|
|
" sigp %r1,%r2,%[order]\n"
|
|
" lpsw 0\n"
|
|
: [addr] "=&d" (addr),
|
|
[code] "+&d" (code)
|
|
: [psw] "a" (psw),
|
|
[order] "L" (SIGP_SET_ARCHITECTURE));
|
|
while (1);
|
|
}
|
|
|
|
unsigned int
|
|
is_verified_address(unsigned long image_addr)
|
|
{
|
|
struct ipl_rb_component_entry *comp;
|
|
struct ipl_rb_components *comps;
|
|
struct ipl_pl_hdr *pl_hdr;
|
|
struct ipl_rl_hdr *rl_hdr;
|
|
struct ipl_rb_hdr *rb_hdr;
|
|
unsigned long tmp;
|
|
void *rl_end;
|
|
|
|
/*
|
|
* There is an IPL report, to find it load the pointer to the
|
|
* IPL parameter information block from lowcore and skip past
|
|
* the IPL parameter list, then align the address to a double
|
|
* word boundary.
|
|
*/
|
|
tmp = (unsigned long) S390_lowcore.ipl_parmblock_ptr;
|
|
pl_hdr = (struct ipl_pl_hdr *) tmp;
|
|
tmp = (tmp + pl_hdr->len + 7) & -8UL;
|
|
rl_hdr = (struct ipl_rl_hdr *) tmp;
|
|
/* Walk through the IPL report blocks in the IPL Report list */
|
|
comps = NULL;
|
|
rl_end = (void *) rl_hdr + rl_hdr->len;
|
|
rb_hdr = (void *) rl_hdr + sizeof(*rl_hdr);
|
|
while ((void *) rb_hdr + sizeof(*rb_hdr) < rl_end &&
|
|
(void *) rb_hdr + rb_hdr->len <= rl_end) {
|
|
switch (rb_hdr->rbt) {
|
|
case IPL_RBT_COMPONENTS:
|
|
comps = (struct ipl_rb_components *) rb_hdr;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
rb_hdr = (void *) rb_hdr + rb_hdr->len;
|
|
}
|
|
|
|
if (!comps)
|
|
return 0;
|
|
|
|
for_each_rb_entry(comp, comps) {
|
|
if (image_addr == comp->addr &&
|
|
comp->flags & IPL_RB_COMPONENT_FLAG_SIGNED &&
|
|
comp->flags & IPL_RB_COMPONENT_FLAG_VERIFIED)
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
unsigned int
|
|
secure_boot_enabled()
|
|
{
|
|
struct ipl_pl_hdr *pl_hdr;
|
|
unsigned int rc;
|
|
|
|
pl_hdr = (void *)get_zeroed_page();
|
|
switch (diag308(DIAG308_STORE, pl_hdr)) {
|
|
case DIAG308_RC_OK:
|
|
rc = pl_hdr->version <= IPL_MAX_SUPPORTED_VERSION &&
|
|
!!(pl_hdr->flags & IPL_FLAG_SECURE);
|
|
break;
|
|
case DIAG308_RC_NO_CONF:
|
|
rc = 0;
|
|
break;
|
|
default:
|
|
panic(ESECUREBOOT, "%s", msg_sipl_noparm);
|
|
break;
|
|
}
|
|
free_page((unsigned long) pl_hdr);
|
|
|
|
return rc;
|
|
}
|
|
|
|
#define ZIPL_ENVBLK_SIGNATURE "# zIPL Environment Block\n"
|
|
#define STR_HASH_SIZE (16)
|
|
|
|
struct env_hash_entry {
|
|
unsigned int next;
|
|
unsigned int name;
|
|
};
|
|
|
|
/**
|
|
* ITEMS: pointer to pre-allocated page, where the new item will be allocated
|
|
* NAME: null-terminated name
|
|
*/
|
|
static void hash_table_add(struct env_hash_entry *items,
|
|
struct env_hash_entry **buckets,
|
|
unsigned int *new_idx,
|
|
unsigned long name)
|
|
{
|
|
struct env_hash_entry *new;
|
|
unsigned int hash;
|
|
|
|
new = &items[*new_idx];
|
|
new->name = name;
|
|
hash = strhash((unsigned char *)name, NULL, STR_HASH_SIZE);
|
|
new->next = (unsigned long)buckets[hash];
|
|
buckets[hash] = new;
|
|
(*new_idx)++;
|
|
}
|
|
|
|
/**
|
|
* Check if string STR coincides with string SHORT_STR prepended
|
|
* with a logical PREFIX (if any).
|
|
* Return 1, if coincides. Otherwise, return 0.
|
|
*/
|
|
static int str_eq(char *str, char *short_str, char *prefix)
|
|
{
|
|
if (prefix)
|
|
return strlen(str) == strlen(short_str) + 1 &&
|
|
*str == *prefix &&
|
|
!strncmp(str + 1, short_str, strlen(short_str));
|
|
return strlen(str) == strlen(short_str) &&
|
|
!strncmp(str, short_str, strlen(str));
|
|
}
|
|
|
|
static struct env_hash_entry *hash_table_find(struct env_hash_entry **buckets,
|
|
char *name, char *prefix)
|
|
{
|
|
struct env_hash_entry *item;
|
|
|
|
if (strlen(name) == 0)
|
|
return NULL;
|
|
item = buckets[strhash((unsigned char *)name, prefix, STR_HASH_SIZE)];
|
|
while (item) {
|
|
if (str_eq((char *)(unsigned long)item->name, name, prefix))
|
|
return item;
|
|
item = (struct env_hash_entry *)(unsigned long)item->next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Format of environment block:
|
|
*
|
|
* magic'\n'name1=value1'\n'...nameN=valueN'\n'zero-padding
|
|
*/
|
|
static void parse_envblk(struct env_hash_entry *items,
|
|
struct env_hash_entry **buckets,
|
|
unsigned int *nr)
|
|
{
|
|
char sgn[] = ZIPL_ENVBLK_SIGNATURE;
|
|
unsigned int len = 0;
|
|
unsigned int off = 0;
|
|
char *name;
|
|
char *value;
|
|
|
|
ebcdic_to_ascii((unsigned char *)sgn,
|
|
(unsigned char *)sgn, sizeof(sgn) - 1);
|
|
|
|
if (strncmp((char *)_stage3_parms.envblk_addr, sgn, sizeof(sgn) - 1)) {
|
|
printf("Bad envblk\n");
|
|
return;
|
|
}
|
|
/* we rely that environment block is consistent */
|
|
name = (char *)_stage3_parms.envblk_addr + sizeof(sgn) - 1;
|
|
/*
|
|
* calculate significant length of the environment block
|
|
* (excluding trailing zeros)
|
|
*/
|
|
while (len < _stage3_parms.envblk_len) {
|
|
if (name[len] == 0)
|
|
break;
|
|
len++;
|
|
}
|
|
while (off < len) {
|
|
value = strchr(name, 0x3D /* = */) + 1;
|
|
/* null-terminate the name */
|
|
*(value - 1) = 0;
|
|
hash_table_add(items, buckets, nr, (unsigned long)name);
|
|
off += (value - name); /* offset of the value */
|
|
|
|
name = strchr(value, 0x0A /* /n */) + 1;
|
|
/* null-terminate the value */
|
|
*(name - 1) = 0;
|
|
off += (name - value); /* offset of the next name (if any) */
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Lookup by NAME prepended with optional PREFIX in the hash table.
|
|
* First, search by prefixed NAME. If nothing was found, then search
|
|
* by NAME without prefix
|
|
*/
|
|
static struct env_hash_entry *lookup_item(struct env_hash_entry **buckets,
|
|
char *name, char *prefix)
|
|
{
|
|
struct env_hash_entry *result;
|
|
|
|
result = hash_table_find(buckets, name, prefix);
|
|
if (result)
|
|
return result;
|
|
return prefix ? hash_table_find(buckets, name, NULL) : NULL;
|
|
}
|
|
|
|
/**
|
|
* Find all instances of the pattern ${NAME} in the command line, and replace
|
|
* each one with corresponding VALUE as found in the hash table by NAME
|
|
* and SITE_ID, using lookup_item() search procedure.
|
|
*
|
|
* CMDL_LEN: length of the command line to be processed
|
|
* BUCKETS: hash table of pairs (NAME, VALUE)
|
|
* SITE_ID: pointer to an alphanumerical string representing a number
|
|
*/
|
|
void process_parm_line(struct env_hash_entry **buckets, unsigned int cmdl_len,
|
|
unsigned int max_len, char *site_id)
|
|
{
|
|
struct env_hash_entry *item;
|
|
char empty_str = 0;
|
|
char *cmdl_end;
|
|
char *start;
|
|
char *end;
|
|
char *val;
|
|
int len;
|
|
|
|
start = (char *)COMMAND_LINE;
|
|
cmdl_end = start + cmdl_len;
|
|
|
|
while (start < cmdl_end) {
|
|
start = strchr(start, 0x24 /* $ */);
|
|
|
|
if (start == NULL || start + 3 >= cmdl_end)
|
|
/* no more instances */
|
|
break;
|
|
if (*(start + 1) != 0x7B /* { */) {
|
|
/* skip "$" without braces */
|
|
start += 1;
|
|
continue;
|
|
}
|
|
end = strchr(start, 0x7D /* } */);
|
|
if (end == NULL || end >= cmdl_end)
|
|
/* no more instances */
|
|
break;
|
|
/*
|
|
* terminate the NAME and find a respective item
|
|
* in the hash table by that NAME prefixed with
|
|
* site-ID (if any)
|
|
*/
|
|
*end = 0;
|
|
item = lookup_item(buckets, start + 2, site_id);
|
|
if (!item) {
|
|
/*
|
|
* Item to replace with not found.
|
|
* The case of "${}" (empty NAME) also gets here!
|
|
*
|
|
* Assign the empty string to remove the instance
|
|
* from the command line
|
|
*/
|
|
val = &empty_str;
|
|
} else {
|
|
val = (char *)(unsigned long)item->name +
|
|
strlen((char *)(unsigned long)item->name) + 1;
|
|
}
|
|
/*
|
|
* try to replace the instance by VALUE as found
|
|
* in the hash table by NAME
|
|
*/
|
|
len = strlen(val);
|
|
|
|
if (cmdl_len + len - (end - start + 1) >= max_len)
|
|
/* VALUE doesn't fit */
|
|
break;
|
|
/*
|
|
* make a room with the beginning at "$" by moving
|
|
* the rest of parm line at the right of "}" to @dst,
|
|
* which is (offset of "$" + size of the VALUE")
|
|
*/
|
|
memmove(start + len, end + 1, cmdl_end - (end + 1));
|
|
/*
|
|
* copy VALUE to the room
|
|
*/
|
|
memcpy(start, val, len);
|
|
cmdl_end += (len - (end - start + 1));
|
|
start += len;
|
|
}
|
|
if (cmdl_len > cmdl_end - (char *)COMMAND_LINE)
|
|
memset(cmdl_end, 0,
|
|
cmdl_len - (cmdl_end - (char *)COMMAND_LINE));
|
|
}
|
|
|
|
/*
|
|
* This represents a number as a pair consisting of an
|
|
* alphanumerical string, and a variable which contains
|
|
* its numerical value (as a result of strtoul conversion)
|
|
*/
|
|
struct nr_pair {
|
|
char str[PARAM_SIZE];
|
|
unsigned long val;
|
|
};
|
|
|
|
#define IPL_SC (*((struct subchannel_id *) &S390_lowcore.subchannel_id))
|
|
|
|
/**
|
|
* Parse LOADPARM to identify a site to be activated.
|
|
* If the site is identified, then store its ID in the structure
|
|
* pointed out by NP
|
|
*/
|
|
static void get_active_site_by_loadparm(struct nr_pair *np)
|
|
{
|
|
char loadparm[PARAM_SIZE + 1];
|
|
char *endptr;
|
|
int i = 0;
|
|
|
|
memset(loadparm, 0, sizeof(loadparm));
|
|
|
|
if (sclp_param(loadparm))
|
|
return;
|
|
/*
|
|
* extract site-ID as a sequence of digits after the first
|
|
* leading 'S'
|
|
*/
|
|
while (i < PARAM_SIZE) {
|
|
if (loadparm[i++] == 'S') {
|
|
if (loadparm[i] == 'S') {
|
|
/*
|
|
* the case of automatic
|
|
* site activation by SSID
|
|
*/
|
|
if ((IPL_SC).one) {
|
|
np->val = (IPL_SC).ssid;
|
|
snprintf(np->str, PARAM_SIZE - 1,
|
|
"%u", np->val);
|
|
return;
|
|
}
|
|
/* site undefined */
|
|
return;
|
|
}
|
|
np->val = ebcdic_strtoul(&loadparm[i], &endptr, 10);
|
|
memcpy(np->str, &loadparm[i], endptr - &loadparm[i]);
|
|
/*
|
|
* not more than (PARAM_SIZE - 1) bytes were copied!
|
|
*/
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static char *get_active_site(struct nr_pair *np)
|
|
{
|
|
memset(np->str, 0, sizeof(np->str));
|
|
np->val = 0;
|
|
|
|
get_active_site_by_loadparm(np);
|
|
if (!np->str[0]) {
|
|
/* No site-ID was found in LOADPARM */
|
|
return NULL;
|
|
}
|
|
ebcdic_to_ascii((unsigned char *)np->str,
|
|
(unsigned char *)np->str,
|
|
strlen(np->str));
|
|
return np->str;
|
|
}
|
|
|
|
/**
|
|
* LEN: length of the command line to be processed
|
|
*/
|
|
static void handle_environment(unsigned int len, unsigned int max_len)
|
|
{
|
|
struct env_hash_entry *buckets[STR_HASH_SIZE];
|
|
struct env_hash_entry *items;
|
|
unsigned int nr_items = 0;
|
|
struct nr_pair np;
|
|
|
|
if (_stage3_parms.envblk_addr == 0 ||
|
|
_stage3_parms.envblk_addr == UNSPECIFIED_ADDRESS)
|
|
return;
|
|
|
|
memset(buckets, 0, sizeof(buckets));
|
|
items = (struct env_hash_entry *)get_zeroed_page();
|
|
/*
|
|
* scan in-memory environment block and populate hash table
|
|
*/
|
|
parse_envblk(items, buckets, &nr_items);
|
|
/*
|
|
* find environment variables in the command line and
|
|
* replace them with their values as found in the hash table
|
|
*/
|
|
process_parm_line(buckets, len, max_len, get_active_site(&np));
|
|
free_page((unsigned long)items);
|
|
}
|
|
|
|
static void verify_secure_boot(void)
|
|
{
|
|
/*
|
|
* IPL process is secure we have to use default IPL values and
|
|
* check if the psw jump address is within at the start of a
|
|
* verified component. If it is not IPL is aborted.
|
|
*/
|
|
if (_stage3_parms.image_addr != IMAGE_LOAD_ADDRESS ||
|
|
_stage3_parms.load_psw != DEFAULT_PSW_LOAD)
|
|
panic(ESECUREBOOT, "%s", msg_sipl_inval);
|
|
|
|
if (!is_verified_address(_stage3_parms.load_psw & PSW32_ADDR_MASK))
|
|
panic(ESECUREBOOT, "%s", msg_sipl_unverified);
|
|
}
|
|
|
|
static void setup_cmdline(void)
|
|
{
|
|
char *cextra = (char *)COMMAND_LINE_EXTRA;
|
|
char *cmdline = (char *)COMMAND_LINE;
|
|
unsigned int cmdline_len = 0;
|
|
unsigned int max_cmdline_len = *(unsigned long *)MAX_COMMAND_LINE_SIZE;
|
|
|
|
if (!max_cmdline_len)
|
|
max_cmdline_len = LEGACY_COMMAND_LINE_SIZE;
|
|
|
|
/* if valid command line is given, copy it into new kernel space */
|
|
if (_stage3_parms.parm_addr != UNSPECIFIED_ADDRESS) {
|
|
strlcpy(cmdline, (void *)(unsigned long *)_stage3_parms.parm_addr,
|
|
max_cmdline_len);
|
|
}
|
|
/* determine length of original parm line */
|
|
cmdline_len = MIN((unsigned int)strlen(cmdline), max_cmdline_len - 1);
|
|
|
|
/* convert extra parameter to ascii */
|
|
if (!_stage3_parms.extra_parm || !*cextra)
|
|
goto noextra;
|
|
|
|
/* Handle extra kernel parameters specified in DASD boot menu. */
|
|
ebcdic_to_ascii((unsigned char *)cextra, (unsigned char *)cextra, COMMAND_LINE_EXTRA_SIZE);
|
|
|
|
while (isspace(*cextra))
|
|
cextra++;
|
|
|
|
/*
|
|
* if extra parm string starts with '=' replace original string,
|
|
* else append
|
|
*/
|
|
if (*cextra == 0x3d) {
|
|
strlcpy(cmdline, cextra+1, max_cmdline_len);
|
|
} else if (*cextra && cmdline_len + 1 <= max_cmdline_len - 1) {
|
|
/* add blank */
|
|
cmdline[cmdline_len++] = 0x20;
|
|
strlcpy(cmdline + cmdline_len, cextra, max_cmdline_len - cmdline_len);
|
|
}
|
|
|
|
noextra:
|
|
handle_environment(strlen(cmdline), max_cmdline_len);
|
|
}
|
|
|
|
void start(void)
|
|
{
|
|
unsigned int subchannel_id;
|
|
|
|
if (secure_boot_enabled())
|
|
verify_secure_boot();
|
|
|
|
/*
|
|
* cut the kernel header
|
|
*/
|
|
memmove((void *)_stage3_parms.image_addr,
|
|
(void *)_stage3_parms.image_addr + IMAGE_LOAD_ADDRESS,
|
|
_stage3_parms.image_len - IMAGE_LOAD_ADDRESS);
|
|
|
|
/* store subchannel ID into low core and into new kernel space */
|
|
subchannel_id = S390_lowcore.subchannel_id;
|
|
*(unsigned int *)__LC_IPLDEV = subchannel_id;
|
|
*(unsigned long long *)IPL_DEVICE = subchannel_id;
|
|
|
|
setup_cmdline();
|
|
|
|
/* copy initrd start address and size intop new kernle space */
|
|
*(unsigned long long *)INITRD_START = _stage3_parms.initrd_addr;
|
|
*(unsigned long long *)INITRD_SIZE = _stage3_parms.initrd_len;
|
|
|
|
/* store address of new kernel to 0 to be able to start it */
|
|
*(unsigned long long *)0 = _stage3_parms.load_psw;
|
|
|
|
kdump_stage3();
|
|
|
|
/* start new kernel */
|
|
start_kernel();
|
|
}
|
|
|
|
void panic_notify(unsigned long UNUSED(rc))
|
|
{
|
|
}
|