]>
Commit | Line | Data |
---|---|---|
85fcde40 BH |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* | |
3 | * crash.c - kernel crash support code. | |
4 | * Copyright (C) 2002-2004 Eric Biederman <[email protected]> | |
5 | */ | |
6 | ||
7 | #include <linux/buildid.h> | |
8 | #include <linux/init.h> | |
9 | #include <linux/utsname.h> | |
10 | #include <linux/vmalloc.h> | |
11 | #include <linux/sizes.h> | |
12 | #include <linux/kexec.h> | |
13 | #include <linux/memory.h> | |
14 | #include <linux/cpuhotplug.h> | |
15 | #include <linux/memblock.h> | |
16 | #include <linux/kexec.h> | |
17 | #include <linux/kmemleak.h> | |
18 | ||
19 | #include <asm/page.h> | |
20 | #include <asm/sections.h> | |
21 | ||
22 | #include <crypto/sha1.h> | |
23 | ||
24 | #include "kallsyms_internal.h" | |
25 | #include "kexec_internal.h" | |
26 | ||
27 | /* Location of the reserved area for the crash kernel */ | |
28 | struct resource crashk_res = { | |
29 | .name = "Crash kernel", | |
30 | .start = 0, | |
31 | .end = 0, | |
32 | .flags = IORESOURCE_BUSY | IORESOURCE_SYSTEM_RAM, | |
33 | .desc = IORES_DESC_CRASH_KERNEL | |
34 | }; | |
35 | struct resource crashk_low_res = { | |
36 | .name = "Crash kernel", | |
37 | .start = 0, | |
38 | .end = 0, | |
39 | .flags = IORESOURCE_BUSY | IORESOURCE_SYSTEM_RAM, | |
40 | .desc = IORES_DESC_CRASH_KERNEL | |
41 | }; | |
42 | ||
43 | /* | |
44 | * parsing the "crashkernel" commandline | |
45 | * | |
46 | * this code is intended to be called from architecture specific code | |
47 | */ | |
48 | ||
49 | ||
50 | /* | |
51 | * This function parses command lines in the format | |
52 | * | |
53 | * crashkernel=ramsize-range:size[,...][@offset] | |
54 | * | |
55 | * The function returns 0 on success and -EINVAL on failure. | |
56 | */ | |
57 | static int __init parse_crashkernel_mem(char *cmdline, | |
58 | unsigned long long system_ram, | |
59 | unsigned long long *crash_size, | |
60 | unsigned long long *crash_base) | |
61 | { | |
62 | char *cur = cmdline, *tmp; | |
63 | unsigned long long total_mem = system_ram; | |
64 | ||
65 | /* | |
66 | * Firmware sometimes reserves some memory regions for its own use, | |
67 | * so the system memory size is less than the actual physical memory | |
68 | * size. Work around this by rounding up the total size to 128M, | |
69 | * which is enough for most test cases. | |
70 | */ | |
71 | total_mem = roundup(total_mem, SZ_128M); | |
72 | ||
73 | /* for each entry of the comma-separated list */ | |
74 | do { | |
75 | unsigned long long start, end = ULLONG_MAX, size; | |
76 | ||
77 | /* get the start of the range */ | |
78 | start = memparse(cur, &tmp); | |
79 | if (cur == tmp) { | |
80 | pr_warn("crashkernel: Memory value expected\n"); | |
81 | return -EINVAL; | |
82 | } | |
83 | cur = tmp; | |
84 | if (*cur != '-') { | |
85 | pr_warn("crashkernel: '-' expected\n"); | |
86 | return -EINVAL; | |
87 | } | |
88 | cur++; | |
89 | ||
90 | /* if no ':' is here, than we read the end */ | |
91 | if (*cur != ':') { | |
92 | end = memparse(cur, &tmp); | |
93 | if (cur == tmp) { | |
94 | pr_warn("crashkernel: Memory value expected\n"); | |
95 | return -EINVAL; | |
96 | } | |
97 | cur = tmp; | |
98 | if (end <= start) { | |
99 | pr_warn("crashkernel: end <= start\n"); | |
100 | return -EINVAL; | |
101 | } | |
102 | } | |
103 | ||
104 | if (*cur != ':') { | |
105 | pr_warn("crashkernel: ':' expected\n"); | |
106 | return -EINVAL; | |
107 | } | |
108 | cur++; | |
109 | ||
110 | size = memparse(cur, &tmp); | |
111 | if (cur == tmp) { | |
4707c13d | 112 | pr_warn("crashkernel: Memory value expected\n"); |
85fcde40 BH |
113 | return -EINVAL; |
114 | } | |
115 | cur = tmp; | |
116 | if (size >= total_mem) { | |
117 | pr_warn("crashkernel: invalid size\n"); | |
118 | return -EINVAL; | |
119 | } | |
120 | ||
121 | /* match ? */ | |
122 | if (total_mem >= start && total_mem < end) { | |
123 | *crash_size = size; | |
124 | break; | |
125 | } | |
126 | } while (*cur++ == ','); | |
127 | ||
128 | if (*crash_size > 0) { | |
129 | while (*cur && *cur != ' ' && *cur != '@') | |
130 | cur++; | |
131 | if (*cur == '@') { | |
132 | cur++; | |
133 | *crash_base = memparse(cur, &tmp); | |
134 | if (cur == tmp) { | |
4707c13d | 135 | pr_warn("crahskernel: Memory value expected after '@'\n"); |
85fcde40 BH |
136 | return -EINVAL; |
137 | } | |
138 | } | |
139 | } else | |
140 | pr_info("crashkernel size resulted in zero bytes\n"); | |
141 | ||
142 | return 0; | |
143 | } | |
144 | ||
145 | /* | |
146 | * That function parses "simple" (old) crashkernel command lines like | |
147 | * | |
148 | * crashkernel=size[@offset] | |
149 | * | |
150 | * It returns 0 on success and -EINVAL on failure. | |
151 | */ | |
152 | static int __init parse_crashkernel_simple(char *cmdline, | |
153 | unsigned long long *crash_size, | |
154 | unsigned long long *crash_base) | |
155 | { | |
156 | char *cur = cmdline; | |
157 | ||
158 | *crash_size = memparse(cmdline, &cur); | |
159 | if (cmdline == cur) { | |
160 | pr_warn("crashkernel: memory value expected\n"); | |
161 | return -EINVAL; | |
162 | } | |
163 | ||
164 | if (*cur == '@') | |
165 | *crash_base = memparse(cur+1, &cur); | |
166 | else if (*cur != ' ' && *cur != '\0') { | |
167 | pr_warn("crashkernel: unrecognized char: %c\n", *cur); | |
168 | return -EINVAL; | |
169 | } | |
170 | ||
171 | return 0; | |
172 | } | |
173 | ||
174 | #define SUFFIX_HIGH 0 | |
175 | #define SUFFIX_LOW 1 | |
176 | #define SUFFIX_NULL 2 | |
177 | static __initdata char *suffix_tbl[] = { | |
178 | [SUFFIX_HIGH] = ",high", | |
179 | [SUFFIX_LOW] = ",low", | |
180 | [SUFFIX_NULL] = NULL, | |
181 | }; | |
182 | ||
183 | /* | |
184 | * That function parses "suffix" crashkernel command lines like | |
185 | * | |
186 | * crashkernel=size,[high|low] | |
187 | * | |
188 | * It returns 0 on success and -EINVAL on failure. | |
189 | */ | |
190 | static int __init parse_crashkernel_suffix(char *cmdline, | |
191 | unsigned long long *crash_size, | |
192 | const char *suffix) | |
193 | { | |
194 | char *cur = cmdline; | |
195 | ||
196 | *crash_size = memparse(cmdline, &cur); | |
197 | if (cmdline == cur) { | |
198 | pr_warn("crashkernel: memory value expected\n"); | |
199 | return -EINVAL; | |
200 | } | |
201 | ||
202 | /* check with suffix */ | |
203 | if (strncmp(cur, suffix, strlen(suffix))) { | |
204 | pr_warn("crashkernel: unrecognized char: %c\n", *cur); | |
205 | return -EINVAL; | |
206 | } | |
207 | cur += strlen(suffix); | |
208 | if (*cur != ' ' && *cur != '\0') { | |
209 | pr_warn("crashkernel: unrecognized char: %c\n", *cur); | |
210 | return -EINVAL; | |
211 | } | |
212 | ||
213 | return 0; | |
214 | } | |
215 | ||
216 | static __init char *get_last_crashkernel(char *cmdline, | |
217 | const char *name, | |
218 | const char *suffix) | |
219 | { | |
220 | char *p = cmdline, *ck_cmdline = NULL; | |
221 | ||
222 | /* find crashkernel and use the last one if there are more */ | |
223 | p = strstr(p, name); | |
224 | while (p) { | |
225 | char *end_p = strchr(p, ' '); | |
226 | char *q; | |
227 | ||
228 | if (!end_p) | |
229 | end_p = p + strlen(p); | |
230 | ||
231 | if (!suffix) { | |
232 | int i; | |
233 | ||
234 | /* skip the one with any known suffix */ | |
235 | for (i = 0; suffix_tbl[i]; i++) { | |
236 | q = end_p - strlen(suffix_tbl[i]); | |
237 | if (!strncmp(q, suffix_tbl[i], | |
238 | strlen(suffix_tbl[i]))) | |
239 | goto next; | |
240 | } | |
241 | ck_cmdline = p; | |
242 | } else { | |
243 | q = end_p - strlen(suffix); | |
244 | if (!strncmp(q, suffix, strlen(suffix))) | |
245 | ck_cmdline = p; | |
246 | } | |
247 | next: | |
248 | p = strstr(p+1, name); | |
249 | } | |
250 | ||
251 | return ck_cmdline; | |
252 | } | |
253 | ||
254 | static int __init __parse_crashkernel(char *cmdline, | |
255 | unsigned long long system_ram, | |
256 | unsigned long long *crash_size, | |
257 | unsigned long long *crash_base, | |
258 | const char *suffix) | |
259 | { | |
260 | char *first_colon, *first_space; | |
261 | char *ck_cmdline; | |
262 | char *name = "crashkernel="; | |
263 | ||
264 | BUG_ON(!crash_size || !crash_base); | |
265 | *crash_size = 0; | |
266 | *crash_base = 0; | |
267 | ||
268 | ck_cmdline = get_last_crashkernel(cmdline, name, suffix); | |
269 | if (!ck_cmdline) | |
270 | return -ENOENT; | |
271 | ||
272 | ck_cmdline += strlen(name); | |
273 | ||
274 | if (suffix) | |
275 | return parse_crashkernel_suffix(ck_cmdline, crash_size, | |
276 | suffix); | |
277 | /* | |
278 | * if the commandline contains a ':', then that's the extended | |
279 | * syntax -- if not, it must be the classic syntax | |
280 | */ | |
281 | first_colon = strchr(ck_cmdline, ':'); | |
282 | first_space = strchr(ck_cmdline, ' '); | |
283 | if (first_colon && (!first_space || first_colon < first_space)) | |
284 | return parse_crashkernel_mem(ck_cmdline, system_ram, | |
285 | crash_size, crash_base); | |
286 | ||
287 | return parse_crashkernel_simple(ck_cmdline, crash_size, crash_base); | |
288 | } | |
289 | ||
290 | /* | |
291 | * That function is the entry point for command line parsing and should be | |
292 | * called from the arch-specific code. | |
293 | * | |
294 | * If crashkernel=,high|low is supported on architecture, non-NULL values | |
295 | * should be passed to parameters 'low_size' and 'high'. | |
296 | */ | |
297 | int __init parse_crashkernel(char *cmdline, | |
298 | unsigned long long system_ram, | |
299 | unsigned long long *crash_size, | |
300 | unsigned long long *crash_base, | |
301 | unsigned long long *low_size, | |
302 | bool *high) | |
303 | { | |
304 | int ret; | |
305 | ||
306 | /* crashkernel=X[@offset] */ | |
307 | ret = __parse_crashkernel(cmdline, system_ram, crash_size, | |
308 | crash_base, NULL); | |
309 | #ifdef CONFIG_ARCH_HAS_GENERIC_CRASHKERNEL_RESERVATION | |
310 | /* | |
311 | * If non-NULL 'high' passed in and no normal crashkernel | |
312 | * setting detected, try parsing crashkernel=,high|low. | |
313 | */ | |
314 | if (high && ret == -ENOENT) { | |
315 | ret = __parse_crashkernel(cmdline, 0, crash_size, | |
316 | crash_base, suffix_tbl[SUFFIX_HIGH]); | |
317 | if (ret || !*crash_size) | |
318 | return -EINVAL; | |
319 | ||
320 | /* | |
321 | * crashkernel=Y,low can be specified or not, but invalid value | |
322 | * is not allowed. | |
323 | */ | |
324 | ret = __parse_crashkernel(cmdline, 0, low_size, | |
325 | crash_base, suffix_tbl[SUFFIX_LOW]); | |
326 | if (ret == -ENOENT) { | |
327 | *low_size = DEFAULT_CRASH_KERNEL_LOW_SIZE; | |
328 | ret = 0; | |
329 | } else if (ret) { | |
330 | return ret; | |
331 | } | |
332 | ||
333 | *high = true; | |
334 | } | |
335 | #endif | |
336 | if (!*crash_size) | |
337 | ret = -EINVAL; | |
338 | ||
339 | return ret; | |
340 | } | |
341 | ||
342 | /* | |
343 | * Add a dummy early_param handler to mark crashkernel= as a known command line | |
344 | * parameter and suppress incorrect warnings in init/main.c. | |
345 | */ | |
346 | static int __init parse_crashkernel_dummy(char *arg) | |
347 | { | |
348 | return 0; | |
349 | } | |
350 | early_param("crashkernel", parse_crashkernel_dummy); | |
351 | ||
352 | #ifdef CONFIG_ARCH_HAS_GENERIC_CRASHKERNEL_RESERVATION | |
353 | static int __init reserve_crashkernel_low(unsigned long long low_size) | |
354 | { | |
355 | #ifdef CONFIG_64BIT | |
356 | unsigned long long low_base; | |
357 | ||
358 | low_base = memblock_phys_alloc_range(low_size, CRASH_ALIGN, 0, CRASH_ADDR_LOW_MAX); | |
359 | if (!low_base) { | |
360 | pr_err("cannot allocate crashkernel low memory (size:0x%llx).\n", low_size); | |
361 | return -ENOMEM; | |
362 | } | |
363 | ||
364 | pr_info("crashkernel low memory reserved: 0x%08llx - 0x%08llx (%lld MB)\n", | |
365 | low_base, low_base + low_size, low_size >> 20); | |
366 | ||
367 | crashk_low_res.start = low_base; | |
368 | crashk_low_res.end = low_base + low_size - 1; | |
32fbe524 | 369 | #ifdef HAVE_ARCH_ADD_CRASH_RES_TO_IOMEM_EARLY |
85fcde40 | 370 | insert_resource(&iomem_resource, &crashk_low_res); |
32fbe524 | 371 | #endif |
85fcde40 BH |
372 | #endif |
373 | return 0; | |
374 | } | |
375 | ||
376 | void __init reserve_crashkernel_generic(char *cmdline, | |
377 | unsigned long long crash_size, | |
378 | unsigned long long crash_base, | |
379 | unsigned long long crash_low_size, | |
380 | bool high) | |
381 | { | |
382 | unsigned long long search_end = CRASH_ADDR_LOW_MAX, search_base = 0; | |
383 | bool fixed_base = false; | |
384 | ||
385 | /* User specifies base address explicitly. */ | |
386 | if (crash_base) { | |
387 | fixed_base = true; | |
388 | search_base = crash_base; | |
389 | search_end = crash_base + crash_size; | |
390 | } else if (high) { | |
391 | search_base = CRASH_ADDR_LOW_MAX; | |
392 | search_end = CRASH_ADDR_HIGH_MAX; | |
393 | } | |
394 | ||
395 | retry: | |
396 | crash_base = memblock_phys_alloc_range(crash_size, CRASH_ALIGN, | |
397 | search_base, search_end); | |
398 | if (!crash_base) { | |
399 | /* | |
400 | * For crashkernel=size[KMG]@offset[KMG], print out failure | |
401 | * message if can't reserve the specified region. | |
402 | */ | |
403 | if (fixed_base) { | |
404 | pr_warn("crashkernel reservation failed - memory is in use.\n"); | |
405 | return; | |
406 | } | |
407 | ||
408 | /* | |
409 | * For crashkernel=size[KMG], if the first attempt was for | |
410 | * low memory, fall back to high memory, the minimum required | |
411 | * low memory will be reserved later. | |
412 | */ | |
413 | if (!high && search_end == CRASH_ADDR_LOW_MAX) { | |
414 | search_end = CRASH_ADDR_HIGH_MAX; | |
415 | search_base = CRASH_ADDR_LOW_MAX; | |
416 | crash_low_size = DEFAULT_CRASH_KERNEL_LOW_SIZE; | |
417 | goto retry; | |
418 | } | |
419 | ||
420 | /* | |
421 | * For crashkernel=size[KMG],high, if the first attempt was | |
422 | * for high memory, fall back to low memory. | |
423 | */ | |
424 | if (high && search_end == CRASH_ADDR_HIGH_MAX) { | |
425 | search_end = CRASH_ADDR_LOW_MAX; | |
426 | search_base = 0; | |
427 | goto retry; | |
428 | } | |
429 | pr_warn("cannot allocate crashkernel (size:0x%llx)\n", | |
430 | crash_size); | |
431 | return; | |
432 | } | |
433 | ||
434 | if ((crash_base >= CRASH_ADDR_LOW_MAX) && | |
435 | crash_low_size && reserve_crashkernel_low(crash_low_size)) { | |
436 | memblock_phys_free(crash_base, crash_size); | |
437 | return; | |
438 | } | |
439 | ||
440 | pr_info("crashkernel reserved: 0x%016llx - 0x%016llx (%lld MB)\n", | |
441 | crash_base, crash_base + crash_size, crash_size >> 20); | |
442 | ||
443 | /* | |
444 | * The crashkernel memory will be removed from the kernel linear | |
445 | * map. Inform kmemleak so that it won't try to access it. | |
446 | */ | |
447 | kmemleak_ignore_phys(crash_base); | |
448 | if (crashk_low_res.end) | |
449 | kmemleak_ignore_phys(crashk_low_res.start); | |
450 | ||
451 | crashk_res.start = crash_base; | |
452 | crashk_res.end = crash_base + crash_size - 1; | |
32fbe524 BH |
453 | #ifdef HAVE_ARCH_ADD_CRASH_RES_TO_IOMEM_EARLY |
454 | insert_resource(&iomem_resource, &crashk_res); | |
455 | #endif | |
85fcde40 BH |
456 | } |
457 | ||
32fbe524 | 458 | #ifndef HAVE_ARCH_ADD_CRASH_RES_TO_IOMEM_EARLY |
85fcde40 BH |
459 | static __init int insert_crashkernel_resources(void) |
460 | { | |
461 | if (crashk_res.start < crashk_res.end) | |
462 | insert_resource(&iomem_resource, &crashk_res); | |
463 | ||
464 | if (crashk_low_res.start < crashk_low_res.end) | |
465 | insert_resource(&iomem_resource, &crashk_low_res); | |
466 | ||
467 | return 0; | |
468 | } | |
469 | early_initcall(insert_crashkernel_resources); | |
470 | #endif | |
32fbe524 | 471 | #endif |