1 | // SPDX-License-Identifier: GPL-2.0 |
2 | #include <linux/kernel.h> |
3 | #include <linux/init.h> |
4 | #include <linux/ctype.h> |
5 | #include <linux/pgtable.h> |
6 | #include <asm/page-states.h> |
7 | #include <asm/ebcdic.h> |
8 | #include <asm/sclp.h> |
9 | #include <asm/sections.h> |
10 | #include <asm/boot_data.h> |
11 | #include <asm/facility.h> |
12 | #include <asm/setup.h> |
13 | #include <asm/uv.h> |
14 | #include "boot.h" |
15 | |
16 | struct parmarea parmarea __section(".parmarea" ) = { |
17 | .kernel_version = (unsigned long)kernel_version, |
18 | .max_command_line_size = COMMAND_LINE_SIZE, |
19 | .command_line = "root=/dev/ram0 ro" , |
20 | }; |
21 | |
22 | char __bootdata(early_command_line)[COMMAND_LINE_SIZE]; |
23 | |
24 | unsigned int __bootdata_preserved(zlib_dfltcc_support) = ZLIB_DFLTCC_FULL; |
25 | struct ipl_parameter_block __bootdata_preserved(ipl_block); |
26 | int __bootdata_preserved(ipl_block_valid); |
27 | int __bootdata_preserved(__kaslr_enabled); |
28 | int __bootdata_preserved(cmma_flag) = 1; |
29 | |
30 | unsigned long vmalloc_size = VMALLOC_DEFAULT_SIZE; |
31 | unsigned long memory_limit; |
32 | int vmalloc_size_set; |
33 | |
34 | static inline int __diag308(unsigned long subcode, void *addr) |
35 | { |
36 | unsigned long reg1, reg2; |
37 | union register_pair r1; |
38 | psw_t old; |
39 | |
40 | r1.even = (unsigned long) addr; |
41 | r1.odd = 0; |
42 | asm volatile( |
43 | " mvc 0(16,%[psw_old]),0(%[psw_pgm])\n" |
44 | " epsw %[reg1],%[reg2]\n" |
45 | " st %[reg1],0(%[psw_pgm])\n" |
46 | " st %[reg2],4(%[psw_pgm])\n" |
47 | " larl %[reg1],1f\n" |
48 | " stg %[reg1],8(%[psw_pgm])\n" |
49 | " diag %[r1],%[subcode],0x308\n" |
50 | "1: mvc 0(16,%[psw_pgm]),0(%[psw_old])\n" |
51 | : [r1] "+&d" (r1.pair), |
52 | [reg1] "=&d" (reg1), |
53 | [reg2] "=&a" (reg2), |
54 | "+Q" (S390_lowcore.program_new_psw), |
55 | "=Q" (old) |
56 | : [subcode] "d" (subcode), |
57 | [psw_old] "a" (&old), |
58 | [psw_pgm] "a" (&S390_lowcore.program_new_psw) |
59 | : "cc" , "memory" ); |
60 | return r1.odd; |
61 | } |
62 | |
63 | void store_ipl_parmblock(void) |
64 | { |
65 | int rc; |
66 | |
67 | rc = __diag308(subcode: DIAG308_STORE, addr: &ipl_block); |
68 | if (rc == DIAG308_RC_OK && |
69 | ipl_block.hdr.version <= IPL_MAX_SUPPORTED_VERSION) |
70 | ipl_block_valid = 1; |
71 | } |
72 | |
73 | bool is_ipl_block_dump(void) |
74 | { |
75 | if (ipl_block.pb0_hdr.pbt == IPL_PBT_FCP && |
76 | ipl_block.fcp.opt == IPL_PB0_FCP_OPT_DUMP) |
77 | return true; |
78 | if (ipl_block.pb0_hdr.pbt == IPL_PBT_NVME && |
79 | ipl_block.nvme.opt == IPL_PB0_NVME_OPT_DUMP) |
80 | return true; |
81 | if (ipl_block.pb0_hdr.pbt == IPL_PBT_ECKD && |
82 | ipl_block.eckd.opt == IPL_PB0_ECKD_OPT_DUMP) |
83 | return true; |
84 | return false; |
85 | } |
86 | |
87 | static size_t scpdata_length(const u8 *buf, size_t count) |
88 | { |
89 | while (count) { |
90 | if (buf[count - 1] != '\0' && buf[count - 1] != ' ') |
91 | break; |
92 | count--; |
93 | } |
94 | return count; |
95 | } |
96 | |
97 | static size_t ipl_block_get_ascii_scpdata(char *dest, size_t size, |
98 | const struct ipl_parameter_block *ipb) |
99 | { |
100 | const __u8 *scp_data; |
101 | __u32 scp_data_len; |
102 | int has_lowercase; |
103 | size_t count = 0; |
104 | size_t i; |
105 | |
106 | switch (ipb->pb0_hdr.pbt) { |
107 | case IPL_PBT_FCP: |
108 | scp_data_len = ipb->fcp.scp_data_len; |
109 | scp_data = ipb->fcp.scp_data; |
110 | break; |
111 | case IPL_PBT_NVME: |
112 | scp_data_len = ipb->nvme.scp_data_len; |
113 | scp_data = ipb->nvme.scp_data; |
114 | break; |
115 | case IPL_PBT_ECKD: |
116 | scp_data_len = ipb->eckd.scp_data_len; |
117 | scp_data = ipb->eckd.scp_data; |
118 | break; |
119 | |
120 | default: |
121 | goto out; |
122 | } |
123 | |
124 | count = min(size - 1, scpdata_length(scp_data, scp_data_len)); |
125 | if (!count) |
126 | goto out; |
127 | |
128 | has_lowercase = 0; |
129 | for (i = 0; i < count; i++) { |
130 | if (!isascii(scp_data[i])) { |
131 | count = 0; |
132 | goto out; |
133 | } |
134 | if (!has_lowercase && islower(scp_data[i])) |
135 | has_lowercase = 1; |
136 | } |
137 | |
138 | if (has_lowercase) |
139 | memcpy(dest, scp_data, count); |
140 | else |
141 | for (i = 0; i < count; i++) |
142 | dest[i] = tolower(scp_data[i]); |
143 | out: |
144 | dest[count] = '\0'; |
145 | return count; |
146 | } |
147 | |
148 | static void append_ipl_block_parm(void) |
149 | { |
150 | char *parm, *delim; |
151 | size_t len, rc = 0; |
152 | |
153 | len = strlen(early_command_line); |
154 | |
155 | delim = early_command_line + len; /* '\0' character position */ |
156 | parm = early_command_line + len + 1; /* append right after '\0' */ |
157 | |
158 | switch (ipl_block.pb0_hdr.pbt) { |
159 | case IPL_PBT_CCW: |
160 | rc = ipl_block_get_ascii_vmparm( |
161 | parm, COMMAND_LINE_SIZE - len - 1, &ipl_block); |
162 | break; |
163 | case IPL_PBT_FCP: |
164 | case IPL_PBT_NVME: |
165 | case IPL_PBT_ECKD: |
166 | rc = ipl_block_get_ascii_scpdata( |
167 | parm, COMMAND_LINE_SIZE - len - 1, &ipl_block); |
168 | break; |
169 | } |
170 | if (rc) { |
171 | if (*parm == '=') |
172 | memmove(early_command_line, parm + 1, rc); |
173 | else |
174 | *delim = ' '; /* replace '\0' with space */ |
175 | } |
176 | } |
177 | |
178 | static inline int has_ebcdic_char(const char *str) |
179 | { |
180 | int i; |
181 | |
182 | for (i = 0; str[i]; i++) |
183 | if (str[i] & 0x80) |
184 | return 1; |
185 | return 0; |
186 | } |
187 | |
188 | void setup_boot_command_line(void) |
189 | { |
190 | parmarea.command_line[COMMAND_LINE_SIZE - 1] = 0; |
191 | /* convert arch command line to ascii if necessary */ |
192 | if (has_ebcdic_char(str: parmarea.command_line)) |
193 | EBCASC(parmarea.command_line, COMMAND_LINE_SIZE); |
194 | /* copy arch command line */ |
195 | strcpy(early_command_line, strim(parmarea.command_line)); |
196 | |
197 | /* append IPL PARM data to the boot command line */ |
198 | if (!is_prot_virt_guest() && ipl_block_valid) |
199 | append_ipl_block_parm(); |
200 | } |
201 | |
202 | static void modify_facility(unsigned long nr, bool clear) |
203 | { |
204 | if (clear) |
205 | __clear_facility(nr, stfle_fac_list); |
206 | else |
207 | __set_facility(nr, stfle_fac_list); |
208 | } |
209 | |
210 | static void check_cleared_facilities(void) |
211 | { |
212 | unsigned long als[] = { FACILITIES_ALS }; |
213 | int i; |
214 | |
215 | for (i = 0; i < ARRAY_SIZE(als); i++) { |
216 | if ((stfle_fac_list[i] & als[i]) != als[i]) { |
217 | sclp_early_printk("Warning: The Linux kernel requires facilities cleared via command line option\n" ); |
218 | print_missing_facilities(); |
219 | break; |
220 | } |
221 | } |
222 | } |
223 | |
224 | static void modify_fac_list(char *str) |
225 | { |
226 | unsigned long val, endval; |
227 | char *endp; |
228 | bool clear; |
229 | |
230 | while (*str) { |
231 | clear = false; |
232 | if (*str == '!') { |
233 | clear = true; |
234 | str++; |
235 | } |
236 | val = simple_strtoull(str, &endp, 0); |
237 | if (str == endp) |
238 | break; |
239 | str = endp; |
240 | if (*str == '-') { |
241 | str++; |
242 | endval = simple_strtoull(str, &endp, 0); |
243 | if (str == endp) |
244 | break; |
245 | str = endp; |
246 | while (val <= endval) { |
247 | modify_facility(nr: val, clear); |
248 | val++; |
249 | } |
250 | } else { |
251 | modify_facility(nr: val, clear); |
252 | } |
253 | if (*str != ',') |
254 | break; |
255 | str++; |
256 | } |
257 | check_cleared_facilities(); |
258 | } |
259 | |
260 | static char command_line_buf[COMMAND_LINE_SIZE]; |
261 | void parse_boot_command_line(void) |
262 | { |
263 | char *param, *val; |
264 | bool enabled; |
265 | char *args; |
266 | int rc; |
267 | |
268 | __kaslr_enabled = IS_ENABLED(CONFIG_RANDOMIZE_BASE); |
269 | args = strcpy(command_line_buf, early_command_line); |
270 | while (*args) { |
271 | args = next_arg(args, param: ¶m, val: &val); |
272 | |
273 | if (!strcmp(param, "mem" ) && val) |
274 | memory_limit = round_down(memparse(val, NULL), PAGE_SIZE); |
275 | |
276 | if (!strcmp(param, "vmalloc" ) && val) { |
277 | vmalloc_size = round_up(memparse(val, NULL), _SEGMENT_SIZE); |
278 | vmalloc_size_set = 1; |
279 | } |
280 | |
281 | if (!strcmp(param, "dfltcc" ) && val) { |
282 | if (!strcmp(val, "off" )) |
283 | zlib_dfltcc_support = ZLIB_DFLTCC_DISABLED; |
284 | else if (!strcmp(val, "on" )) |
285 | zlib_dfltcc_support = ZLIB_DFLTCC_FULL; |
286 | else if (!strcmp(val, "def_only" )) |
287 | zlib_dfltcc_support = ZLIB_DFLTCC_DEFLATE_ONLY; |
288 | else if (!strcmp(val, "inf_only" )) |
289 | zlib_dfltcc_support = ZLIB_DFLTCC_INFLATE_ONLY; |
290 | else if (!strcmp(val, "always" )) |
291 | zlib_dfltcc_support = ZLIB_DFLTCC_FULL_DEBUG; |
292 | } |
293 | |
294 | if (!strcmp(param, "facilities" ) && val) |
295 | modify_fac_list(str: val); |
296 | |
297 | if (!strcmp(param, "nokaslr" )) |
298 | __kaslr_enabled = 0; |
299 | |
300 | if (!strcmp(param, "cmma" )) { |
301 | rc = kstrtobool(s: val, res: &enabled); |
302 | if (!rc && !enabled) |
303 | cmma_flag = 0; |
304 | } |
305 | |
306 | #if IS_ENABLED(CONFIG_KVM) |
307 | if (!strcmp(param, "prot_virt" )) { |
308 | rc = kstrtobool(s: val, res: &enabled); |
309 | if (!rc && enabled) |
310 | prot_virt_host = 1; |
311 | } |
312 | #endif |
313 | } |
314 | } |
315 | |