]>
Commit | Line | Data |
---|---|---|
9eaa654a CW |
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 "libc.h" | |
13 | #include "s390-ccw.h" | |
dbf2091a | 14 | #include "sclp.h" |
e70bc57b | 15 | #include "s390-time.h" |
9eaa654a | 16 | |
ff5dbf1b CW |
17 | #define KEYCODE_NO_INP '\0' |
18 | #define KEYCODE_ESCAPE '\033' | |
19 | #define KEYCODE_BACKSP '\177' | |
20 | #define KEYCODE_ENTER '\r' | |
21 | ||
53b310ce CW |
22 | /* Offsets from zipl fields to zipl banner start */ |
23 | #define ZIPL_TIMEOUT_OFFSET 138 | |
24 | #define ZIPL_FLAG_OFFSET 140 | |
25 | ||
ff5dbf1b CW |
26 | #define TOD_CLOCK_MILLISECOND 0x3e8000 |
27 | ||
28 | #define LOW_CORE_EXTERNAL_INT_ADDR 0x86 | |
29 | #define CLOCK_COMPARATOR_INT 0X1004 | |
30 | ||
9eaa654a CW |
31 | static uint8_t flag; |
32 | static uint64_t timeout; | |
33 | ||
ff5dbf1b CW |
34 | static inline void enable_clock_int(void) |
35 | { | |
36 | uint64_t tmp = 0; | |
37 | ||
38 | asm volatile( | |
052b66e7 | 39 | "stctg %%c0,%%c0,%0\n" |
ff5dbf1b | 40 | "oi 6+%0, 0x8\n" |
052b66e7 | 41 | "lctlg %%c0,%%c0,%0" |
ff5dbf1b CW |
42 | : : "Q" (tmp) : "memory" |
43 | ); | |
44 | } | |
45 | ||
46 | static inline void disable_clock_int(void) | |
47 | { | |
48 | uint64_t tmp = 0; | |
49 | ||
50 | asm volatile( | |
052b66e7 | 51 | "stctg %%c0,%%c0,%0\n" |
ff5dbf1b | 52 | "ni 6+%0, 0xf7\n" |
052b66e7 | 53 | "lctlg %%c0,%%c0,%0" |
ff5dbf1b CW |
54 | : : "Q" (tmp) : "memory" |
55 | ); | |
56 | } | |
57 | ||
58 | static inline void set_clock_comparator(uint64_t time) | |
59 | { | |
60 | asm volatile("sckc %0" : : "Q" (time)); | |
61 | } | |
62 | ||
63 | static inline bool check_clock_int(void) | |
64 | { | |
65 | uint16_t *code = (uint16_t *)LOW_CORE_EXTERNAL_INT_ADDR; | |
66 | ||
67 | consume_sclp_int(); | |
68 | ||
69 | return *code == CLOCK_COMPARATOR_INT; | |
70 | } | |
71 | ||
72 | static int read_prompt(char *buf, size_t len) | |
73 | { | |
74 | char inp[2] = {}; | |
75 | uint8_t idx = 0; | |
76 | uint64_t time; | |
77 | ||
78 | if (timeout) { | |
79 | time = get_clock() + timeout * TOD_CLOCK_MILLISECOND; | |
80 | set_clock_comparator(time); | |
81 | enable_clock_int(); | |
82 | timeout = 0; | |
83 | } | |
84 | ||
85 | while (!check_clock_int()) { | |
86 | ||
87 | sclp_read(inp, 1); /* Process only one character at a time */ | |
88 | ||
89 | switch (inp[0]) { | |
90 | case KEYCODE_NO_INP: | |
91 | case KEYCODE_ESCAPE: | |
92 | continue; | |
93 | case KEYCODE_BACKSP: | |
94 | if (idx > 0) { | |
95 | buf[--idx] = 0; | |
96 | sclp_print("\b \b"); | |
97 | } | |
98 | continue; | |
99 | case KEYCODE_ENTER: | |
100 | disable_clock_int(); | |
101 | return idx; | |
102 | default: | |
103 | /* Echo input and add to buffer */ | |
104 | if (idx < len) { | |
105 | buf[idx++] = inp[0]; | |
106 | sclp_print(inp); | |
107 | } | |
108 | } | |
109 | } | |
110 | ||
111 | disable_clock_int(); | |
112 | *buf = 0; | |
113 | ||
114 | return 0; | |
115 | } | |
116 | ||
117 | static int get_index(void) | |
118 | { | |
119 | char buf[11]; | |
120 | int len; | |
121 | int i; | |
122 | ||
123 | memset(buf, 0, sizeof(buf)); | |
124 | ||
dbf2091a CW |
125 | sclp_set_write_mask(SCLP_EVENT_MASK_MSG_ASCII, SCLP_EVENT_MASK_MSG_ASCII); |
126 | ||
ff5dbf1b CW |
127 | len = read_prompt(buf, sizeof(buf) - 1); |
128 | ||
dbf2091a CW |
129 | sclp_set_write_mask(0, SCLP_EVENT_MASK_MSG_ASCII); |
130 | ||
ff5dbf1b CW |
131 | /* If no input, boot default */ |
132 | if (len == 0) { | |
133 | return 0; | |
134 | } | |
135 | ||
136 | /* Check for erroneous input */ | |
137 | for (i = 0; i < len; i++) { | |
d796588b | 138 | if (!isdigit((unsigned char)buf[i])) { |
ff5dbf1b CW |
139 | return -1; |
140 | } | |
141 | } | |
142 | ||
143 | return atoui(buf); | |
144 | } | |
145 | ||
146 | static void boot_menu_prompt(bool retry) | |
147 | { | |
148 | char tmp[11]; | |
149 | ||
150 | if (retry) { | |
151 | sclp_print("\nError: undefined configuration" | |
152 | "\nPlease choose:\n"); | |
153 | } else if (timeout > 0) { | |
154 | sclp_print("Please choose (default will boot in "); | |
155 | sclp_print(uitoa(timeout / 1000, tmp, sizeof(tmp))); | |
156 | sclp_print(" seconds):\n"); | |
157 | } else { | |
158 | sclp_print("Please choose:\n"); | |
159 | } | |
160 | } | |
161 | ||
7385e947 | 162 | static int get_boot_index(bool *valid_entries) |
ba831b25 | 163 | { |
ff5dbf1b CW |
164 | int boot_index; |
165 | bool retry = false; | |
166 | char tmp[5]; | |
167 | ||
168 | do { | |
169 | boot_menu_prompt(retry); | |
170 | boot_index = get_index(); | |
171 | retry = true; | |
7385e947 CW |
172 | } while (boot_index < 0 || boot_index >= MAX_BOOT_ENTRIES || |
173 | !valid_entries[boot_index]); | |
ff5dbf1b CW |
174 | |
175 | sclp_print("\nBooting entry #"); | |
176 | sclp_print(uitoa(boot_index, tmp, sizeof(tmp))); | |
177 | ||
178 | return boot_index; | |
ba831b25 CW |
179 | } |
180 | ||
7385e947 CW |
181 | /* Returns the entry number that was printed */ |
182 | static int zipl_print_entry(const char *data, size_t len) | |
f7178910 CW |
183 | { |
184 | char buf[len + 2]; | |
185 | ||
186 | ebcdic_to_ascii(data, buf, len); | |
187 | buf[len] = '\n'; | |
188 | buf[len + 1] = '\0'; | |
189 | ||
190 | sclp_print(buf); | |
7385e947 CW |
191 | |
192 | return buf[0] == ' ' ? atoui(buf + 1) : atoui(buf); | |
f7178910 CW |
193 | } |
194 | ||
195 | int menu_get_zipl_boot_index(const char *menu_data) | |
196 | { | |
197 | size_t len; | |
7385e947 CW |
198 | int entry; |
199 | bool valid_entries[MAX_BOOT_ENTRIES] = {false}; | |
53b310ce CW |
200 | uint16_t zipl_flag = *(uint16_t *)(menu_data - ZIPL_FLAG_OFFSET); |
201 | uint16_t zipl_timeout = *(uint16_t *)(menu_data - ZIPL_TIMEOUT_OFFSET); | |
202 | ||
203 | if (flag == QIPL_FLAG_BM_OPTS_ZIPL) { | |
204 | if (!zipl_flag) { | |
205 | return 0; /* Boot default */ | |
206 | } | |
207 | /* zipl stores timeout as seconds */ | |
208 | timeout = zipl_timeout * 1000; | |
209 | } | |
f7178910 | 210 | |
7385e947 CW |
211 | /* Print banner */ |
212 | sclp_print("s390-ccw zIPL Boot Menu\n\n"); | |
213 | menu_data += strlen(menu_data) + 1; | |
214 | ||
215 | /* Print entries */ | |
216 | while (*menu_data) { | |
f7178910 | 217 | len = strlen(menu_data); |
7385e947 | 218 | entry = zipl_print_entry(menu_data, len); |
f7178910 CW |
219 | menu_data += len + 1; |
220 | ||
7385e947 CW |
221 | valid_entries[entry] = true; |
222 | ||
223 | if (entry == 0) { | |
f7178910 CW |
224 | sclp_print("\n"); |
225 | } | |
226 | } | |
227 | ||
228 | sclp_print("\n"); | |
7385e947 | 229 | return get_boot_index(valid_entries); |
f7178910 CW |
230 | } |
231 | ||
622b3917 | 232 | int menu_get_enum_boot_index(bool *valid_entries) |
ffb4a1c8 | 233 | { |
622b3917 CW |
234 | char tmp[3]; |
235 | int i; | |
ffb4a1c8 | 236 | |
622b3917 | 237 | sclp_print("s390-ccw Enumerated Boot Menu.\n\n"); |
ffb4a1c8 | 238 | |
622b3917 CW |
239 | for (i = 0; i < MAX_BOOT_ENTRIES; i++) { |
240 | if (valid_entries[i]) { | |
241 | if (i < 10) { | |
242 | sclp_print(" "); | |
243 | } | |
244 | sclp_print("["); | |
245 | sclp_print(uitoa(i, tmp, sizeof(tmp))); | |
246 | sclp_print("]"); | |
247 | if (i == 0) { | |
248 | sclp_print(" default\n"); | |
249 | } | |
250 | sclp_print("\n"); | |
251 | } | |
252 | } | |
ffb4a1c8 | 253 | |
622b3917 CW |
254 | sclp_print("\n"); |
255 | return get_boot_index(valid_entries); | |
ffb4a1c8 CW |
256 | } |
257 | ||
9eaa654a CW |
258 | void menu_set_parms(uint8_t boot_menu_flag, uint32_t boot_menu_timeout) |
259 | { | |
260 | flag = boot_menu_flag; | |
261 | timeout = boot_menu_timeout; | |
262 | } | |
ba831b25 CW |
263 | |
264 | bool menu_is_enabled_zipl(void) | |
265 | { | |
53b310ce | 266 | return flag & (QIPL_FLAG_BM_OPTS_CMD | QIPL_FLAG_BM_OPTS_ZIPL); |
ba831b25 | 267 | } |
ffb4a1c8 CW |
268 | |
269 | bool menu_is_enabled_enum(void) | |
270 | { | |
271 | return flag & QIPL_FLAG_BM_OPTS_CMD; | |
272 | } |