diff mbox

[v2,4/5] s390-ccw: interactive boot menu for eckd dasd

Message ID 1513030760-26245-5-git-send-email-walling@linux.vnet.ibm.com (mailing list archive)
State New, archived
Headers show

Commit Message

Collin L. Walling Dec. 11, 2017, 10:19 p.m. UTC
When the boot menu options are present and the guest's
disk has been configured by the zipl tool, then the user
will be presented with an interactive boot menu with
labeled entries. An example of what the menu might look
like:

    zIPL v1.37.1-build-20170714 interactive boot menu.

     0. default (linux-4.13.0)

     1. linux-4.13.0
     2. performance
     3. kvm

    Please choose (default will boot in 10 seconds):

If the user's input is empty or 0, the default zipl entry will
be chosen. If the input is within the range presented by the
menu, then the selection will be booted. Any erroneous input
will cancel the timeout and prompt the user until correct
input is given.

Any value set for loadparm will override all boot menu options.
If loadparm=PROMPT, then the menu prompt will continuously wait
until correct user input is given.

The absence of any boot options on the command line will attempt
to use the zipl loader values.

Signed-off-by: Collin L. Walling <walling@linux.vnet.ibm.com>
---
 pc-bios/s390-ccw/Makefile   |   2 +-
 pc-bios/s390-ccw/bootmap.c  |  71 +++++++++++++-
 pc-bios/s390-ccw/bootmap.h  |   2 +
 pc-bios/s390-ccw/main.c     |   3 +
 pc-bios/s390-ccw/menu.c     | 223 ++++++++++++++++++++++++++++++++++++++++++++
 pc-bios/s390-ccw/menu.h     |  28 ++++++
 pc-bios/s390-ccw/s390-ccw.h |   2 +
 pc-bios/s390-ccw/sclp.c     |  20 ++++
 pc-bios/s390-ccw/virtio.c   |   2 +-
 9 files changed, 348 insertions(+), 5 deletions(-)
 create mode 100644 pc-bios/s390-ccw/menu.c
 create mode 100644 pc-bios/s390-ccw/menu.h

Comments

Farhan Ali Dec. 12, 2017, 4:30 p.m. UTC | #1
On 12/11/2017 05:19 PM, Collin L. Walling wrote:
> When the boot menu options are present and the guest's
> disk has been configured by the zipl tool, then the user
> will be presented with an interactive boot menu with
> labeled entries. An example of what the menu might look
> like:
> 
>      zIPL v1.37.1-build-20170714 interactive boot menu.
> 
>       0. default (linux-4.13.0)
> 
>       1. linux-4.13.0
>       2. performance
>       3. kvm
> 
>      Please choose (default will boot in 10 seconds):
> 
> If the user's input is empty or 0, the default zipl entry will
> be chosen. If the input is within the range presented by the
> menu, then the selection will be booted. Any erroneous input
> will cancel the timeout and prompt the user until correct
> input is given.
> 
> Any value set for loadparm will override all boot menu options.
> If loadparm=PROMPT, then the menu prompt will continuously wait
> until correct user input is given.
> 
> The absence of any boot options on the command line will attempt
> to use the zipl loader values.
> 
> Signed-off-by: Collin L. Walling <walling@linux.vnet.ibm.com>
> ---
>   pc-bios/s390-ccw/Makefile   |   2 +-
>   pc-bios/s390-ccw/bootmap.c  |  71 +++++++++++++-
>   pc-bios/s390-ccw/bootmap.h  |   2 +
>   pc-bios/s390-ccw/main.c     |   3 +
>   pc-bios/s390-ccw/menu.c     | 223 ++++++++++++++++++++++++++++++++++++++++++++
>   pc-bios/s390-ccw/menu.h     |  28 ++++++
>   pc-bios/s390-ccw/s390-ccw.h |   2 +
>   pc-bios/s390-ccw/sclp.c     |  20 ++++
>   pc-bios/s390-ccw/virtio.c   |   2 +-
>   9 files changed, 348 insertions(+), 5 deletions(-)
>   create mode 100644 pc-bios/s390-ccw/menu.c
>   create mode 100644 pc-bios/s390-ccw/menu.h
> 
> diff --git a/pc-bios/s390-ccw/Makefile b/pc-bios/s390-ccw/Makefile
> index 9f7904f..1712c2d 100644
> --- a/pc-bios/s390-ccw/Makefile
> +++ b/pc-bios/s390-ccw/Makefile
> @@ -9,7 +9,7 @@ $(call set-vpath, $(SRC_PATH)/pc-bios/s390-ccw)
> 
>   .PHONY : all clean build-all
> 
> -OBJECTS = start.o main.o bootmap.o sclp.o virtio.o virtio-scsi.o virtio-blkdev.o libc.o
> +OBJECTS = start.o main.o bootmap.o sclp.o virtio.o virtio-scsi.o virtio-blkdev.o libc.o menu.o
>   QEMU_CFLAGS := $(filter -W%, $(QEMU_CFLAGS))
>   QEMU_CFLAGS += -ffreestanding -fno-delete-null-pointer-checks -msoft-float
>   QEMU_CFLAGS += -march=z900 -fPIE -fno-strict-aliasing
> diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
> index 5546b79..c817cf8 100644
> --- a/pc-bios/s390-ccw/bootmap.c
> +++ b/pc-bios/s390-ccw/bootmap.c
> @@ -13,6 +13,7 @@
>   #include "bootmap.h"
>   #include "virtio.h"
>   #include "bswap.h"
> +#include "menu.h"
> 
>   #ifdef DEBUG
>   /* #define DEBUG_FALLBACK */
> @@ -83,6 +84,7 @@ static void jump_to_IPL_code(uint64_t address)
> 
>   static unsigned char _bprs[8*1024]; /* guessed "max" ECKD sector size */
>   static const int max_bprs_entries = sizeof(_bprs) / sizeof(ExtEckdBlockPtr);
> +static uint8_t stage2[STAGE2_MAX_SIZE] __attribute__((__aligned__(PAGE_SIZE)));
> 
>   static inline void verify_boot_info(BootInfo *bip)
>   {
> @@ -182,7 +184,57 @@ static block_number_t load_eckd_segments(block_number_t blk, uint64_t *address)
>       return block_nr;
>   }
> 
> -static void run_eckd_boot_script(block_number_t mbr_block_nr)
> +static void read_stage2(block_number_t s1b_block_nr)
> +{
> +    block_number_t s2_block_nr;
> +    EckdStage1b *s1b = (void *)sec;
> +    int i;
> +
> +    /* Get Stage1b data */
> +    memset(sec, FREE_SPACE_FILLER, sizeof(sec));
> +    read_block(s1b_block_nr, s1b, "Cannot read stage1b boot loader.");
> +
> +    /* Get Stage2 data */
> +    memset(stage2, FREE_SPACE_FILLER, sizeof(stage2));
> +
> +    for (i = 0; i < STAGE2_MAX_SIZE / MAX_SECTOR_SIZE; i++) {
> +        s2_block_nr = eckd_block_num((void *)&(s1b->seek[i].cyl));
> +
> +        if (!s2_block_nr) {
> +            break;
> +        }
> +
> +        read_block(s2_block_nr, (stage2 + MAX_SECTOR_SIZE * i),
> +                   "Error reading Stage2 data");
> +    }
> +}
> +
> +static bool find_zipl_boot_menu_data(block_number_t s1b_block_nr,
> +                                     ZiplParms *zipl_parms)
> +{
> +    int offset;
> +    void *s2_offset;
> +
> +    read_stage2(s1b_block_nr);
> +
> +    /* Menu banner starts with "zIPL" */
> +    for (offset = 0; offset < STAGE2_MAX_SIZE - 4; offset++) {
> +        s2_offset = stage2 + offset;
> +
> +        if (magic_match(s2_offset, ZIPL_MAGIC_EBCDIC)) {
> +            zipl_parms->flag = *(uint16_t *)(s2_offset - 140);
> +            zipl_parms->timeout = *(uint16_t *)(s2_offset - 138);
> +            zipl_parms->menu_start = offset;
> +            return true;
> +        }
> +    }
> +
> +    sclp_print("No zipl boot menu data found. Booting default entry.");
> +    return false;
> +}
> +
> +static void run_eckd_boot_script(block_number_t mbr_block_nr,
> +                                 block_number_t s1b_block_nr)
>   {
>       int i;
>       unsigned int loadparm = get_loadparm_index();
> @@ -190,6 +242,12 @@ static void run_eckd_boot_script(block_number_t mbr_block_nr)
>       uint64_t address;
>       ScsiMbr *bte = (void *)sec; /* Eckd bootmap table entry */
>       BootMapScript *bms = (void *)sec;
> +    ZiplParms zipl_parms;
> +
> +    if (menu_check_flags(BOOT_MENU_FLAG_BOOT_OPTS | BOOT_MENU_FLAG_ZIPL_OPTS)
> +        && find_zipl_boot_menu_data(s1b_block_nr, &zipl_parms)) {
> +        loadparm = menu_get_zipl_boot_index(stage2, zipl_parms);
> +    }
> 
>       debug_print_int("loadparm", loadparm);
>       IPL_assert(loadparm < 31, "loadparm value greater than"
> @@ -224,6 +282,7 @@ static void ipl_eckd_cdl(void)
>       EckdCdlIpl2 *ipl2 = (void *)sec;
>       IplVolumeLabel *vlbl = (void *)sec;
>       block_number_t mbr_block_nr;
> +    block_number_t s1b_block_nr;
> 
>       /* we have just read the block #0 and recognized it as "IPL1" */
>       sclp_print("CDL\n");
> @@ -241,6 +300,9 @@ static void ipl_eckd_cdl(void)
>       /* save pointer to Boot Script */
>       mbr_block_nr = eckd_block_num((void *)&(mbr->blockptr));
> 
> +    /* save pointer to Stage1b Data */
> +    s1b_block_nr = eckd_block_num((void *)&(ipl2->stage1.seek[0].cyl));
> +
>       memset(sec, FREE_SPACE_FILLER, sizeof(sec));
>       read_block(2, vlbl, "Cannot read Volume Label at block 2");
>       IPL_assert(magic_match(vlbl->key, VOL1_MAGIC),
> @@ -249,7 +311,7 @@ static void ipl_eckd_cdl(void)
>                  "Invalid magic of volser block");
>       print_volser(vlbl->f.volser);
> 
> -    run_eckd_boot_script(mbr_block_nr);
> +    run_eckd_boot_script(mbr_block_nr, s1b_block_nr);
>       /* no return */
>   }
> 
> @@ -281,6 +343,7 @@ static void print_eckd_ldl_msg(ECKD_IPL_mode_t mode)
>   static void ipl_eckd_ldl(ECKD_IPL_mode_t mode)
>   {
>       block_number_t mbr_block_nr;
> +    block_number_t s1b_block_nr;
>       EckdLdlIpl1 *ipl1 = (void *)sec;
> 
>       if (mode != ECKD_LDL_UNLABELED) {
> @@ -302,7 +365,9 @@ static void ipl_eckd_ldl(ECKD_IPL_mode_t mode)
>       mbr_block_nr =
>           eckd_block_num((void *)&(ipl1->boot_info.bp.ipl.bm_ptr.eckd.bptr));
> 
> -    run_eckd_boot_script(mbr_block_nr);
> +    s1b_block_nr = eckd_block_num((void *)&(ipl1->stage1.seek[0].cyl));
> +
> +    run_eckd_boot_script(mbr_block_nr, s1b_block_nr);
>       /* no return */
>   }
> 
> diff --git a/pc-bios/s390-ccw/bootmap.h b/pc-bios/s390-ccw/bootmap.h
> index b700d08..8089402 100644
> --- a/pc-bios/s390-ccw/bootmap.h
> +++ b/pc-bios/s390-ccw/bootmap.h
> @@ -74,6 +74,7 @@ typedef struct ScsiMbr {
>   } __attribute__ ((packed)) ScsiMbr;
> 
>   #define ZIPL_MAGIC              "zIPL"
> +#define ZIPL_MAGIC_EBCDIC       "\xa9\xc9\xd7\xd3"
>   #define IPL1_MAGIC "\xc9\xd7\xd3\xf1" /* == "IPL1" in EBCDIC */
>   #define IPL2_MAGIC "\xc9\xd7\xd3\xf2" /* == "IPL2" in EBCDIC */
>   #define VOL1_MAGIC "\xe5\xd6\xd3\xf1" /* == "VOL1" in EBCDIC */
> @@ -229,6 +230,7 @@ typedef struct BootInfo {          /* @ 0x70, record #0    */
>   /*
>    * Structs for IPL
>    */
> +#define STAGE2_MAX_SIZE     0x3000

Is there a reason this is in hex?

>   #define STAGE2_BLK_CNT_MAX  24 /* Stage 1b can load up to 24 blocks */
> 
>   typedef struct EckdCdlIpl1 {
> diff --git a/pc-bios/s390-ccw/main.c b/pc-bios/s390-ccw/main.c
> index a8ef120..fb0ef92 100644
> --- a/pc-bios/s390-ccw/main.c
> +++ b/pc-bios/s390-ccw/main.c
> @@ -11,6 +11,7 @@
>   #include "libc.h"
>   #include "s390-ccw.h"
>   #include "virtio.h"
> +#include "menu.h"
> 
>   char stack[PAGE_SIZE * 8] __attribute__((__aligned__(PAGE_SIZE)));
>   static SubChannelId blk_schid = { .one = 1 };
> @@ -101,6 +102,8 @@ static void virtio_setup(void)
>               blk_schid.ssid = iplb.ccw.ssid & 0x3;
>               debug_print_int("ssid ", blk_schid.ssid);
>               found = find_dev(&schib, dev_no);
> +            menu_set_parms(iplb.ccw.boot_menu_flags,
> +                           iplb.ccw.boot_menu_timeout);
>               break;
>           case S390_IPL_TYPE_QEMU_SCSI:
>               vdev->scsi_device_selected = true;
> diff --git a/pc-bios/s390-ccw/menu.c b/pc-bios/s390-ccw/menu.c
> new file mode 100644
> index 0000000..d707afb
> --- /dev/null
> +++ b/pc-bios/s390-ccw/menu.c
> @@ -0,0 +1,223 @@
> +/*
> + * QEMU S390 Interactive Boot Menu
> + *
> + * Copyright 2017 IBM Corp.
> + * Author: Collin L. Walling <walling@linux.vnet.ibm.com>
> + *
> + * This work is licensed under the terms of the GNU GPL, version 2 or (at
> + * your option) any later version. See the COPYING file in the top-level
> + * directory.
> + */
> +
> +#include "libc.h"
> +#include "s390-ccw.h"
> +#include "menu.h"
> +
> +#define KEYCODE_NO_INP '\0'
> +#define KEYCODE_ESCAPE '\033'
> +#define KEYCODE_BACKSP '\177'
> +#define KEYCODE_ENTER  '\r'
> +
> +static uint8_t flags;
> +static uint64_t timeout;
> +
> +static inline void enable_clock_int(void)
> +{
> +    uint64_t tmp = 0;
> +
> +    asm volatile(
> +        "stctg      0,0,%0\n"
> +        "oi         6+%0, 0x8\n"
> +        "lctlg      0,0,%0"
> +        : : "Q" (tmp)
> +    );
> +}
> +
> +static inline void disable_clock_int(void)
> +{
> +    uint64_t tmp = 0;
> +
> +    asm volatile(
> +        "stctg      0,0,%0\n"
> +        "ni         6+%0, 0xf7\n"
> +        "lctlg      0,0,%0"
> +        : : "Q" (tmp)
> +    );
> +}
> +
> +static inline void set_clock_comparator(uint64_t time)
> +{
> +    asm volatile("sckc %0" : : "Q" (time));
> +}
> +
> +static inline bool check_clock_int(void)
> +{
> +    uint16_t code = *(uint16_t *)0x86;
> +
> +    consume_sclp_int();
> +
> +    return code == 0x1004;
> +}
> +
> +static int read_prompt(char *buf, size_t len)
> +{
> +    char inp[2];
> +    uint8_t idx = 0;
> +    uint64_t time;
> +
> +    if (timeout) {
> +        time = get_clock() + (timeout << 32);
> +        set_clock_comparator(time);
> +        enable_clock_int();
> +    }
> +
> +    inp[1] = '\0';
> +
> +    while (!check_clock_int()) {
> +
> +        /* Process only one character at a time */
> +        sclp_read(inp, 1);
> +
> +        switch (inp[0]) {
> +        case KEYCODE_NO_INP:
> +        case KEYCODE_ESCAPE:
> +            continue;
> +        case KEYCODE_BACKSP:
> +            if (idx > 0) {
> +                /* Remove last character */
> +                buf[idx - 1] = ' ';
> +                sclp_print("\r");
> +                sclp_print(buf);
> +
> +                idx--;
> +
> +                /* Reset cursor */
> +                buf[idx] = 0;
> +                sclp_print("\r");
> +                sclp_print(buf);
> +            }
> +            continue;
> +        case KEYCODE_ENTER:
> +            disable_clock_int();
> +            return idx;
> +        }
> +
> +        /* Echo input and add to buffer */
> +        if (idx < len) {
> +            buf[idx] = inp[0];
> +            sclp_print(inp);
> +            idx++;
> +        }
> +    }
> +
> +    disable_clock_int();
> +    *buf = NULL;
> +
> +    return 0;
> +}
> +
> +static int get_index(void)
> +{
> +    char buf[10];
> +    int len;
> +    int i;
> +
> +    memset(buf, 0, sizeof(buf));
> +
> +    len = read_prompt(buf, sizeof(buf));
> +
> +    if (len == 0) {
> +        return 0;
> +    }
> +
> +    for (i = 0; i < len; i++) {
> +        if (!isdigit(buf[i])) {
> +            return -1;
> +        }
> +    }
> +

doesn't the atoi function already do the isdigit check?
Is there a reason we are doing it here again?

> +    return atoi(buf);
> +}
> +
> +static int get_boot_index(int entries)
> +{
> +    char tmp[6];
> +    int boot_index;
> +
> +    /* Prompt User */
> +    if (timeout > 0) {
> +        sclp_print("Please choose (default will boot in ");
> +        sclp_print(itostr(timeout, tmp, sizeof(tmp)));
> +        sclp_print(" seconds):\n");
> +    } else {
> +        sclp_print("Please choose:\n");
> +    }
> +
> +    /* Get Menu Choice */
> +    boot_index = get_index();
> +
> +    timeout = 0;
> +
> +    while (boot_index < 0 || boot_index >= entries) {
> +        sclp_print("\nError: undefined configuration"
> +                   "\nPlease choose:\n");
> +        boot_index = get_index();
> +    }
> +
> +    sclp_print("\nBooting entry #");
> +    sclp_print(itostr(boot_index, tmp, sizeof(tmp)));
> +
> +    return boot_index;
> +}
> +
> +static void zipl_println(const char *data, size_t len)
> +{
> +    char buf[len + 1];
> +
> +    ebcdic_to_ascii(data, buf, len);
> +    buf[len] = '\n';
> +    buf[len + 1] = '\0';
> +
> +    sclp_print(buf);
> +}
> +
> +int menu_get_zipl_boot_index(const void *stage2, ZiplParms zipl_parms)
> +{
> +    const char *data = stage2 + zipl_parms.menu_start;
> +    size_t len;
> +    int ct;
> +
> +    if (flags & BOOT_MENU_FLAG_ZIPL_OPTS) {
> +        if (zipl_parms.flag) {
should we not check for zipl_parms.timeout? or do we know that if flags 
is set than timeout will always be set?


> +            timeout = zipl_parms.timeout;
> +        } else {
> +            return 0; /* Boot default */
> +        }
> +    }
> +
> +    /* Print and count all menu items, including the banner */
> +    for (ct = 0; *data; ct++) {
> +        len = strlen(data);
> +        zipl_println(data, len);
> +        data += len + 1;
> +
> +        if (ct < 2) {
> +            sclp_print("\n");
> +        }
> +    }
> +
> +    sclp_print("\n");
> +
> +    return get_boot_index(ct - 1);
> +}
> +
> +void menu_set_parms(uint8_t boot_menu_flag, uint16_t boot_menu_timeout)
> +{
> +    flags = boot_menu_flag;
> +    timeout = boot_menu_timeout;
> +}
> +
> +int menu_check_flags(uint8_t check_flags)
> +{
> +    return flags & check_flags;
> +}
> diff --git a/pc-bios/s390-ccw/menu.h b/pc-bios/s390-ccw/menu.h
> new file mode 100644
> index 0000000..a8727fa
> --- /dev/null
> +++ b/pc-bios/s390-ccw/menu.h
> @@ -0,0 +1,28 @@
> +/*
> + * QEMU S390 Interactive Boot Menu
> + *
> + * Copyright 2017 IBM Corp.
> + * Author: Collin L. Walling <walling@linux.vnet.ibm.com>
> + *
> + * This work is licensed under the terms of the GNU GPL, version 2 or (at
> + * your option) any later version. See the COPYING file in the top-level
> + * directory.
> + */
> +
> +#ifndef MENU_H
> +#define MENU_H
> +
> +#define BOOT_MENU_FLAG_BOOT_OPTS 0x80
> +#define BOOT_MENU_FLAG_ZIPL_OPTS 0x40
> +
> +typedef struct ZiplParms {
> +    unsigned short flag;
> +    unsigned short timeout;
> +    unsigned long long menu_start;
> +} ZiplParms;
> +
> +void menu_set_parms(uint8_t boot_menu_flags, uint16_t boot_menu_timeout);
> +bool menu_check_flags(uint8_t check_flags);
> +int menu_get_zipl_boot_index(const void *stage2, ZiplParms zipl_parms);
> +
> +#endif /* MENU_H */
> diff --git a/pc-bios/s390-ccw/s390-ccw.h b/pc-bios/s390-ccw/s390-ccw.h
> index 25d4d21..df4bc88 100644
> --- a/pc-bios/s390-ccw/s390-ccw.h
> +++ b/pc-bios/s390-ccw/s390-ccw.h
> @@ -71,6 +71,7 @@ unsigned int get_loadparm_index(void);
>   void sclp_print(const char *string);
>   void sclp_setup(void);
>   void sclp_get_loadparm_ascii(char *loadparm);
> +void sclp_read(char *str, size_t len);
> 
>   /* virtio.c */
>   unsigned long virtio_load_direct(ulong rec_list1, ulong rec_list2,
> @@ -79,6 +80,7 @@ bool virtio_is_supported(SubChannelId schid);
>   void virtio_blk_setup_device(SubChannelId schid);
>   int virtio_read(ulong sector, void *load_addr);
>   int enable_mss_facility(void);
> +u64 get_clock(void);
>   ulong get_second(void);
> 
>   /* bootmap.c */
> diff --git a/pc-bios/s390-ccw/sclp.c b/pc-bios/s390-ccw/sclp.c
> index 486fce1..5e4a78b 100644
> --- a/pc-bios/s390-ccw/sclp.c
> +++ b/pc-bios/s390-ccw/sclp.c
> @@ -101,3 +101,23 @@ void sclp_get_loadparm_ascii(char *loadparm)
>           ebcdic_to_ascii((char *) sccb->loadparm, loadparm, 8);
>       }
>   }
> +
> +void sclp_read(char *str, size_t len)
> +{
> +    ReadEventData *sccb = (void *)_sccb;
> +    char *buf = (char *)(&sccb->ebh) + 7;
> +
> +    /* Len should not exceed the maximum size of the event buffer */
> +    if (len > SCCB_SIZE - 8) {
> +        len = SCCB_SIZE - 8;
> +    }
> +
> +    sccb->h.length = SCCB_SIZE;
> +    sccb->h.function_code = SCLP_UNCONDITIONAL_READ;
> +    sccb->ebh.length = sizeof(EventBufferHeader);
> +    sccb->ebh.type = SCLP_EVENT_ASCII_CONSOLE_DATA;
> +    sccb->ebh.flags = 0;
> +
> +    sclp_service_call(SCLP_CMD_READ_EVENT_DATA, sccb);
> +    memcpy(str, buf, len);
> +}
> diff --git a/pc-bios/s390-ccw/virtio.c b/pc-bios/s390-ccw/virtio.c
> index c890a03..817e7f5 100644
> --- a/pc-bios/s390-ccw/virtio.c
> +++ b/pc-bios/s390-ccw/virtio.c
> @@ -176,7 +176,7 @@ void vring_send_buf(VRing *vr, void *p, int len, int flags)
>       }
>   }
> 
> -static u64 get_clock(void)
> +u64 get_clock(void)
>   {
>       u64 r;
>
Collin L. Walling Dec. 12, 2017, 5:04 p.m. UTC | #2
On 12/12/2017 11:30 AM, Farhan Ali wrote:
>

>

> On 12/11/2017 05:19 PM, Collin L. Walling wrote:

>> When the boot menu options are present and the guest's

>> disk has been configured by the zipl tool, then the user

>> will be presented with an interactive boot menu with

>> labeled entries. An example of what the menu might look

>> like:

>>

>>      zIPL v1.37.1-build-20170714 interactive boot menu.

>>

>>       0. default (linux-4.13.0)

>>

>>       1. linux-4.13.0

>>       2. performance

>>       3. kvm

>>

>>      Please choose (default will boot in 10 seconds):

>>

>> If the user's input is empty or 0, the default zipl entry will

>> be chosen. If the input is within the range presented by the

>> menu, then the selection will be booted. Any erroneous input

>> will cancel the timeout and prompt the user until correct

>> input is given.

>>

>> Any value set for loadparm will override all boot menu options.

>> If loadparm=PROMPT, then the menu prompt will continuously wait

>> until correct user input is given.

>>

>> The absence of any boot options on the command line will attempt

>> to use the zipl loader values.

>>

>> Signed-off-by: Collin L. Walling <walling@linux.vnet.ibm.com>

>> ---

>>   pc-bios/s390-ccw/Makefile   |   2 +-

>>   pc-bios/s390-ccw/bootmap.c  |  71 +++++++++++++-

>>   pc-bios/s390-ccw/bootmap.h  |   2 +

>>   pc-bios/s390-ccw/main.c     |   3 +

>>   pc-bios/s390-ccw/menu.c     | 223 

>> ++++++++++++++++++++++++++++++++++++++++++++

>>   pc-bios/s390-ccw/menu.h     |  28 ++++++

>>   pc-bios/s390-ccw/s390-ccw.h |   2 +

>>   pc-bios/s390-ccw/sclp.c     |  20 ++++

>>   pc-bios/s390-ccw/virtio.c   |   2 +-

>>   9 files changed, 348 insertions(+), 5 deletions(-)

>>   create mode 100644 pc-bios/s390-ccw/menu.c

>>   create mode 100644 pc-bios/s390-ccw/menu.h

>>

>> diff --git a/pc-bios/s390-ccw/Makefile b/pc-bios/s390-ccw/Makefile

>> index 9f7904f..1712c2d 100644

>> --- a/pc-bios/s390-ccw/Makefile

>> +++ b/pc-bios/s390-ccw/Makefile

>> @@ -9,7 +9,7 @@ $(call set-vpath, $(SRC_PATH)/pc-bios/s390-ccw)

>>

>>   .PHONY : all clean build-all

>>

>> -OBJECTS = start.o main.o bootmap.o sclp.o virtio.o virtio-scsi.o 

>> virtio-blkdev.o libc.o

>> +OBJECTS = start.o main.o bootmap.o sclp.o virtio.o virtio-scsi.o 

>> virtio-blkdev.o libc.o menu.o

>>   QEMU_CFLAGS := $(filter -W%, $(QEMU_CFLAGS))

>>   QEMU_CFLAGS += -ffreestanding -fno-delete-null-pointer-checks 

>> -msoft-float

>>   QEMU_CFLAGS += -march=z900 -fPIE -fno-strict-aliasing

>> diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c

>> index 5546b79..c817cf8 100644

>> --- a/pc-bios/s390-ccw/bootmap.c

>> +++ b/pc-bios/s390-ccw/bootmap.c

>> @@ -13,6 +13,7 @@

>>   #include "bootmap.h"

>>   #include "virtio.h"

>>   #include "bswap.h"

>> +#include "menu.h"

>>

>>   #ifdef DEBUG

>>   /* #define DEBUG_FALLBACK */

>> @@ -83,6 +84,7 @@ static void jump_to_IPL_code(uint64_t address)

>>

>>   static unsigned char _bprs[8*1024]; /* guessed "max" ECKD sector 

>> size */

>>   static const int max_bprs_entries = sizeof(_bprs) / 

>> sizeof(ExtEckdBlockPtr);

>> +static uint8_t stage2[STAGE2_MAX_SIZE] 

>> __attribute__((__aligned__(PAGE_SIZE)));

>>

>>   static inline void verify_boot_info(BootInfo *bip)

>>   {

>> @@ -182,7 +184,57 @@ static block_number_t 

>> load_eckd_segments(block_number_t blk, uint64_t *address)

>>       return block_nr;

>>   }

>>

>> -static void run_eckd_boot_script(block_number_t mbr_block_nr)

>> +static void read_stage2(block_number_t s1b_block_nr)

>> +{

>> +    block_number_t s2_block_nr;

>> +    EckdStage1b *s1b = (void *)sec;

>> +    int i;

>> +

>> +    /* Get Stage1b data */

>> +    memset(sec, FREE_SPACE_FILLER, sizeof(sec));

>> +    read_block(s1b_block_nr, s1b, "Cannot read stage1b boot loader.");

>> +

>> +    /* Get Stage2 data */

>> +    memset(stage2, FREE_SPACE_FILLER, sizeof(stage2));

>> +

>> +    for (i = 0; i < STAGE2_MAX_SIZE / MAX_SECTOR_SIZE; i++) {

>> +        s2_block_nr = eckd_block_num((void *)&(s1b->seek[i].cyl));

>> +

>> +        if (!s2_block_nr) {

>> +            break;

>> +        }

>> +

>> +        read_block(s2_block_nr, (stage2 + MAX_SECTOR_SIZE * i),

>> +                   "Error reading Stage2 data");

>> +    }

>> +}

>> +

>> +static bool find_zipl_boot_menu_data(block_number_t s1b_block_nr,

>> +                                     ZiplParms *zipl_parms)

>> +{

>> +    int offset;

>> +    void *s2_offset;

>> +

>> +    read_stage2(s1b_block_nr);

>> +

>> +    /* Menu banner starts with "zIPL" */

>> +    for (offset = 0; offset < STAGE2_MAX_SIZE - 4; offset++) {

>> +        s2_offset = stage2 + offset;

>> +

>> +        if (magic_match(s2_offset, ZIPL_MAGIC_EBCDIC)) {

>> +            zipl_parms->flag = *(uint16_t *)(s2_offset - 140);

>> +            zipl_parms->timeout = *(uint16_t *)(s2_offset - 138);

>> +            zipl_parms->menu_start = offset;

>> +            return true;

>> +        }

>> +    }

>> +

>> +    sclp_print("No zipl boot menu data found. Booting default entry.");

>> +    return false;

>> +}

>> +

>> +static void run_eckd_boot_script(block_number_t mbr_block_nr,

>> +                                 block_number_t s1b_block_nr)

>>   {

>>       int i;

>>       unsigned int loadparm = get_loadparm_index();

>> @@ -190,6 +242,12 @@ static void run_eckd_boot_script(block_number_t 

>> mbr_block_nr)

>>       uint64_t address;

>>       ScsiMbr *bte = (void *)sec; /* Eckd bootmap table entry */

>>       BootMapScript *bms = (void *)sec;

>> +    ZiplParms zipl_parms;

>> +

>> +    if (menu_check_flags(BOOT_MENU_FLAG_BOOT_OPTS | 

>> BOOT_MENU_FLAG_ZIPL_OPTS)

>> +        && find_zipl_boot_menu_data(s1b_block_nr, &zipl_parms)) {

>> +        loadparm = menu_get_zipl_boot_index(stage2, zipl_parms);

>> +    }

>>

>>       debug_print_int("loadparm", loadparm);

>>       IPL_assert(loadparm < 31, "loadparm value greater than"

>> @@ -224,6 +282,7 @@ static void ipl_eckd_cdl(void)

>>       EckdCdlIpl2 *ipl2 = (void *)sec;

>>       IplVolumeLabel *vlbl = (void *)sec;

>>       block_number_t mbr_block_nr;

>> +    block_number_t s1b_block_nr;

>>

>>       /* we have just read the block #0 and recognized it as "IPL1" */

>>       sclp_print("CDL\n");

>> @@ -241,6 +300,9 @@ static void ipl_eckd_cdl(void)

>>       /* save pointer to Boot Script */

>>       mbr_block_nr = eckd_block_num((void *)&(mbr->blockptr));

>>

>> +    /* save pointer to Stage1b Data */

>> +    s1b_block_nr = eckd_block_num((void *)&(ipl2->stage1.seek[0].cyl));

>> +

>>       memset(sec, FREE_SPACE_FILLER, sizeof(sec));

>>       read_block(2, vlbl, "Cannot read Volume Label at block 2");

>>       IPL_assert(magic_match(vlbl->key, VOL1_MAGIC),

>> @@ -249,7 +311,7 @@ static void ipl_eckd_cdl(void)

>>                  "Invalid magic of volser block");

>>       print_volser(vlbl->f.volser);

>>

>> -    run_eckd_boot_script(mbr_block_nr);

>> +    run_eckd_boot_script(mbr_block_nr, s1b_block_nr);

>>       /* no return */

>>   }

>>

>> @@ -281,6 +343,7 @@ static void print_eckd_ldl_msg(ECKD_IPL_mode_t mode)

>>   static void ipl_eckd_ldl(ECKD_IPL_mode_t mode)

>>   {

>>       block_number_t mbr_block_nr;

>> +    block_number_t s1b_block_nr;

>>       EckdLdlIpl1 *ipl1 = (void *)sec;

>>

>>       if (mode != ECKD_LDL_UNLABELED) {

>> @@ -302,7 +365,9 @@ static void ipl_eckd_ldl(ECKD_IPL_mode_t mode)

>>       mbr_block_nr =

>>           eckd_block_num((void 

>> *)&(ipl1->boot_info.bp.ipl.bm_ptr.eckd.bptr));

>>

>> -    run_eckd_boot_script(mbr_block_nr);

>> +    s1b_block_nr = eckd_block_num((void *)&(ipl1->stage1.seek[0].cyl));

>> +

>> +    run_eckd_boot_script(mbr_block_nr, s1b_block_nr);

>>       /* no return */

>>   }

>>

>> diff --git a/pc-bios/s390-ccw/bootmap.h b/pc-bios/s390-ccw/bootmap.h

>> index b700d08..8089402 100644

>> --- a/pc-bios/s390-ccw/bootmap.h

>> +++ b/pc-bios/s390-ccw/bootmap.h

>> @@ -74,6 +74,7 @@ typedef struct ScsiMbr {

>>   } __attribute__ ((packed)) ScsiMbr;

>>

>>   #define ZIPL_MAGIC              "zIPL"

>> +#define ZIPL_MAGIC_EBCDIC       "\xa9\xc9\xd7\xd3"

>>   #define IPL1_MAGIC "\xc9\xd7\xd3\xf1" /* == "IPL1" in EBCDIC */

>>   #define IPL2_MAGIC "\xc9\xd7\xd3\xf2" /* == "IPL2" in EBCDIC */

>>   #define VOL1_MAGIC "\xe5\xd6\xd3\xf1" /* == "VOL1" in EBCDIC */

>> @@ -229,6 +230,7 @@ typedef struct BootInfo { /* @ 0x70, record #0    */

>>   /*

>>    * Structs for IPL

>>    */

>> +#define STAGE2_MAX_SIZE     0x3000

>

> Is there a reason this is in hex?



I suspect that if the stage2 size were to grow in the future, it will be 
by a
multiple of one sector size.  Having this value in hex is a little more 
sane
to update than if it were in decimal.


>

>>   #define STAGE2_BLK_CNT_MAX  24 /* Stage 1b can load up to 24 blocks */

>>

>>   typedef struct EckdCdlIpl1 {

>> diff --git a/pc-bios/s390-ccw/main.c b/pc-bios/s390-ccw/main.c

>> index a8ef120..fb0ef92 100644

>> --- a/pc-bios/s390-ccw/main.c

>> +++ b/pc-bios/s390-ccw/main.c

>> @@ -11,6 +11,7 @@

>>   #include "libc.h"

>>   #include "s390-ccw.h"

>>   #include "virtio.h"

>> +#include "menu.h"

>>

>>   char stack[PAGE_SIZE * 8] __attribute__((__aligned__(PAGE_SIZE)));

>>   static SubChannelId blk_schid = { .one = 1 };

>> @@ -101,6 +102,8 @@ static void virtio_setup(void)

>>               blk_schid.ssid = iplb.ccw.ssid & 0x3;

>>               debug_print_int("ssid ", blk_schid.ssid);

>>               found = find_dev(&schib, dev_no);

>> +            menu_set_parms(iplb.ccw.boot_menu_flags,

>> + iplb.ccw.boot_menu_timeout);

>>               break;

>>           case S390_IPL_TYPE_QEMU_SCSI:

>>               vdev->scsi_device_selected = true;

>> diff --git a/pc-bios/s390-ccw/menu.c b/pc-bios/s390-ccw/menu.c

>> new file mode 100644

>> index 0000000..d707afb

>> --- /dev/null

>> +++ b/pc-bios/s390-ccw/menu.c

>> @@ -0,0 +1,223 @@

>> +/*

>> + * QEMU S390 Interactive Boot Menu

>> + *

>> + * Copyright 2017 IBM Corp.

>> + * Author: Collin L. Walling <walling@linux.vnet.ibm.com>

>> + *

>> + * This work is licensed under the terms of the GNU GPL, version 2 

>> or (at

>> + * your option) any later version. See the COPYING file in the 

>> top-level

>> + * directory.

>> + */

>> +

>> +#include "libc.h"

>> +#include "s390-ccw.h"

>> +#include "menu.h"

>> +

>> +#define KEYCODE_NO_INP '\0'

>> +#define KEYCODE_ESCAPE '\033'

>> +#define KEYCODE_BACKSP '\177'

>> +#define KEYCODE_ENTER  '\r'

>> +

>> +static uint8_t flags;

>> +static uint64_t timeout;

>> +

>> +static inline void enable_clock_int(void)

>> +{

>> +    uint64_t tmp = 0;

>> +

>> +    asm volatile(

>> +        "stctg      0,0,%0\n"

>> +        "oi         6+%0, 0x8\n"

>> +        "lctlg      0,0,%0"

>> +        : : "Q" (tmp)

>> +    );

>> +}

>> +

>> +static inline void disable_clock_int(void)

>> +{

>> +    uint64_t tmp = 0;

>> +

>> +    asm volatile(

>> +        "stctg      0,0,%0\n"

>> +        "ni         6+%0, 0xf7\n"

>> +        "lctlg      0,0,%0"

>> +        : : "Q" (tmp)

>> +    );

>> +}

>> +

>> +static inline void set_clock_comparator(uint64_t time)

>> +{

>> +    asm volatile("sckc %0" : : "Q" (time));

>> +}

>> +

>> +static inline bool check_clock_int(void)

>> +{

>> +    uint16_t code = *(uint16_t *)0x86;

>> +

>> +    consume_sclp_int();

>> +

>> +    return code == 0x1004;

>> +}

>> +

>> +static int read_prompt(char *buf, size_t len)

>> +{

>> +    char inp[2];

>> +    uint8_t idx = 0;

>> +    uint64_t time;

>> +

>> +    if (timeout) {

>> +        time = get_clock() + (timeout << 32);

>> +        set_clock_comparator(time);

>> +        enable_clock_int();

>> +    }

>> +

>> +    inp[1] = '\0';

>> +

>> +    while (!check_clock_int()) {

>> +

>> +        /* Process only one character at a time */

>> +        sclp_read(inp, 1);

>> +

>> +        switch (inp[0]) {

>> +        case KEYCODE_NO_INP:

>> +        case KEYCODE_ESCAPE:

>> +            continue;

>> +        case KEYCODE_BACKSP:

>> +            if (idx > 0) {

>> +                /* Remove last character */

>> +                buf[idx - 1] = ' ';

>> +                sclp_print("\r");

>> +                sclp_print(buf);

>> +

>> +                idx--;

>> +

>> +                /* Reset cursor */

>> +                buf[idx] = 0;

>> +                sclp_print("\r");

>> +                sclp_print(buf);

>> +            }

>> +            continue;

>> +        case KEYCODE_ENTER:

>> +            disable_clock_int();

>> +            return idx;

>> +        }

>> +

>> +        /* Echo input and add to buffer */

>> +        if (idx < len) {

>> +            buf[idx] = inp[0];

>> +            sclp_print(inp);

>> +            idx++;

>> +        }

>> +    }

>> +

>> +    disable_clock_int();

>> +    *buf = NULL;

>> +

>> +    return 0;

>> +}

>> +

>> +static int get_index(void)

>> +{

>> +    char buf[10];

>> +    int len;

>> +    int i;

>> +

>> +    memset(buf, 0, sizeof(buf));

>> +

>> +    len = read_prompt(buf, sizeof(buf));

>> +

>> +    if (len == 0) {

>> +        return 0;

>> +    }

>> +

>> +    for (i = 0; i < len; i++) {

>> +        if (!isdigit(buf[i])) {

>> +            return -1;

>> +        }

>> +    }

>> +

>

> doesn't the atoi function already do the isdigit check?

> Is there a reason we are doing it here again?



c-like atoi ends the conversion when we encounter a non-numerical 
character.
So 1234abc5 would return 1234.  It does not do any kind of error checking.
The loop is to perform a more robust check to ensure we do not have any
erroneous characters at all.

Thinking about it -- perhaps implementing a strtol-like function would be
better (minus all the base-conversion stuff).  We can still use atoi for
get_loadparm_index().


>

>> +    return atoi(buf);

>> +}

>> +

>> +static int get_boot_index(int entries)

>> +{

>> +    char tmp[6];

>> +    int boot_index;

>> +

>> +    /* Prompt User */

>> +    if (timeout > 0) {

>> +        sclp_print("Please choose (default will boot in ");

>> +        sclp_print(itostr(timeout, tmp, sizeof(tmp)));

>> +        sclp_print(" seconds):\n");

>> +    } else {

>> +        sclp_print("Please choose:\n");

>> +    }

>> +

>> +    /* Get Menu Choice */

>> +    boot_index = get_index();

>> +

>> +    timeout = 0;

>> +

>> +    while (boot_index < 0 || boot_index >= entries) {

>> +        sclp_print("\nError: undefined configuration"

>> +                   "\nPlease choose:\n");

>> +        boot_index = get_index();

>> +    }

>> +

>> +    sclp_print("\nBooting entry #");

>> +    sclp_print(itostr(boot_index, tmp, sizeof(tmp)));

>> +

>> +    return boot_index;

>> +}

>> +

>> +static void zipl_println(const char *data, size_t len)

>> +{

>> +    char buf[len + 1];

>> +

>> +    ebcdic_to_ascii(data, buf, len);

>> +    buf[len] = '\n';

>> +    buf[len + 1] = '\0';

>> +

>> +    sclp_print(buf);

>> +}

>> +

>> +int menu_get_zipl_boot_index(const void *stage2, ZiplParms zipl_parms)

>> +{

>> +    const char *data = stage2 + zipl_parms.menu_start;

>> +    size_t len;

>> +    int ct;

>> +

>> +    if (flags & BOOT_MENU_FLAG_ZIPL_OPTS) {

>> +        if (zipl_parms.flag) {

> should we not check for zipl_parms.timeout? or do we know that if 

> flags is set than timeout will always be set?



If timeout was not specified in the zipl configuration file, then it 
will be 0.
(which means wait forever)


>

>

>> +            timeout = zipl_parms.timeout;

>> +        } else {

>> +            return 0; /* Boot default */

>> +        }

>> +    }

>> +

>> +    /* Print and count all menu items, including the banner */

>> +    for (ct = 0; *data; ct++) {

>> +        len = strlen(data);

>> +        zipl_println(data, len);

>> +        data += len + 1;

>> +

>> +        if (ct < 2) {

>> +            sclp_print("\n");

>> +        }

>> +    }

>> +

>> +    sclp_print("\n");

>> +

>> +    return get_boot_index(ct - 1);

>> +}

>> +

>> +void menu_set_parms(uint8_t boot_menu_flag, uint16_t boot_menu_timeout)

>> +{

>> +    flags = boot_menu_flag;

>> +    timeout = boot_menu_timeout;

>> +}

>> +

>> +int menu_check_flags(uint8_t check_flags)

>> +{

>> +    return flags & check_flags;

>> +}

>> diff --git a/pc-bios/s390-ccw/menu.h b/pc-bios/s390-ccw/menu.h

>> new file mode 100644

>> index 0000000..a8727fa

>> --- /dev/null

>> +++ b/pc-bios/s390-ccw/menu.h

>> @@ -0,0 +1,28 @@

>> +/*

>> + * QEMU S390 Interactive Boot Menu

>> + *

>> + * Copyright 2017 IBM Corp.

>> + * Author: Collin L. Walling <walling@linux.vnet.ibm.com>

>> + *

>> + * This work is licensed under the terms of the GNU GPL, version 2 

>> or (at

>> + * your option) any later version. See the COPYING file in the 

>> top-level

>> + * directory.

>> + */

>> +

>> +#ifndef MENU_H

>> +#define MENU_H

>> +

>> +#define BOOT_MENU_FLAG_BOOT_OPTS 0x80

>> +#define BOOT_MENU_FLAG_ZIPL_OPTS 0x40

>> +

>> +typedef struct ZiplParms {

>> +    unsigned short flag;

>> +    unsigned short timeout;

>> +    unsigned long long menu_start;

>> +} ZiplParms;

>> +

>> +void menu_set_parms(uint8_t boot_menu_flags, uint16_t 

>> boot_menu_timeout);

>> +bool menu_check_flags(uint8_t check_flags);

>> +int menu_get_zipl_boot_index(const void *stage2, ZiplParms zipl_parms);

>> +

>> +#endif /* MENU_H */

>> diff --git a/pc-bios/s390-ccw/s390-ccw.h b/pc-bios/s390-ccw/s390-ccw.h

>> index 25d4d21..df4bc88 100644

>> --- a/pc-bios/s390-ccw/s390-ccw.h

>> +++ b/pc-bios/s390-ccw/s390-ccw.h

>> @@ -71,6 +71,7 @@ unsigned int get_loadparm_index(void);

>>   void sclp_print(const char *string);

>>   void sclp_setup(void);

>>   void sclp_get_loadparm_ascii(char *loadparm);

>> +void sclp_read(char *str, size_t len);

>>

>>   /* virtio.c */

>>   unsigned long virtio_load_direct(ulong rec_list1, ulong rec_list2,

>> @@ -79,6 +80,7 @@ bool virtio_is_supported(SubChannelId schid);

>>   void virtio_blk_setup_device(SubChannelId schid);

>>   int virtio_read(ulong sector, void *load_addr);

>>   int enable_mss_facility(void);

>> +u64 get_clock(void);

>>   ulong get_second(void);

>>

>>   /* bootmap.c */

>> diff --git a/pc-bios/s390-ccw/sclp.c b/pc-bios/s390-ccw/sclp.c

>> index 486fce1..5e4a78b 100644

>> --- a/pc-bios/s390-ccw/sclp.c

>> +++ b/pc-bios/s390-ccw/sclp.c

>> @@ -101,3 +101,23 @@ void sclp_get_loadparm_ascii(char *loadparm)

>>           ebcdic_to_ascii((char *) sccb->loadparm, loadparm, 8);

>>       }

>>   }

>> +

>> +void sclp_read(char *str, size_t len)

>> +{

>> +    ReadEventData *sccb = (void *)_sccb;

>> +    char *buf = (char *)(&sccb->ebh) + 7;

>> +

>> +    /* Len should not exceed the maximum size of the event buffer */

>> +    if (len > SCCB_SIZE - 8) {

>> +        len = SCCB_SIZE - 8;

>> +    }

>> +

>> +    sccb->h.length = SCCB_SIZE;

>> +    sccb->h.function_code = SCLP_UNCONDITIONAL_READ;

>> +    sccb->ebh.length = sizeof(EventBufferHeader);

>> +    sccb->ebh.type = SCLP_EVENT_ASCII_CONSOLE_DATA;

>> +    sccb->ebh.flags = 0;

>> +

>> +    sclp_service_call(SCLP_CMD_READ_EVENT_DATA, sccb);

>> +    memcpy(str, buf, len);

>> +}

>> diff --git a/pc-bios/s390-ccw/virtio.c b/pc-bios/s390-ccw/virtio.c

>> index c890a03..817e7f5 100644

>> --- a/pc-bios/s390-ccw/virtio.c

>> +++ b/pc-bios/s390-ccw/virtio.c

>> @@ -176,7 +176,7 @@ void vring_send_buf(VRing *vr, void *p, int len, 

>> int flags)

>>       }

>>   }

>>

>> -static u64 get_clock(void)

>> +u64 get_clock(void)

>>   {

>>       u64 r;

>>

>

>



-- 
- Collin L Walling
Thomas Huth Dec. 18, 2017, 1:43 p.m. UTC | #3
On 11.12.2017 23:19, Collin L. Walling wrote:
> When the boot menu options are present and the guest's
> disk has been configured by the zipl tool, then the user
> will be presented with an interactive boot menu with
> labeled entries. An example of what the menu might look
> like:
> 
>     zIPL v1.37.1-build-20170714 interactive boot menu.
> 
>      0. default (linux-4.13.0)
> 
>      1. linux-4.13.0
>      2. performance
>      3. kvm
> 
>     Please choose (default will boot in 10 seconds):
> 
> If the user's input is empty or 0, the default zipl entry will
> be chosen. If the input is within the range presented by the
> menu, then the selection will be booted. Any erroneous input
> will cancel the timeout and prompt the user until correct
> input is given.
> 
> Any value set for loadparm will override all boot menu options.
> If loadparm=PROMPT, then the menu prompt will continuously wait
> until correct user input is given.
> 
> The absence of any boot options on the command line will attempt
> to use the zipl loader values.
> 
> Signed-off-by: Collin L. Walling <walling@linux.vnet.ibm.com>
> ---
[...]
> diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
> index 5546b79..c817cf8 100644
> --- a/pc-bios/s390-ccw/bootmap.c
> +++ b/pc-bios/s390-ccw/bootmap.c
> @@ -13,6 +13,7 @@
>  #include "bootmap.h"
>  #include "virtio.h"
>  #include "bswap.h"
> +#include "menu.h"
>  
>  #ifdef DEBUG
>  /* #define DEBUG_FALLBACK */
> @@ -83,6 +84,7 @@ static void jump_to_IPL_code(uint64_t address)
>  
>  static unsigned char _bprs[8*1024]; /* guessed "max" ECKD sector size */
>  static const int max_bprs_entries = sizeof(_bprs) / sizeof(ExtEckdBlockPtr);
> +static uint8_t stage2[STAGE2_MAX_SIZE] __attribute__((__aligned__(PAGE_SIZE)));
>  
>  static inline void verify_boot_info(BootInfo *bip)
>  {
> @@ -182,7 +184,57 @@ static block_number_t load_eckd_segments(block_number_t blk, uint64_t *address)
>      return block_nr;
>  }
>  
> -static void run_eckd_boot_script(block_number_t mbr_block_nr)
> +static void read_stage2(block_number_t s1b_block_nr)
> +{
> +    block_number_t s2_block_nr;
> +    EckdStage1b *s1b = (void *)sec;
> +    int i;
> +
> +    /* Get Stage1b data */
> +    memset(sec, FREE_SPACE_FILLER, sizeof(sec));
> +    read_block(s1b_block_nr, s1b, "Cannot read stage1b boot loader.");
> +
> +    /* Get Stage2 data */
> +    memset(stage2, FREE_SPACE_FILLER, sizeof(stage2));
> +
> +    for (i = 0; i < STAGE2_MAX_SIZE / MAX_SECTOR_SIZE; i++) {
> +        s2_block_nr = eckd_block_num((void *)&(s1b->seek[i].cyl));

You can omit the parentheses around s1b->seek[i].cyl.

> +        if (!s2_block_nr) {
> +            break;
> +        }
> +
> +        read_block(s2_block_nr, (stage2 + MAX_SECTOR_SIZE * i),

You can omit the parentheses around the second parameter.

> +                   "Error reading Stage2 data");
> +    }
> +}
[...]
> @@ -241,6 +300,9 @@ static void ipl_eckd_cdl(void)
>      /* save pointer to Boot Script */
>      mbr_block_nr = eckd_block_num((void *)&(mbr->blockptr));
>  
> +    /* save pointer to Stage1b Data */
> +    s1b_block_nr = eckd_block_num((void *)&(ipl2->stage1.seek[0].cyl));

You can omit the parentheses around ipl2->stage1.seek[0].cyl.

>      memset(sec, FREE_SPACE_FILLER, sizeof(sec));
>      read_block(2, vlbl, "Cannot read Volume Label at block 2");
>      IPL_assert(magic_match(vlbl->key, VOL1_MAGIC),
> @@ -249,7 +311,7 @@ static void ipl_eckd_cdl(void)
>                 "Invalid magic of volser block");
>      print_volser(vlbl->f.volser);
>  
> -    run_eckd_boot_script(mbr_block_nr);
> +    run_eckd_boot_script(mbr_block_nr, s1b_block_nr);
>      /* no return */
>  }
>  
> @@ -281,6 +343,7 @@ static void print_eckd_ldl_msg(ECKD_IPL_mode_t mode)
>  static void ipl_eckd_ldl(ECKD_IPL_mode_t mode)
>  {
>      block_number_t mbr_block_nr;
> +    block_number_t s1b_block_nr;
>      EckdLdlIpl1 *ipl1 = (void *)sec;
>  
>      if (mode != ECKD_LDL_UNLABELED) {
> @@ -302,7 +365,9 @@ static void ipl_eckd_ldl(ECKD_IPL_mode_t mode)
>      mbr_block_nr =
>          eckd_block_num((void *)&(ipl1->boot_info.bp.ipl.bm_ptr.eckd.bptr));
>  
> -    run_eckd_boot_script(mbr_block_nr);
> +    s1b_block_nr = eckd_block_num((void *)&(ipl1->stage1.seek[0].cyl));

dito.

> +    run_eckd_boot_script(mbr_block_nr, s1b_block_nr);
>      /* no return */
>  }
>  
> diff --git a/pc-bios/s390-ccw/bootmap.h b/pc-bios/s390-ccw/bootmap.h
> index b700d08..8089402 100644
> --- a/pc-bios/s390-ccw/bootmap.h
> +++ b/pc-bios/s390-ccw/bootmap.h
> @@ -74,6 +74,7 @@ typedef struct ScsiMbr {
>  } __attribute__ ((packed)) ScsiMbr;
>  
>  #define ZIPL_MAGIC              "zIPL"
> +#define ZIPL_MAGIC_EBCDIC       "\xa9\xc9\xd7\xd3"
>  #define IPL1_MAGIC "\xc9\xd7\xd3\xf1" /* == "IPL1" in EBCDIC */
>  #define IPL2_MAGIC "\xc9\xd7\xd3\xf2" /* == "IPL2" in EBCDIC */
>  #define VOL1_MAGIC "\xe5\xd6\xd3\xf1" /* == "VOL1" in EBCDIC */
> @@ -229,6 +230,7 @@ typedef struct BootInfo {          /* @ 0x70, record #0    */
>  /*
>   * Structs for IPL
>   */
> +#define STAGE2_MAX_SIZE     0x3000
>  #define STAGE2_BLK_CNT_MAX  24 /* Stage 1b can load up to 24 blocks */
>  
>  typedef struct EckdCdlIpl1 {
> diff --git a/pc-bios/s390-ccw/main.c b/pc-bios/s390-ccw/main.c
> index a8ef120..fb0ef92 100644
> --- a/pc-bios/s390-ccw/main.c
> +++ b/pc-bios/s390-ccw/main.c
> @@ -11,6 +11,7 @@
>  #include "libc.h"
>  #include "s390-ccw.h"
>  #include "virtio.h"
> +#include "menu.h"
>  
>  char stack[PAGE_SIZE * 8] __attribute__((__aligned__(PAGE_SIZE)));
>  static SubChannelId blk_schid = { .one = 1 };
> @@ -101,6 +102,8 @@ static void virtio_setup(void)
>              blk_schid.ssid = iplb.ccw.ssid & 0x3;
>              debug_print_int("ssid ", blk_schid.ssid);
>              found = find_dev(&schib, dev_no);
> +            menu_set_parms(iplb.ccw.boot_menu_flags,
> +                           iplb.ccw.boot_menu_timeout);
>              break;
>          case S390_IPL_TYPE_QEMU_SCSI:
>              vdev->scsi_device_selected = true;
> diff --git a/pc-bios/s390-ccw/menu.c b/pc-bios/s390-ccw/menu.c
> new file mode 100644
> index 0000000..d707afb
> --- /dev/null
> +++ b/pc-bios/s390-ccw/menu.c
> @@ -0,0 +1,223 @@
> +/*
> + * QEMU S390 Interactive Boot Menu
> + *
> + * Copyright 2017 IBM Corp.
> + * Author: Collin L. Walling <walling@linux.vnet.ibm.com>
> + *
> + * This work is licensed under the terms of the GNU GPL, version 2 or (at
> + * your option) any later version. See the COPYING file in the top-level
> + * directory.
> + */
> +
> +#include "libc.h"
> +#include "s390-ccw.h"
> +#include "menu.h"
> +
> +#define KEYCODE_NO_INP '\0'
> +#define KEYCODE_ESCAPE '\033'
> +#define KEYCODE_BACKSP '\177'
> +#define KEYCODE_ENTER  '\r'
> +
> +static uint8_t flags;
> +static uint64_t timeout;
> +
> +static inline void enable_clock_int(void)
> +{
> +    uint64_t tmp = 0;
> +
> +    asm volatile(
> +        "stctg      0,0,%0\n"
> +        "oi         6+%0, 0x8\n"
> +        "lctlg      0,0,%0"
> +        : : "Q" (tmp)

Did you check whether we need "memory" in the clobber list here?

> +    );
> +}
> +
> +static inline void disable_clock_int(void)
> +{
> +    uint64_t tmp = 0;
> +
> +    asm volatile(
> +        "stctg      0,0,%0\n"
> +        "ni         6+%0, 0xf7\n"
> +        "lctlg      0,0,%0"
> +        : : "Q" (tmp)
> +    );
> +}
> +
> +static inline void set_clock_comparator(uint64_t time)
> +{
> +    asm volatile("sckc %0" : : "Q" (time));
> +}
> +
> +static inline bool check_clock_int(void)
> +{
> +    uint16_t code = *(uint16_t *)0x86;
> +
> +    consume_sclp_int();

Don't you rather have to read the code from memory *after* calling
consume_sclp_int() ?

> +    return code == 0x1004;
> +}
> +
> +static int read_prompt(char *buf, size_t len)
> +{
> +    char inp[2];
> +    uint8_t idx = 0;
> +    uint64_t time;
> +
> +    if (timeout) {
> +        time = get_clock() + (timeout << 32);
> +        set_clock_comparator(time);
> +        enable_clock_int();
> +    }
> +
> +    inp[1] = '\0';

I think I'd rather declare "char inp[2] = {}", just to make sure that
inp[0] is also correctly pre-initialized - so that in case anything goes
wrong with sclp_read() below, we can be sure that there is not random
data in inp[0].

> +    while (!check_clock_int()) {
> +
> +        /* Process only one character at a time */
> +        sclp_read(inp, 1);
> +
> +        switch (inp[0]) {
> +        case KEYCODE_NO_INP:
> +        case KEYCODE_ESCAPE:
> +            continue;
> +        case KEYCODE_BACKSP:
> +            if (idx > 0) {
> +                /* Remove last character */
> +                buf[idx - 1] = ' ';
> +                sclp_print("\r");
> +                sclp_print(buf);
> +
> +                idx--;
> +
> +                /* Reset cursor */
> +                buf[idx] = 0;
> +                sclp_print("\r");
> +                sclp_print(buf);
> +            }
> +            continue;
> +        case KEYCODE_ENTER:
> +            disable_clock_int();
> +            return idx;
> +        }
> +
> +        /* Echo input and add to buffer */
> +        if (idx < len) {
> +            buf[idx] = inp[0];
> +            sclp_print(inp);
> +            idx++;
> +        }
> +    }
> +
> +    disable_clock_int();
> +    *buf = NULL;

Hmm, I'm used to see NULL only as a pointer, so "*buf = 0" would IMHO be
nicer here?

> +
> +    return 0;
> +}
> +
> +static int get_index(void)
> +{
> +    char buf[10];
> +    int len;
> +    int i;
> +
> +    memset(buf, 0, sizeof(buf));
> +
> +    len = read_prompt(buf, sizeof(buf));

I think you should rather use "sizeof(buf) - 1" here to make sure that
the string is always terminated with a 0?

> +    if (len == 0) {
> +        return 0;
> +    }
> +
> +    for (i = 0; i < len; i++) {
> +        if (!isdigit(buf[i])) {
> +            return -1;
> +        }
> +    }
> +
> +    return atoi(buf);
> +}

 Thomas
diff mbox

Patch

diff --git a/pc-bios/s390-ccw/Makefile b/pc-bios/s390-ccw/Makefile
index 9f7904f..1712c2d 100644
--- a/pc-bios/s390-ccw/Makefile
+++ b/pc-bios/s390-ccw/Makefile
@@ -9,7 +9,7 @@  $(call set-vpath, $(SRC_PATH)/pc-bios/s390-ccw)
 
 .PHONY : all clean build-all
 
-OBJECTS = start.o main.o bootmap.o sclp.o virtio.o virtio-scsi.o virtio-blkdev.o libc.o
+OBJECTS = start.o main.o bootmap.o sclp.o virtio.o virtio-scsi.o virtio-blkdev.o libc.o menu.o
 QEMU_CFLAGS := $(filter -W%, $(QEMU_CFLAGS))
 QEMU_CFLAGS += -ffreestanding -fno-delete-null-pointer-checks -msoft-float
 QEMU_CFLAGS += -march=z900 -fPIE -fno-strict-aliasing
diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
index 5546b79..c817cf8 100644
--- a/pc-bios/s390-ccw/bootmap.c
+++ b/pc-bios/s390-ccw/bootmap.c
@@ -13,6 +13,7 @@ 
 #include "bootmap.h"
 #include "virtio.h"
 #include "bswap.h"
+#include "menu.h"
 
 #ifdef DEBUG
 /* #define DEBUG_FALLBACK */
@@ -83,6 +84,7 @@  static void jump_to_IPL_code(uint64_t address)
 
 static unsigned char _bprs[8*1024]; /* guessed "max" ECKD sector size */
 static const int max_bprs_entries = sizeof(_bprs) / sizeof(ExtEckdBlockPtr);
+static uint8_t stage2[STAGE2_MAX_SIZE] __attribute__((__aligned__(PAGE_SIZE)));
 
 static inline void verify_boot_info(BootInfo *bip)
 {
@@ -182,7 +184,57 @@  static block_number_t load_eckd_segments(block_number_t blk, uint64_t *address)
     return block_nr;
 }
 
-static void run_eckd_boot_script(block_number_t mbr_block_nr)
+static void read_stage2(block_number_t s1b_block_nr)
+{
+    block_number_t s2_block_nr;
+    EckdStage1b *s1b = (void *)sec;
+    int i;
+
+    /* Get Stage1b data */
+    memset(sec, FREE_SPACE_FILLER, sizeof(sec));
+    read_block(s1b_block_nr, s1b, "Cannot read stage1b boot loader.");
+
+    /* Get Stage2 data */
+    memset(stage2, FREE_SPACE_FILLER, sizeof(stage2));
+
+    for (i = 0; i < STAGE2_MAX_SIZE / MAX_SECTOR_SIZE; i++) {
+        s2_block_nr = eckd_block_num((void *)&(s1b->seek[i].cyl));
+
+        if (!s2_block_nr) {
+            break;
+        }
+
+        read_block(s2_block_nr, (stage2 + MAX_SECTOR_SIZE * i),
+                   "Error reading Stage2 data");
+    }
+}
+
+static bool find_zipl_boot_menu_data(block_number_t s1b_block_nr,
+                                     ZiplParms *zipl_parms)
+{
+    int offset;
+    void *s2_offset;
+
+    read_stage2(s1b_block_nr);
+
+    /* Menu banner starts with "zIPL" */
+    for (offset = 0; offset < STAGE2_MAX_SIZE - 4; offset++) {
+        s2_offset = stage2 + offset;
+
+        if (magic_match(s2_offset, ZIPL_MAGIC_EBCDIC)) {
+            zipl_parms->flag = *(uint16_t *)(s2_offset - 140);
+            zipl_parms->timeout = *(uint16_t *)(s2_offset - 138);
+            zipl_parms->menu_start = offset;
+            return true;
+        }
+    }
+
+    sclp_print("No zipl boot menu data found. Booting default entry.");
+    return false;
+}
+
+static void run_eckd_boot_script(block_number_t mbr_block_nr,
+                                 block_number_t s1b_block_nr)
 {
     int i;
     unsigned int loadparm = get_loadparm_index();
@@ -190,6 +242,12 @@  static void run_eckd_boot_script(block_number_t mbr_block_nr)
     uint64_t address;
     ScsiMbr *bte = (void *)sec; /* Eckd bootmap table entry */
     BootMapScript *bms = (void *)sec;
+    ZiplParms zipl_parms;
+
+    if (menu_check_flags(BOOT_MENU_FLAG_BOOT_OPTS | BOOT_MENU_FLAG_ZIPL_OPTS)
+        && find_zipl_boot_menu_data(s1b_block_nr, &zipl_parms)) {
+        loadparm = menu_get_zipl_boot_index(stage2, zipl_parms);
+    }
 
     debug_print_int("loadparm", loadparm);
     IPL_assert(loadparm < 31, "loadparm value greater than"
@@ -224,6 +282,7 @@  static void ipl_eckd_cdl(void)
     EckdCdlIpl2 *ipl2 = (void *)sec;
     IplVolumeLabel *vlbl = (void *)sec;
     block_number_t mbr_block_nr;
+    block_number_t s1b_block_nr;
 
     /* we have just read the block #0 and recognized it as "IPL1" */
     sclp_print("CDL\n");
@@ -241,6 +300,9 @@  static void ipl_eckd_cdl(void)
     /* save pointer to Boot Script */
     mbr_block_nr = eckd_block_num((void *)&(mbr->blockptr));
 
+    /* save pointer to Stage1b Data */
+    s1b_block_nr = eckd_block_num((void *)&(ipl2->stage1.seek[0].cyl));
+
     memset(sec, FREE_SPACE_FILLER, sizeof(sec));
     read_block(2, vlbl, "Cannot read Volume Label at block 2");
     IPL_assert(magic_match(vlbl->key, VOL1_MAGIC),
@@ -249,7 +311,7 @@  static void ipl_eckd_cdl(void)
                "Invalid magic of volser block");
     print_volser(vlbl->f.volser);
 
-    run_eckd_boot_script(mbr_block_nr);
+    run_eckd_boot_script(mbr_block_nr, s1b_block_nr);
     /* no return */
 }
 
@@ -281,6 +343,7 @@  static void print_eckd_ldl_msg(ECKD_IPL_mode_t mode)
 static void ipl_eckd_ldl(ECKD_IPL_mode_t mode)
 {
     block_number_t mbr_block_nr;
+    block_number_t s1b_block_nr;
     EckdLdlIpl1 *ipl1 = (void *)sec;
 
     if (mode != ECKD_LDL_UNLABELED) {
@@ -302,7 +365,9 @@  static void ipl_eckd_ldl(ECKD_IPL_mode_t mode)
     mbr_block_nr =
         eckd_block_num((void *)&(ipl1->boot_info.bp.ipl.bm_ptr.eckd.bptr));
 
-    run_eckd_boot_script(mbr_block_nr);
+    s1b_block_nr = eckd_block_num((void *)&(ipl1->stage1.seek[0].cyl));
+
+    run_eckd_boot_script(mbr_block_nr, s1b_block_nr);
     /* no return */
 }
 
diff --git a/pc-bios/s390-ccw/bootmap.h b/pc-bios/s390-ccw/bootmap.h
index b700d08..8089402 100644
--- a/pc-bios/s390-ccw/bootmap.h
+++ b/pc-bios/s390-ccw/bootmap.h
@@ -74,6 +74,7 @@  typedef struct ScsiMbr {
 } __attribute__ ((packed)) ScsiMbr;
 
 #define ZIPL_MAGIC              "zIPL"
+#define ZIPL_MAGIC_EBCDIC       "\xa9\xc9\xd7\xd3"
 #define IPL1_MAGIC "\xc9\xd7\xd3\xf1" /* == "IPL1" in EBCDIC */
 #define IPL2_MAGIC "\xc9\xd7\xd3\xf2" /* == "IPL2" in EBCDIC */
 #define VOL1_MAGIC "\xe5\xd6\xd3\xf1" /* == "VOL1" in EBCDIC */
@@ -229,6 +230,7 @@  typedef struct BootInfo {          /* @ 0x70, record #0    */
 /*
  * Structs for IPL
  */
+#define STAGE2_MAX_SIZE     0x3000
 #define STAGE2_BLK_CNT_MAX  24 /* Stage 1b can load up to 24 blocks */
 
 typedef struct EckdCdlIpl1 {
diff --git a/pc-bios/s390-ccw/main.c b/pc-bios/s390-ccw/main.c
index a8ef120..fb0ef92 100644
--- a/pc-bios/s390-ccw/main.c
+++ b/pc-bios/s390-ccw/main.c
@@ -11,6 +11,7 @@ 
 #include "libc.h"
 #include "s390-ccw.h"
 #include "virtio.h"
+#include "menu.h"
 
 char stack[PAGE_SIZE * 8] __attribute__((__aligned__(PAGE_SIZE)));
 static SubChannelId blk_schid = { .one = 1 };
@@ -101,6 +102,8 @@  static void virtio_setup(void)
             blk_schid.ssid = iplb.ccw.ssid & 0x3;
             debug_print_int("ssid ", blk_schid.ssid);
             found = find_dev(&schib, dev_no);
+            menu_set_parms(iplb.ccw.boot_menu_flags,
+                           iplb.ccw.boot_menu_timeout);
             break;
         case S390_IPL_TYPE_QEMU_SCSI:
             vdev->scsi_device_selected = true;
diff --git a/pc-bios/s390-ccw/menu.c b/pc-bios/s390-ccw/menu.c
new file mode 100644
index 0000000..d707afb
--- /dev/null
+++ b/pc-bios/s390-ccw/menu.c
@@ -0,0 +1,223 @@ 
+/*
+ * QEMU S390 Interactive Boot Menu
+ *
+ * Copyright 2017 IBM Corp.
+ * Author: Collin L. Walling <walling@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at
+ * your option) any later version. See the COPYING file in the top-level
+ * directory.
+ */
+
+#include "libc.h"
+#include "s390-ccw.h"
+#include "menu.h"
+
+#define KEYCODE_NO_INP '\0'
+#define KEYCODE_ESCAPE '\033'
+#define KEYCODE_BACKSP '\177'
+#define KEYCODE_ENTER  '\r'
+
+static uint8_t flags;
+static uint64_t timeout;
+
+static inline void enable_clock_int(void)
+{
+    uint64_t tmp = 0;
+
+    asm volatile(
+        "stctg      0,0,%0\n"
+        "oi         6+%0, 0x8\n"
+        "lctlg      0,0,%0"
+        : : "Q" (tmp)
+    );
+}
+
+static inline void disable_clock_int(void)
+{
+    uint64_t tmp = 0;
+
+    asm volatile(
+        "stctg      0,0,%0\n"
+        "ni         6+%0, 0xf7\n"
+        "lctlg      0,0,%0"
+        : : "Q" (tmp)
+    );
+}
+
+static inline void set_clock_comparator(uint64_t time)
+{
+    asm volatile("sckc %0" : : "Q" (time));
+}
+
+static inline bool check_clock_int(void)
+{
+    uint16_t code = *(uint16_t *)0x86;
+
+    consume_sclp_int();
+
+    return code == 0x1004;
+}
+
+static int read_prompt(char *buf, size_t len)
+{
+    char inp[2];
+    uint8_t idx = 0;
+    uint64_t time;
+
+    if (timeout) {
+        time = get_clock() + (timeout << 32);
+        set_clock_comparator(time);
+        enable_clock_int();
+    }
+
+    inp[1] = '\0';
+
+    while (!check_clock_int()) {
+
+        /* Process only one character at a time */
+        sclp_read(inp, 1);
+
+        switch (inp[0]) {
+        case KEYCODE_NO_INP:
+        case KEYCODE_ESCAPE:
+            continue;
+        case KEYCODE_BACKSP:
+            if (idx > 0) {
+                /* Remove last character */
+                buf[idx - 1] = ' ';
+                sclp_print("\r");
+                sclp_print(buf);
+
+                idx--;
+
+                /* Reset cursor */
+                buf[idx] = 0;
+                sclp_print("\r");
+                sclp_print(buf);
+            }
+            continue;
+        case KEYCODE_ENTER:
+            disable_clock_int();
+            return idx;
+        }
+
+        /* Echo input and add to buffer */
+        if (idx < len) {
+            buf[idx] = inp[0];
+            sclp_print(inp);
+            idx++;
+        }
+    }
+
+    disable_clock_int();
+    *buf = NULL;
+
+    return 0;
+}
+
+static int get_index(void)
+{
+    char buf[10];
+    int len;
+    int i;
+
+    memset(buf, 0, sizeof(buf));
+
+    len = read_prompt(buf, sizeof(buf));
+
+    if (len == 0) {
+        return 0;
+    }
+
+    for (i = 0; i < len; i++) {
+        if (!isdigit(buf[i])) {
+            return -1;
+        }
+    }
+
+    return atoi(buf);
+}
+
+static int get_boot_index(int entries)
+{
+    char tmp[6];
+    int boot_index;
+
+    /* Prompt User */
+    if (timeout > 0) {
+        sclp_print("Please choose (default will boot in ");
+        sclp_print(itostr(timeout, tmp, sizeof(tmp)));
+        sclp_print(" seconds):\n");
+    } else {
+        sclp_print("Please choose:\n");
+    }
+
+    /* Get Menu Choice */
+    boot_index = get_index();
+
+    timeout = 0;
+
+    while (boot_index < 0 || boot_index >= entries) {
+        sclp_print("\nError: undefined configuration"
+                   "\nPlease choose:\n");
+        boot_index = get_index();
+    }
+
+    sclp_print("\nBooting entry #");
+    sclp_print(itostr(boot_index, tmp, sizeof(tmp)));
+
+    return boot_index;
+}
+
+static void zipl_println(const char *data, size_t len)
+{
+    char buf[len + 1];
+
+    ebcdic_to_ascii(data, buf, len);
+    buf[len] = '\n';
+    buf[len + 1] = '\0';
+
+    sclp_print(buf);
+}
+
+int menu_get_zipl_boot_index(const void *stage2, ZiplParms zipl_parms)
+{
+    const char *data = stage2 + zipl_parms.menu_start;
+    size_t len;
+    int ct;
+
+    if (flags & BOOT_MENU_FLAG_ZIPL_OPTS) {
+        if (zipl_parms.flag) {
+            timeout = zipl_parms.timeout;
+        } else {
+            return 0; /* Boot default */
+        }
+    }
+
+    /* Print and count all menu items, including the banner */
+    for (ct = 0; *data; ct++) {
+        len = strlen(data);
+        zipl_println(data, len);
+        data += len + 1;
+
+        if (ct < 2) {
+            sclp_print("\n");
+        }
+    }
+
+    sclp_print("\n");
+
+    return get_boot_index(ct - 1);
+}
+
+void menu_set_parms(uint8_t boot_menu_flag, uint16_t boot_menu_timeout)
+{
+    flags = boot_menu_flag;
+    timeout = boot_menu_timeout;
+}
+
+int menu_check_flags(uint8_t check_flags)
+{
+    return flags & check_flags;
+}
diff --git a/pc-bios/s390-ccw/menu.h b/pc-bios/s390-ccw/menu.h
new file mode 100644
index 0000000..a8727fa
--- /dev/null
+++ b/pc-bios/s390-ccw/menu.h
@@ -0,0 +1,28 @@ 
+/*
+ * QEMU S390 Interactive Boot Menu
+ *
+ * Copyright 2017 IBM Corp.
+ * Author: Collin L. Walling <walling@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at
+ * your option) any later version. See the COPYING file in the top-level
+ * directory.
+ */
+
+#ifndef MENU_H
+#define MENU_H
+
+#define BOOT_MENU_FLAG_BOOT_OPTS 0x80
+#define BOOT_MENU_FLAG_ZIPL_OPTS 0x40
+
+typedef struct ZiplParms {
+    unsigned short flag;
+    unsigned short timeout;
+    unsigned long long menu_start;
+} ZiplParms;
+
+void menu_set_parms(uint8_t boot_menu_flags, uint16_t boot_menu_timeout);
+bool menu_check_flags(uint8_t check_flags);
+int menu_get_zipl_boot_index(const void *stage2, ZiplParms zipl_parms);
+
+#endif /* MENU_H */
diff --git a/pc-bios/s390-ccw/s390-ccw.h b/pc-bios/s390-ccw/s390-ccw.h
index 25d4d21..df4bc88 100644
--- a/pc-bios/s390-ccw/s390-ccw.h
+++ b/pc-bios/s390-ccw/s390-ccw.h
@@ -71,6 +71,7 @@  unsigned int get_loadparm_index(void);
 void sclp_print(const char *string);
 void sclp_setup(void);
 void sclp_get_loadparm_ascii(char *loadparm);
+void sclp_read(char *str, size_t len);
 
 /* virtio.c */
 unsigned long virtio_load_direct(ulong rec_list1, ulong rec_list2,
@@ -79,6 +80,7 @@  bool virtio_is_supported(SubChannelId schid);
 void virtio_blk_setup_device(SubChannelId schid);
 int virtio_read(ulong sector, void *load_addr);
 int enable_mss_facility(void);
+u64 get_clock(void);
 ulong get_second(void);
 
 /* bootmap.c */
diff --git a/pc-bios/s390-ccw/sclp.c b/pc-bios/s390-ccw/sclp.c
index 486fce1..5e4a78b 100644
--- a/pc-bios/s390-ccw/sclp.c
+++ b/pc-bios/s390-ccw/sclp.c
@@ -101,3 +101,23 @@  void sclp_get_loadparm_ascii(char *loadparm)
         ebcdic_to_ascii((char *) sccb->loadparm, loadparm, 8);
     }
 }
+
+void sclp_read(char *str, size_t len)
+{
+    ReadEventData *sccb = (void *)_sccb;
+    char *buf = (char *)(&sccb->ebh) + 7;
+
+    /* Len should not exceed the maximum size of the event buffer */
+    if (len > SCCB_SIZE - 8) {
+        len = SCCB_SIZE - 8;
+    }
+
+    sccb->h.length = SCCB_SIZE;
+    sccb->h.function_code = SCLP_UNCONDITIONAL_READ;
+    sccb->ebh.length = sizeof(EventBufferHeader);
+    sccb->ebh.type = SCLP_EVENT_ASCII_CONSOLE_DATA;
+    sccb->ebh.flags = 0;
+
+    sclp_service_call(SCLP_CMD_READ_EVENT_DATA, sccb);
+    memcpy(str, buf, len);
+}
diff --git a/pc-bios/s390-ccw/virtio.c b/pc-bios/s390-ccw/virtio.c
index c890a03..817e7f5 100644
--- a/pc-bios/s390-ccw/virtio.c
+++ b/pc-bios/s390-ccw/virtio.c
@@ -176,7 +176,7 @@  void vring_send_buf(VRing *vr, void *p, int len, int flags)
     }
 }
 
-static u64 get_clock(void)
+u64 get_clock(void)
 {
     u64 r;