9bfc04f9ef
The POP states that for a list directed IPL the IPLB is stored into memory by the machine loader and its address is stored at offset 0x14 of the lowcore. ZIPL currently uses the address in offset 0x14 to access the IPLB and acquire flags about secure boot. If the IPLB address points into memory which has an unsupported mix of flags set, ZIPL will panic instead of booting the OS. As the lowcore can have quite a high entropy for a guest that did drop out of protected mode (i.e. rebooted) we encountered the ZIPL panic quite often. Signed-off-by: Janosch Frank <frankja@linux.ibm.com> Tested-by: Marc Hartmayer <mhartmay@linux.ibm.com> Message-Id: <20200304114231.23493-19-frankja@linux.ibm.com> Reviewed-by: Christian Borntraeger <borntraeger@de.ibm.com> Reviewed-by: David Hildenbrand <david@redhat.com> Signed-off-by: Christian Borntraeger <borntraeger@de.ibm.com>
562 lines
15 KiB
C
562 lines
15 KiB
C
/*
|
|
* S390 virtio-ccw network boot loading program
|
|
*
|
|
* Copyright 2017 Thomas Huth, Red Hat Inc.
|
|
*
|
|
* Based on the S390 virtio-ccw loading program (main.c)
|
|
* Copyright (c) 2013 Alexander Graf <agraf@suse.de>
|
|
*
|
|
* And based on the network loading code from SLOF (netload.c)
|
|
* Copyright (c) 2004, 2008 IBM Corporation
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version.
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include <tftp.h>
|
|
#include <ethernet.h>
|
|
#include <dhcp.h>
|
|
#include <dhcpv6.h>
|
|
#include <ipv4.h>
|
|
#include <ipv6.h>
|
|
#include <dns.h>
|
|
#include <time.h>
|
|
#include <pxelinux.h>
|
|
|
|
#include "s390-ccw.h"
|
|
#include "cio.h"
|
|
#include "virtio.h"
|
|
|
|
#define DEFAULT_BOOT_RETRIES 10
|
|
#define DEFAULT_TFTP_RETRIES 20
|
|
|
|
extern char _start[];
|
|
void write_iplb_location(void) {}
|
|
|
|
#define KERNEL_ADDR ((void *)0L)
|
|
#define KERNEL_MAX_SIZE ((long)_start)
|
|
#define ARCH_COMMAND_LINE_SIZE 896 /* Taken from Linux kernel */
|
|
|
|
/* STSI 3.2.2 offset of first vmdb + offset of uuid inside vmdb */
|
|
#define STSI322_VMDB_UUID_OFFSET ((8 + 12) * 4)
|
|
|
|
char stack[PAGE_SIZE * 8] __attribute__((aligned(PAGE_SIZE)));
|
|
IplParameterBlock iplb __attribute__((aligned(PAGE_SIZE)));
|
|
static char cfgbuf[2048];
|
|
|
|
static SubChannelId net_schid = { .one = 1 };
|
|
static uint8_t mac[6];
|
|
static uint64_t dest_timer;
|
|
|
|
static uint64_t get_timer_ms(void)
|
|
{
|
|
uint64_t clk;
|
|
|
|
asm volatile(" stck %0 " : : "Q"(clk) : "memory");
|
|
|
|
/* Bit 51 is incremented each microsecond */
|
|
return (clk >> (63 - 51)) / 1000;
|
|
}
|
|
|
|
void set_timer(int val)
|
|
{
|
|
dest_timer = get_timer_ms() + val;
|
|
}
|
|
|
|
int get_timer(void)
|
|
{
|
|
return dest_timer - get_timer_ms();
|
|
}
|
|
|
|
int get_sec_ticks(void)
|
|
{
|
|
return 1000; /* number of ticks in 1 second */
|
|
}
|
|
|
|
/**
|
|
* Obtain IP and configuration info from DHCP server (either IPv4 or IPv6).
|
|
* @param fn_ip contains the following configuration information:
|
|
* client MAC, client IP, TFTP-server MAC, TFTP-server IP,
|
|
* boot file name
|
|
* @param retries Number of DHCP attempts
|
|
* @return 0 : IP and configuration info obtained;
|
|
* non-0 : error condition occurred.
|
|
*/
|
|
static int dhcp(struct filename_ip *fn_ip, int retries)
|
|
{
|
|
int i = retries + 1;
|
|
int rc = -1;
|
|
|
|
printf(" Requesting information via DHCP: ");
|
|
|
|
dhcpv4_generate_transaction_id();
|
|
dhcpv6_generate_transaction_id();
|
|
|
|
do {
|
|
printf("\b\b\b%03d", i - 1);
|
|
if (!--i) {
|
|
printf("\nGiving up after %d DHCP requests\n", retries);
|
|
return -1;
|
|
}
|
|
fn_ip->ip_version = 4;
|
|
rc = dhcpv4(NULL, fn_ip);
|
|
if (rc == -1) {
|
|
fn_ip->ip_version = 6;
|
|
set_ipv6_address(fn_ip->fd, 0);
|
|
rc = dhcpv6(NULL, fn_ip);
|
|
if (rc == 0) {
|
|
memcpy(&fn_ip->own_ip6, get_ipv6_address(), 16);
|
|
break;
|
|
}
|
|
}
|
|
if (rc != -1) { /* either success or non-dhcp failure */
|
|
break;
|
|
}
|
|
} while (1);
|
|
printf("\b\b\b\bdone\n");
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Seed the random number generator with our mac and current timestamp
|
|
*/
|
|
static void seed_rng(uint8_t mac[])
|
|
{
|
|
uint64_t seed;
|
|
|
|
asm volatile(" stck %0 " : : "Q"(seed) : "memory");
|
|
seed ^= (mac[2] << 24) | (mac[3] << 16) | (mac[4] << 8) | mac[5];
|
|
srand(seed);
|
|
}
|
|
|
|
static int tftp_load(filename_ip_t *fnip, void *buffer, int len)
|
|
{
|
|
tftp_err_t tftp_err;
|
|
int rc;
|
|
|
|
rc = tftp(fnip, buffer, len, DEFAULT_TFTP_RETRIES, &tftp_err);
|
|
|
|
if (rc < 0) {
|
|
/* Make sure that error messages are put into a new line */
|
|
printf("\n ");
|
|
}
|
|
|
|
if (rc > 1024) {
|
|
printf(" TFTP: Received %s (%d KBytes)\n", fnip->filename, rc / 1024);
|
|
} else if (rc > 0) {
|
|
printf(" TFTP: Received %s (%d Bytes)\n", fnip->filename, rc);
|
|
} else {
|
|
const char *errstr = NULL;
|
|
int ecode;
|
|
tftp_get_error_info(fnip, &tftp_err, rc, &errstr, &ecode);
|
|
printf("TFTP error: %s\n", errstr ? errstr : "unknown error");
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int net_init(filename_ip_t *fn_ip)
|
|
{
|
|
int rc;
|
|
|
|
memset(fn_ip, 0, sizeof(filename_ip_t));
|
|
|
|
rc = virtio_net_init(mac);
|
|
if (rc < 0) {
|
|
puts("Could not initialize network device");
|
|
return -101;
|
|
}
|
|
fn_ip->fd = rc;
|
|
|
|
printf(" Using MAC address: %02x:%02x:%02x:%02x:%02x:%02x\n",
|
|
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
|
|
|
set_mac_address(mac); /* init ethernet layer */
|
|
seed_rng(mac);
|
|
|
|
rc = dhcp(fn_ip, DEFAULT_BOOT_RETRIES);
|
|
if (rc >= 0) {
|
|
if (fn_ip->ip_version == 4) {
|
|
set_ipv4_address(fn_ip->own_ip);
|
|
}
|
|
} else {
|
|
puts("Could not get IP address");
|
|
return -101;
|
|
}
|
|
|
|
if (fn_ip->ip_version == 4) {
|
|
printf(" Using IPv4 address: %d.%d.%d.%d\n",
|
|
(fn_ip->own_ip >> 24) & 0xFF, (fn_ip->own_ip >> 16) & 0xFF,
|
|
(fn_ip->own_ip >> 8) & 0xFF, fn_ip->own_ip & 0xFF);
|
|
} else if (fn_ip->ip_version == 6) {
|
|
char ip6_str[40];
|
|
ipv6_to_str(fn_ip->own_ip6.addr, ip6_str);
|
|
printf(" Using IPv6 address: %s\n", ip6_str);
|
|
}
|
|
|
|
if (rc == -2) {
|
|
printf("ARP request to TFTP server (%d.%d.%d.%d) failed\n",
|
|
(fn_ip->server_ip >> 24) & 0xFF, (fn_ip->server_ip >> 16) & 0xFF,
|
|
(fn_ip->server_ip >> 8) & 0xFF, fn_ip->server_ip & 0xFF);
|
|
return -102;
|
|
}
|
|
if (rc == -4 || rc == -3) {
|
|
puts("Can't obtain TFTP server IP address");
|
|
return -107;
|
|
}
|
|
|
|
printf(" Using TFTP server: ");
|
|
if (fn_ip->ip_version == 4) {
|
|
printf("%d.%d.%d.%d\n",
|
|
(fn_ip->server_ip >> 24) & 0xFF, (fn_ip->server_ip >> 16) & 0xFF,
|
|
(fn_ip->server_ip >> 8) & 0xFF, fn_ip->server_ip & 0xFF);
|
|
} else if (fn_ip->ip_version == 6) {
|
|
char ip6_str[40];
|
|
ipv6_to_str(fn_ip->server_ip6.addr, ip6_str);
|
|
printf("%s\n", ip6_str);
|
|
}
|
|
|
|
if (strlen(fn_ip->filename) > 0) {
|
|
printf(" Bootfile name: '%s'\n", fn_ip->filename);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void net_release(filename_ip_t *fn_ip)
|
|
{
|
|
if (fn_ip->ip_version == 4) {
|
|
dhcp_send_release(fn_ip->fd);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieve the Universally Unique Identifier of the VM.
|
|
* @return UUID string, or NULL in case of errors
|
|
*/
|
|
static const char *get_uuid(void)
|
|
{
|
|
register int r0 asm("0");
|
|
register int r1 asm("1");
|
|
uint8_t *mem, *buf, uuid[16];
|
|
int i, cc, chk = 0;
|
|
static char uuid_str[37];
|
|
|
|
mem = malloc(2 * PAGE_SIZE);
|
|
if (!mem) {
|
|
puts("Out of memory ... can not get UUID.");
|
|
return NULL;
|
|
}
|
|
buf = (uint8_t *)(((uint64_t)mem + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1));
|
|
memset(buf, 0, PAGE_SIZE);
|
|
|
|
/* Get SYSIB 3.2.2 */
|
|
r0 = (3 << 28) | 2;
|
|
r1 = 2;
|
|
asm volatile(" stsi 0(%[addr])\n"
|
|
" ipm %[cc]\n"
|
|
" srl %[cc],28\n"
|
|
: [cc] "=d" (cc)
|
|
: "d" (r0), "d" (r1), [addr] "a" (buf)
|
|
: "cc", "memory");
|
|
if (cc) {
|
|
free(mem);
|
|
return NULL;
|
|
}
|
|
|
|
for (i = 0; i < 16; i++) {
|
|
uuid[i] = buf[STSI322_VMDB_UUID_OFFSET + i];
|
|
chk |= uuid[i];
|
|
}
|
|
free(mem);
|
|
if (!chk) {
|
|
return NULL;
|
|
}
|
|
|
|
sprintf(uuid_str, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-"
|
|
"%02x%02x%02x%02x%02x%02x", uuid[0], uuid[1], uuid[2], uuid[3],
|
|
uuid[4], uuid[5], uuid[6], uuid[7], uuid[8], uuid[9], uuid[10],
|
|
uuid[11], uuid[12], uuid[13], uuid[14], uuid[15]);
|
|
|
|
return uuid_str;
|
|
}
|
|
|
|
/**
|
|
* Load a kernel with initrd (i.e. with the information that we've got from
|
|
* a pxelinux.cfg config file)
|
|
*/
|
|
static int load_kernel_with_initrd(filename_ip_t *fn_ip,
|
|
struct pl_cfg_entry *entry)
|
|
{
|
|
int rc;
|
|
|
|
printf("Loading pxelinux.cfg entry '%s'\n", entry->label);
|
|
|
|
if (!entry->kernel) {
|
|
printf("Kernel entry is missing!\n");
|
|
return -1;
|
|
}
|
|
|
|
strncpy(fn_ip->filename, entry->kernel, sizeof(fn_ip->filename));
|
|
rc = tftp_load(fn_ip, KERNEL_ADDR, KERNEL_MAX_SIZE);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
if (entry->initrd) {
|
|
uint64_t iaddr = (rc + 0xfff) & ~0xfffUL;
|
|
|
|
strncpy(fn_ip->filename, entry->initrd, sizeof(fn_ip->filename));
|
|
rc = tftp_load(fn_ip, (void *)iaddr, KERNEL_MAX_SIZE - iaddr);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
/* Patch location and size: */
|
|
*(uint64_t *)0x10408 = iaddr;
|
|
*(uint64_t *)0x10410 = rc;
|
|
rc += iaddr;
|
|
}
|
|
|
|
if (entry->append) {
|
|
strncpy((char *)0x10480, entry->append, ARCH_COMMAND_LINE_SIZE);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
#define MAX_PXELINUX_ENTRIES 16
|
|
|
|
static int net_try_pxelinux_cfg(filename_ip_t *fn_ip)
|
|
{
|
|
struct pl_cfg_entry entries[MAX_PXELINUX_ENTRIES];
|
|
int num_ent, def_ent = 0;
|
|
|
|
num_ent = pxelinux_load_parse_cfg(fn_ip, mac, get_uuid(),
|
|
DEFAULT_TFTP_RETRIES,
|
|
cfgbuf, sizeof(cfgbuf),
|
|
entries, MAX_PXELINUX_ENTRIES, &def_ent);
|
|
if (num_ent > 0) {
|
|
return load_kernel_with_initrd(fn_ip, &entries[def_ent]);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Load via information from a .INS file (which can be found on CD-ROMs
|
|
* for example)
|
|
*/
|
|
static int handle_ins_cfg(filename_ip_t *fn_ip, char *cfg, int cfgsize)
|
|
{
|
|
char *ptr;
|
|
int rc = -1, llen;
|
|
void *destaddr;
|
|
char *insbuf = cfg;
|
|
|
|
ptr = strchr(insbuf, '\n');
|
|
if (!ptr) {
|
|
puts("Does not seem to be a valid .INS file");
|
|
return -1;
|
|
}
|
|
|
|
*ptr = 0;
|
|
printf("\nParsing .INS file:\n %s\n", &insbuf[2]);
|
|
|
|
insbuf = ptr + 1;
|
|
while (*insbuf && insbuf < cfg + cfgsize) {
|
|
ptr = strchr(insbuf, '\n');
|
|
if (ptr) {
|
|
*ptr = 0;
|
|
}
|
|
llen = strlen(insbuf);
|
|
if (!llen) {
|
|
insbuf = ptr + 1;
|
|
continue;
|
|
}
|
|
ptr = strchr(insbuf, ' ');
|
|
if (!ptr) {
|
|
puts("Missing space separator in .INS file");
|
|
return -1;
|
|
}
|
|
*ptr = 0;
|
|
strncpy(fn_ip->filename, insbuf, sizeof(fn_ip->filename));
|
|
destaddr = (char *)atol(ptr + 1);
|
|
rc = tftp_load(fn_ip, destaddr, (long)_start - (long)destaddr);
|
|
if (rc <= 0) {
|
|
break;
|
|
}
|
|
insbuf += llen + 1;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int net_try_direct_tftp_load(filename_ip_t *fn_ip)
|
|
{
|
|
int rc;
|
|
void *loadaddr = (void *)0x2000; /* Load right after the low-core */
|
|
|
|
rc = tftp_load(fn_ip, loadaddr, KERNEL_MAX_SIZE - (long)loadaddr);
|
|
if (rc < 0) {
|
|
return rc;
|
|
} else if (rc < 8) {
|
|
printf("'%s' is too small (%i bytes only).\n", fn_ip->filename, rc);
|
|
return -1;
|
|
}
|
|
|
|
/* Check whether it is a configuration file instead of a kernel */
|
|
if (rc < sizeof(cfgbuf) - 1) {
|
|
memcpy(cfgbuf, loadaddr, rc);
|
|
cfgbuf[rc] = 0; /* Make sure that it is NUL-terminated */
|
|
if (!strncmp("* ", cfgbuf, 2)) {
|
|
return handle_ins_cfg(fn_ip, cfgbuf, rc);
|
|
}
|
|
/*
|
|
* pxelinux.cfg support via bootfile name is just here for developers'
|
|
* convenience (it eases testing with the built-in DHCP server of QEMU
|
|
* that does not support RFC 5071). The official way to configure a
|
|
* pxelinux.cfg file name is to use DHCP options 209 and 210 instead.
|
|
* So only use the pxelinux.cfg parser here for files that start with
|
|
* a magic comment string.
|
|
*/
|
|
if (!strncasecmp("# pxelinux", cfgbuf, 10)) {
|
|
struct pl_cfg_entry entries[MAX_PXELINUX_ENTRIES];
|
|
int num_ent, def_ent = 0;
|
|
|
|
num_ent = pxelinux_parse_cfg(cfgbuf, sizeof(cfgbuf), entries,
|
|
MAX_PXELINUX_ENTRIES, &def_ent);
|
|
if (num_ent <= 0) {
|
|
return -1;
|
|
}
|
|
return load_kernel_with_initrd(fn_ip, &entries[def_ent]);
|
|
}
|
|
}
|
|
|
|
/* Move kernel to right location */
|
|
memmove(KERNEL_ADDR, loadaddr, rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
void panic(const char *string)
|
|
{
|
|
sclp_print(string);
|
|
for (;;) {
|
|
disabled_wait();
|
|
}
|
|
}
|
|
|
|
void write_subsystem_identification(void)
|
|
{
|
|
SubChannelId *schid = (SubChannelId *) 184;
|
|
uint32_t *zeroes = (uint32_t *) 188;
|
|
|
|
*schid = net_schid;
|
|
*zeroes = 0;
|
|
}
|
|
|
|
static bool find_net_dev(Schib *schib, int dev_no)
|
|
{
|
|
int i, r;
|
|
|
|
for (i = 0; i < 0x10000; i++) {
|
|
net_schid.sch_no = i;
|
|
r = stsch_err(net_schid, schib);
|
|
if (r == 3 || r == -EIO) {
|
|
break;
|
|
}
|
|
if (!schib->pmcw.dnv) {
|
|
continue;
|
|
}
|
|
enable_subchannel(net_schid);
|
|
if (!virtio_is_supported(net_schid)) {
|
|
continue;
|
|
}
|
|
if (virtio_get_device_type() != VIRTIO_ID_NET) {
|
|
continue;
|
|
}
|
|
if (dev_no < 0 || schib->pmcw.dev == dev_no) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void virtio_setup(void)
|
|
{
|
|
Schib schib;
|
|
int ssid;
|
|
bool found = false;
|
|
uint16_t dev_no;
|
|
|
|
/*
|
|
* We unconditionally enable mss support. In every sane configuration,
|
|
* this will succeed; and even if it doesn't, stsch_err() can deal
|
|
* with the consequences.
|
|
*/
|
|
enable_mss_facility();
|
|
|
|
if (store_iplb(&iplb)) {
|
|
IPL_assert(iplb.pbt == S390_IPL_TYPE_CCW, "IPL_TYPE_CCW expected");
|
|
dev_no = iplb.ccw.devno;
|
|
debug_print_int("device no. ", dev_no);
|
|
net_schid.ssid = iplb.ccw.ssid & 0x3;
|
|
debug_print_int("ssid ", net_schid.ssid);
|
|
found = find_net_dev(&schib, dev_no);
|
|
} else {
|
|
for (ssid = 0; ssid < 0x3; ssid++) {
|
|
net_schid.ssid = ssid;
|
|
found = find_net_dev(&schib, -1);
|
|
if (found) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
IPL_assert(found, "No virtio net device found");
|
|
}
|
|
|
|
void main(void)
|
|
{
|
|
filename_ip_t fn_ip;
|
|
int rc, fnlen;
|
|
|
|
sclp_setup();
|
|
sclp_print("Network boot starting...\n");
|
|
|
|
virtio_setup();
|
|
|
|
rc = net_init(&fn_ip);
|
|
if (rc) {
|
|
panic("Network initialization failed. Halting.\n");
|
|
}
|
|
|
|
fnlen = strlen(fn_ip.filename);
|
|
if (fnlen > 0 && fn_ip.filename[fnlen - 1] != '/') {
|
|
rc = net_try_direct_tftp_load(&fn_ip);
|
|
}
|
|
if (rc <= 0) {
|
|
rc = net_try_pxelinux_cfg(&fn_ip);
|
|
}
|
|
|
|
net_release(&fn_ip);
|
|
|
|
if (rc > 0) {
|
|
sclp_print("Network loading done, starting kernel...\n");
|
|
jump_to_low_kernel();
|
|
}
|
|
|
|
panic("Failed to load OS from network\n");
|
|
}
|