qemu/pc-bios/s390-ccw/netmain.c

558 lines
14 KiB
C
Raw Normal View History

/*
* 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 "virtio.h"
#define DEFAULT_BOOT_RETRIES 10
#define DEFAULT_TFTP_RETRIES 20
extern char _start[];
#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) {
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();
}
}
pc-bios/s390-ccw/net: Use diag308 to reset machine before jumping to the OS The netboot firmware so far simply jumped directly into the OS kernel after the download has been completed. This, however, bears the risk that the virtio-net device still might be active in the background and incoming packets are still placed into the buffers - which could destroy memory of the now-running Linux kernel in case it did not take over the device fast enough. Also the SCLP console is not put into a well-defined state here. We should hand over the system in a clean state when jumping into the kernel, so let's use the same mechanism as it's done in the main s390-ccw firmware and reset the machine with diag308 into a clean state before jumping into the OS kernel code. To be able to share the code with the main s390-ccw firmware, the related functions are now extracted from bootmap.c into a new file called jump2ipl.c. Since we now also set the boot device schid at address 184 for the network boot device, this patch also slightly changes the way how we detect the entry points for non-ELF binary images: The code now looks for the "S390EP" magic first and then jumps to 0x10000 in case it has been found. This is necessary for booting from network devices, since the normal kernel code (where the PSW at ddress 0 points to) tries to do a block load from the boot device. This of course fails for a virtio-net device and causes the kernel to abort with a panic-PSW silently. Acked-by: Christian Borntraeger <borntraeger@de.ibm.com> Signed-off-by: Thomas Huth <thuth@redhat.com>
2018-04-20 12:30:42 +03:00
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;
}
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");
pc-bios/s390-ccw/net: Use diag308 to reset machine before jumping to the OS The netboot firmware so far simply jumped directly into the OS kernel after the download has been completed. This, however, bears the risk that the virtio-net device still might be active in the background and incoming packets are still placed into the buffers - which could destroy memory of the now-running Linux kernel in case it did not take over the device fast enough. Also the SCLP console is not put into a well-defined state here. We should hand over the system in a clean state when jumping into the kernel, so let's use the same mechanism as it's done in the main s390-ccw firmware and reset the machine with diag308 into a clean state before jumping into the OS kernel code. To be able to share the code with the main s390-ccw firmware, the related functions are now extracted from bootmap.c into a new file called jump2ipl.c. Since we now also set the boot device schid at address 184 for the network boot device, this patch also slightly changes the way how we detect the entry points for non-ELF binary images: The code now looks for the "S390EP" magic first and then jumps to 0x10000 in case it has been found. This is necessary for booting from network devices, since the normal kernel code (where the PSW at ddress 0 points to) tries to do a block load from the boot device. This of course fails for a virtio-net device and causes the kernel to abort with a panic-PSW silently. Acked-by: Christian Borntraeger <borntraeger@de.ibm.com> Signed-off-by: Thomas Huth <thuth@redhat.com>
2018-04-20 12:30:42 +03:00
jump_to_low_kernel();
}
panic("Failed to load OS from network\n");
}