mirror of https://gitlab.com/qemu-project/qemu
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
268 lines
5.7 KiB
C
268 lines
5.7 KiB
C
/*
|
|
* QEMU S390 Interactive Boot Menu
|
|
*
|
|
* Copyright 2018 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 <ctype.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "s390-ccw.h"
|
|
#include "sclp.h"
|
|
#include "s390-time.h"
|
|
|
|
#define KEYCODE_NO_INP '\0'
|
|
#define KEYCODE_ESCAPE '\033'
|
|
#define KEYCODE_BACKSP '\177'
|
|
#define KEYCODE_ENTER '\r'
|
|
|
|
/* Offsets from zipl fields to zipl banner start */
|
|
#define ZIPL_TIMEOUT_OFFSET 138
|
|
#define ZIPL_FLAG_OFFSET 140
|
|
|
|
#define TOD_CLOCK_MILLISECOND 0x3e8000
|
|
|
|
#define LOW_CORE_EXTERNAL_INT_ADDR 0x86
|
|
#define CLOCK_COMPARATOR_INT 0X1004
|
|
|
|
static uint8_t flag;
|
|
static uint64_t timeout;
|
|
|
|
static inline void enable_clock_int(void)
|
|
{
|
|
uint64_t tmp = 0;
|
|
|
|
asm volatile(
|
|
"stctg %%c0,%%c0,%0\n"
|
|
"oi 6+%0, 0x8\n"
|
|
"lctlg %%c0,%%c0,%0"
|
|
: : "Q" (tmp) : "memory"
|
|
);
|
|
}
|
|
|
|
static inline void disable_clock_int(void)
|
|
{
|
|
uint64_t tmp = 0;
|
|
|
|
asm volatile(
|
|
"stctg %%c0,%%c0,%0\n"
|
|
"ni 6+%0, 0xf7\n"
|
|
"lctlg %%c0,%%c0,%0"
|
|
: : "Q" (tmp) : "memory"
|
|
);
|
|
}
|
|
|
|
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 *)LOW_CORE_EXTERNAL_INT_ADDR;
|
|
|
|
consume_sclp_int();
|
|
|
|
return *code == CLOCK_COMPARATOR_INT;
|
|
}
|
|
|
|
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 * TOD_CLOCK_MILLISECOND;
|
|
set_clock_comparator(time);
|
|
enable_clock_int();
|
|
timeout = 0;
|
|
}
|
|
|
|
while (!check_clock_int()) {
|
|
|
|
sclp_read(inp, 1); /* Process only one character at a time */
|
|
|
|
switch (inp[0]) {
|
|
case KEYCODE_NO_INP:
|
|
case KEYCODE_ESCAPE:
|
|
continue;
|
|
case KEYCODE_BACKSP:
|
|
if (idx > 0) {
|
|
buf[--idx] = 0;
|
|
printf("\b \b");
|
|
}
|
|
continue;
|
|
case KEYCODE_ENTER:
|
|
disable_clock_int();
|
|
return idx;
|
|
default:
|
|
/* Echo input and add to buffer */
|
|
if (idx < len) {
|
|
buf[idx++] = inp[0];
|
|
printf("%s", inp);
|
|
}
|
|
}
|
|
}
|
|
|
|
disable_clock_int();
|
|
*buf = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int get_index(void)
|
|
{
|
|
char buf[11];
|
|
int len;
|
|
int i;
|
|
|
|
memset(buf, 0, sizeof(buf));
|
|
|
|
sclp_set_write_mask(SCLP_EVENT_MASK_MSG_ASCII, SCLP_EVENT_MASK_MSG_ASCII);
|
|
|
|
len = read_prompt(buf, sizeof(buf) - 1);
|
|
|
|
sclp_set_write_mask(0, SCLP_EVENT_MASK_MSG_ASCII);
|
|
|
|
/* If no input, boot default */
|
|
if (len == 0) {
|
|
return 0;
|
|
}
|
|
|
|
/* Check for erroneous input */
|
|
for (i = 0; i < len; i++) {
|
|
if (!isdigit((unsigned char)buf[i])) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return atoi(buf);
|
|
}
|
|
|
|
static void boot_menu_prompt(bool retry)
|
|
{
|
|
if (retry) {
|
|
printf("\nError: undefined configuration"
|
|
"\nPlease choose:\n");
|
|
} else if (timeout > 0) {
|
|
printf("Please choose (default will boot in %d seconds):\n",
|
|
(int)(timeout / 1000));
|
|
} else {
|
|
puts("Please choose:");
|
|
}
|
|
}
|
|
|
|
static int get_boot_index(bool *valid_entries)
|
|
{
|
|
int boot_index;
|
|
bool retry = false;
|
|
|
|
do {
|
|
boot_menu_prompt(retry);
|
|
boot_index = get_index();
|
|
retry = true;
|
|
} while (boot_index < 0 || boot_index >= MAX_BOOT_ENTRIES ||
|
|
!valid_entries[boot_index]);
|
|
|
|
printf("\nBooting entry #%d", boot_index);
|
|
|
|
return boot_index;
|
|
}
|
|
|
|
/* Returns the entry number that was printed */
|
|
static int zipl_print_entry(const char *data, size_t len)
|
|
{
|
|
char buf[len + 2];
|
|
|
|
ebcdic_to_ascii(data, buf, len);
|
|
buf[len] = '\n';
|
|
buf[len + 1] = '\0';
|
|
|
|
printf("%s", buf);
|
|
|
|
return buf[0] == ' ' ? atoi(buf + 1) : atoi(buf);
|
|
}
|
|
|
|
int menu_get_zipl_boot_index(const char *menu_data)
|
|
{
|
|
size_t len;
|
|
int entry;
|
|
bool valid_entries[MAX_BOOT_ENTRIES] = {false};
|
|
uint16_t zipl_flag = *(uint16_t *)(menu_data - ZIPL_FLAG_OFFSET);
|
|
uint16_t zipl_timeout = *(uint16_t *)(menu_data - ZIPL_TIMEOUT_OFFSET);
|
|
|
|
if (flag == QIPL_FLAG_BM_OPTS_ZIPL) {
|
|
if (!zipl_flag) {
|
|
return 0; /* Boot default */
|
|
}
|
|
/* zipl stores timeout as seconds */
|
|
timeout = zipl_timeout * 1000;
|
|
}
|
|
|
|
/* Print banner */
|
|
puts("s390-ccw zIPL Boot Menu\n");
|
|
menu_data += strlen(menu_data) + 1;
|
|
|
|
/* Print entries */
|
|
while (*menu_data) {
|
|
len = strlen(menu_data);
|
|
entry = zipl_print_entry(menu_data, len);
|
|
menu_data += len + 1;
|
|
|
|
valid_entries[entry] = true;
|
|
|
|
if (entry == 0) {
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
printf("\n");
|
|
return get_boot_index(valid_entries);
|
|
}
|
|
|
|
int menu_get_enum_boot_index(bool *valid_entries)
|
|
{
|
|
int i;
|
|
|
|
puts("s390-ccw Enumerated Boot Menu.\n");
|
|
|
|
for (i = 0; i < MAX_BOOT_ENTRIES; i++) {
|
|
if (valid_entries[i]) {
|
|
if (i < 10) {
|
|
printf(" ");
|
|
}
|
|
printf("[%d]", i);
|
|
if (i == 0) {
|
|
printf(" default\n");
|
|
}
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
printf("\n");
|
|
return get_boot_index(valid_entries);
|
|
}
|
|
|
|
void menu_set_parms(uint8_t boot_menu_flag, uint32_t boot_menu_timeout)
|
|
{
|
|
flag = boot_menu_flag;
|
|
timeout = boot_menu_timeout;
|
|
}
|
|
|
|
bool menu_is_enabled_zipl(void)
|
|
{
|
|
return flag & (QIPL_FLAG_BM_OPTS_CMD | QIPL_FLAG_BM_OPTS_ZIPL);
|
|
}
|
|
|
|
bool menu_is_enabled_enum(void)
|
|
{
|
|
return flag & QIPL_FLAG_BM_OPTS_CMD;
|
|
}
|