| 1 | /* |
| 2 | * QEMU S390 Interactive Boot Menu |
| 3 | * |
| 4 | * Copyright 2018 IBM Corp. |
| 5 | * Author: Collin L. Walling <walling@linux.vnet.ibm.com> |
| 6 | * |
| 7 | * This work is licensed under the terms of the GNU GPL, version 2 or (at |
| 8 | * your option) any later version. See the COPYING file in the top-level |
| 9 | * directory. |
| 10 | */ |
| 11 | |
| 12 | #include <ctype.h> |
| 13 | #include <stdio.h> |
| 14 | #include <stdlib.h> |
| 15 | #include <string.h> |
| 16 | #include "s390-ccw.h" |
| 17 | #include "sclp.h" |
| 18 | #include "s390-time.h" |
| 19 | #include "helper.h" |
| 20 | |
| 21 | #define KEYCODE_NO_INP '\0' |
| 22 | #define KEYCODE_ESCAPE '\033' |
| 23 | #define KEYCODE_BACKSP '\177' |
| 24 | #define KEYCODE_ENTER '\r' |
| 25 | |
| 26 | /* Offsets from zipl fields to zipl banner start */ |
| 27 | #define ZIPL_TIMEOUT_OFFSET 138 |
| 28 | #define ZIPL_FLAG_OFFSET 140 |
| 29 | |
| 30 | /* Max printable chars for a zipl boot menu entry */ |
| 31 | #define ZIPL_ENTRY_MAX 80 |
| 32 | |
| 33 | #define TOD_CLOCK_MILLISECOND 0x3e8000 |
| 34 | |
| 35 | #define LOW_CORE_EXTERNAL_INT_ADDR 0x86 |
| 36 | #define CLOCK_COMPARATOR_INT 0X1004 |
| 37 | |
| 38 | static uint8_t flag; |
| 39 | static uint64_t timeout; |
| 40 | |
| 41 | static inline void enable_clock_int(void) |
| 42 | { |
| 43 | uint64_t tmp = 0; |
| 44 | |
| 45 | asm volatile( |
| 46 | "stctg %%c0,%%c0,%0\n" |
| 47 | "oi 6+%0, 0x8\n" |
| 48 | "lctlg %%c0,%%c0,%0" |
| 49 | : : "Q" (tmp) : "memory" |
| 50 | ); |
| 51 | } |
| 52 | |
| 53 | static inline void disable_clock_int(void) |
| 54 | { |
| 55 | uint64_t tmp = 0; |
| 56 | |
| 57 | asm volatile( |
| 58 | "stctg %%c0,%%c0,%0\n" |
| 59 | "ni 6+%0, 0xf7\n" |
| 60 | "lctlg %%c0,%%c0,%0" |
| 61 | : : "Q" (tmp) : "memory" |
| 62 | ); |
| 63 | } |
| 64 | |
| 65 | static inline void set_clock_comparator(uint64_t time) |
| 66 | { |
| 67 | asm volatile("sckc %0" : : "Q" (time)); |
| 68 | } |
| 69 | |
| 70 | static inline bool check_clock_int(void) |
| 71 | { |
| 72 | uint16_t *code = (uint16_t *)LOW_CORE_EXTERNAL_INT_ADDR; |
| 73 | |
| 74 | consume_sclp_int(); |
| 75 | |
| 76 | return *code == CLOCK_COMPARATOR_INT; |
| 77 | } |
| 78 | |
| 79 | static int read_prompt(char *buf, size_t len) |
| 80 | { |
| 81 | char inp[2] = {}; |
| 82 | uint8_t idx = 0; |
| 83 | uint64_t time; |
| 84 | |
| 85 | if (timeout) { |
| 86 | time = get_clock() + timeout * TOD_CLOCK_MILLISECOND; |
| 87 | set_clock_comparator(time); |
| 88 | enable_clock_int(); |
| 89 | timeout = 0; |
| 90 | } |
| 91 | |
| 92 | while (!check_clock_int()) { |
| 93 | |
| 94 | sclp_read(inp, 1); /* Process only one character at a time */ |
| 95 | |
| 96 | switch (inp[0]) { |
| 97 | case KEYCODE_NO_INP: |
| 98 | case KEYCODE_ESCAPE: |
| 99 | continue; |
| 100 | case KEYCODE_BACKSP: |
| 101 | if (idx > 0) { |
| 102 | buf[--idx] = 0; |
| 103 | printf("\b \b"); |
| 104 | } |
| 105 | continue; |
| 106 | case KEYCODE_ENTER: |
| 107 | disable_clock_int(); |
| 108 | return idx; |
| 109 | default: |
| 110 | /* Echo input and add to buffer */ |
| 111 | if (idx < len) { |
| 112 | buf[idx++] = inp[0]; |
| 113 | printf("%s", inp); |
| 114 | } |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | disable_clock_int(); |
| 119 | *buf = 0; |
| 120 | |
| 121 | return 0; |
| 122 | } |
| 123 | |
| 124 | static int get_index(void) |
| 125 | { |
| 126 | char buf[11]; |
| 127 | int len; |
| 128 | int i; |
| 129 | |
| 130 | memset(buf, 0, sizeof(buf)); |
| 131 | |
| 132 | sclp_set_write_mask(SCLP_EVENT_MASK_MSG_ASCII, SCLP_EVENT_MASK_MSG_ASCII); |
| 133 | |
| 134 | len = read_prompt(buf, sizeof(buf) - 1); |
| 135 | |
| 136 | sclp_set_write_mask(0, SCLP_EVENT_MASK_MSG_ASCII); |
| 137 | |
| 138 | /* If no input, boot default */ |
| 139 | if (len == 0) { |
| 140 | return 0; |
| 141 | } |
| 142 | |
| 143 | /* Check for erroneous input */ |
| 144 | for (i = 0; i < len; i++) { |
| 145 | if (!isdigit((unsigned char)buf[i])) { |
| 146 | return -1; |
| 147 | } |
| 148 | } |
| 149 | |
| 150 | return atoi(buf); |
| 151 | } |
| 152 | |
| 153 | static void boot_menu_prompt(bool retry) |
| 154 | { |
| 155 | if (retry) { |
| 156 | printf("\nError: undefined configuration" |
| 157 | "\nPlease choose:\n"); |
| 158 | } else if (timeout > 0) { |
| 159 | printf("Please choose (default will boot in %d seconds):\n", |
| 160 | (int)(timeout / 1000)); |
| 161 | } else { |
| 162 | puts("Please choose:"); |
| 163 | } |
| 164 | } |
| 165 | |
| 166 | int menu_get_boot_index(bool *valid_entries) |
| 167 | { |
| 168 | int boot_index; |
| 169 | bool retry = false; |
| 170 | |
| 171 | do { |
| 172 | boot_menu_prompt(retry); |
| 173 | boot_index = get_index(); |
| 174 | retry = true; |
| 175 | } while (boot_index < 0 || boot_index >= MAX_BOOT_ENTRIES || |
| 176 | !valid_entries[boot_index]); |
| 177 | |
| 178 | printf("\nBooting entry #%d", boot_index); |
| 179 | |
| 180 | return boot_index; |
| 181 | } |
| 182 | |
| 183 | /* Returns the entry number that was printed, or -1 on invalid entry */ |
| 184 | static int zipl_print_entry(const char *data, size_t len) |
| 185 | { |
| 186 | char buf[ZIPL_ENTRY_MAX + 2]; |
| 187 | const char *p; |
| 188 | |
| 189 | if (len > ZIPL_ENTRY_MAX) { |
| 190 | len = ZIPL_ENTRY_MAX; |
| 191 | } |
| 192 | |
| 193 | ebcdic_to_ascii(data, buf, len); |
| 194 | buf[len] = '\n'; |
| 195 | buf[len + 1] = '\0'; |
| 196 | |
| 197 | p = (buf[0] == ' ') ? buf + 1 : buf; |
| 198 | if (!isdigit((unsigned char)*p)) { |
| 199 | return -1; |
| 200 | } |
| 201 | |
| 202 | printf("%s", buf); |
| 203 | |
| 204 | return atoi(p); |
| 205 | } |
| 206 | |
| 207 | int menu_get_zipl_boot_index(const char *menu_data, const char *menu_data_end) |
| 208 | { |
| 209 | size_t len; |
| 210 | int entry; |
| 211 | bool valid_entries[MAX_BOOT_ENTRIES] = {false}; |
| 212 | uint16_t zipl_flag = *(uint16_t *)(menu_data - ZIPL_FLAG_OFFSET); |
| 213 | uint16_t zipl_timeout = *(uint16_t *)(menu_data - ZIPL_TIMEOUT_OFFSET); |
| 214 | |
| 215 | if (flag == QIPL_FLAG_BM_OPTS_ZIPL) { |
| 216 | if (!zipl_flag) { |
| 217 | return 0; /* Boot default */ |
| 218 | } |
| 219 | /* zipl stores timeout as seconds */ |
| 220 | timeout = zipl_timeout * 1000; |
| 221 | } |
| 222 | |
| 223 | if (menu_data >= menu_data_end) { |
| 224 | return 0; /* Boot default */ |
| 225 | } |
| 226 | |
| 227 | /* Skip banner */ |
| 228 | len = strnlen(menu_data, menu_data_end - menu_data); |
| 229 | menu_data += len + 1; |
| 230 | if (menu_data >= menu_data_end || !(*menu_data)) { |
| 231 | return 0; /* No entries, boot default */ |
| 232 | } |
| 233 | |
| 234 | puts("s390-ccw zIPL Boot Menu\n"); |
| 235 | |
| 236 | /* Print entries */ |
| 237 | while (menu_data < menu_data_end && *menu_data) { |
| 238 | len = strnlen(menu_data, menu_data_end - menu_data); |
| 239 | entry = zipl_print_entry(menu_data, len); |
| 240 | menu_data += len + 1; |
| 241 | |
| 242 | if (entry < 0 || entry >= MAX_BOOT_ENTRIES) { |
| 243 | continue; |
| 244 | } |
| 245 | valid_entries[entry] = true; |
| 246 | |
| 247 | if (entry == 0) { |
| 248 | printf("\n"); |
| 249 | } |
| 250 | } |
| 251 | |
| 252 | printf("\n"); |
| 253 | return menu_get_boot_index(valid_entries); |
| 254 | } |
| 255 | |
| 256 | int menu_get_enum_boot_index(bool *valid_entries) |
| 257 | { |
| 258 | int i; |
| 259 | |
| 260 | puts("s390-ccw Enumerated Boot Menu.\n"); |
| 261 | |
| 262 | for (i = 0; i < MAX_BOOT_ENTRIES; i++) { |
| 263 | if (valid_entries[i]) { |
| 264 | if (i < 10) { |
| 265 | printf(" "); |
| 266 | } |
| 267 | printf("[%d]", i); |
| 268 | if (i == 0) { |
| 269 | printf(" default\n"); |
| 270 | } |
| 271 | printf("\n"); |
| 272 | } |
| 273 | } |
| 274 | |
| 275 | printf("\n"); |
| 276 | return menu_get_boot_index(valid_entries); |
| 277 | } |
| 278 | |
| 279 | void menu_set_parms(uint8_t boot_menu_flag, uint32_t boot_menu_timeout) |
| 280 | { |
| 281 | flag = boot_menu_flag; |
| 282 | timeout = boot_menu_timeout; |
| 283 | } |
| 284 | |
| 285 | bool menu_is_enabled_zipl(void) |
| 286 | { |
| 287 | return flag & (QIPL_FLAG_BM_OPTS_CMD | QIPL_FLAG_BM_OPTS_ZIPL); |
| 288 | } |
| 289 | |
| 290 | bool menu_is_enabled_enum(void) |
| 291 | { |
| 292 | return flag & QIPL_FLAG_BM_OPTS_CMD; |
| 293 | } |