Merge tag 'for-linus-5.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/rw...
[linux-2.6-microblaze.git] / drivers / of / kexec.c
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright (C) 2020 Arm Limited
4  *
5  * Based on arch/arm64/kernel/machine_kexec_file.c:
6  *  Copyright (C) 2018 Linaro Limited
7  *
8  * And arch/powerpc/kexec/file_load.c:
9  *  Copyright (C) 2016  IBM Corporation
10  */
11
12 #include <linux/kernel.h>
13 #include <linux/kexec.h>
14 #include <linux/memblock.h>
15 #include <linux/libfdt.h>
16 #include <linux/of.h>
17 #include <linux/of_fdt.h>
18 #include <linux/random.h>
19 #include <linux/types.h>
20
21 /* relevant device tree properties */
22 #define FDT_PROP_KEXEC_ELFHDR   "linux,elfcorehdr"
23 #define FDT_PROP_MEM_RANGE      "linux,usable-memory-range"
24 #define FDT_PROP_INITRD_START   "linux,initrd-start"
25 #define FDT_PROP_INITRD_END     "linux,initrd-end"
26 #define FDT_PROP_BOOTARGS       "bootargs"
27 #define FDT_PROP_KASLR_SEED     "kaslr-seed"
28 #define FDT_PROP_RNG_SEED       "rng-seed"
29 #define RNG_SEED_SIZE           128
30
31 /*
32  * Additional space needed for the FDT buffer so that we can add initrd,
33  * bootargs, kaslr-seed, rng-seed, useable-memory-range and elfcorehdr.
34  */
35 #define FDT_EXTRA_SPACE 0x1000
36
37 /**
38  * fdt_find_and_del_mem_rsv - delete memory reservation with given address and size
39  *
40  * @fdt:        Flattened device tree for the current kernel.
41  * @start:      Starting address of the reserved memory.
42  * @size:       Size of the reserved memory.
43  *
44  * Return: 0 on success, or negative errno on error.
45  */
46 static int fdt_find_and_del_mem_rsv(void *fdt, unsigned long start, unsigned long size)
47 {
48         int i, ret, num_rsvs = fdt_num_mem_rsv(fdt);
49
50         for (i = 0; i < num_rsvs; i++) {
51                 u64 rsv_start, rsv_size;
52
53                 ret = fdt_get_mem_rsv(fdt, i, &rsv_start, &rsv_size);
54                 if (ret) {
55                         pr_err("Malformed device tree.\n");
56                         return -EINVAL;
57                 }
58
59                 if (rsv_start == start && rsv_size == size) {
60                         ret = fdt_del_mem_rsv(fdt, i);
61                         if (ret) {
62                                 pr_err("Error deleting device tree reservation.\n");
63                                 return -EINVAL;
64                         }
65
66                         return 0;
67                 }
68         }
69
70         return -ENOENT;
71 }
72
73 /**
74  * get_addr_size_cells - Get address and size of root node
75  *
76  * @addr_cells: Return address of the root node
77  * @size_cells: Return size of the root node
78  *
79  * Return: 0 on success, or negative errno on error.
80  */
81 static int get_addr_size_cells(int *addr_cells, int *size_cells)
82 {
83         struct device_node *root;
84
85         root = of_find_node_by_path("/");
86         if (!root)
87                 return -EINVAL;
88
89         *addr_cells = of_n_addr_cells(root);
90         *size_cells = of_n_size_cells(root);
91
92         of_node_put(root);
93
94         return 0;
95 }
96
97 /**
98  * do_get_kexec_buffer - Get address and size of device tree property
99  *
100  * @prop: Device tree property
101  * @len: Size of @prop
102  * @addr: Return address of the node
103  * @size: Return size of the node
104  *
105  * Return: 0 on success, or negative errno on error.
106  */
107 static int do_get_kexec_buffer(const void *prop, int len, unsigned long *addr,
108                                size_t *size)
109 {
110         int ret, addr_cells, size_cells;
111
112         ret = get_addr_size_cells(&addr_cells, &size_cells);
113         if (ret)
114                 return ret;
115
116         if (len < 4 * (addr_cells + size_cells))
117                 return -ENOENT;
118
119         *addr = of_read_number(prop, addr_cells);
120         *size = of_read_number(prop + 4 * addr_cells, size_cells);
121
122         return 0;
123 }
124
125 /**
126  * ima_get_kexec_buffer - get IMA buffer from the previous kernel
127  * @addr:       On successful return, set to point to the buffer contents.
128  * @size:       On successful return, set to the buffer size.
129  *
130  * Return: 0 on success, negative errno on error.
131  */
132 int ima_get_kexec_buffer(void **addr, size_t *size)
133 {
134         int ret, len;
135         unsigned long tmp_addr;
136         size_t tmp_size;
137         const void *prop;
138
139         if (!IS_ENABLED(CONFIG_HAVE_IMA_KEXEC))
140                 return -ENOTSUPP;
141
142         prop = of_get_property(of_chosen, "linux,ima-kexec-buffer", &len);
143         if (!prop)
144                 return -ENOENT;
145
146         ret = do_get_kexec_buffer(prop, len, &tmp_addr, &tmp_size);
147         if (ret)
148                 return ret;
149
150         *addr = __va(tmp_addr);
151         *size = tmp_size;
152
153         return 0;
154 }
155
156 /**
157  * ima_free_kexec_buffer - free memory used by the IMA buffer
158  */
159 int ima_free_kexec_buffer(void)
160 {
161         int ret;
162         unsigned long addr;
163         size_t size;
164         struct property *prop;
165
166         if (!IS_ENABLED(CONFIG_HAVE_IMA_KEXEC))
167                 return -ENOTSUPP;
168
169         prop = of_find_property(of_chosen, "linux,ima-kexec-buffer", NULL);
170         if (!prop)
171                 return -ENOENT;
172
173         ret = do_get_kexec_buffer(prop->value, prop->length, &addr, &size);
174         if (ret)
175                 return ret;
176
177         ret = of_remove_property(of_chosen, prop);
178         if (ret)
179                 return ret;
180
181         return memblock_free(addr, size);
182
183 }
184
185 /**
186  * remove_ima_buffer - remove the IMA buffer property and reservation from @fdt
187  *
188  * @fdt: Flattened Device Tree to update
189  * @chosen_node: Offset to the chosen node in the device tree
190  *
191  * The IMA measurement buffer is of no use to a subsequent kernel, so we always
192  * remove it from the device tree.
193  */
194 static void remove_ima_buffer(void *fdt, int chosen_node)
195 {
196         int ret, len;
197         unsigned long addr;
198         size_t size;
199         const void *prop;
200
201         if (!IS_ENABLED(CONFIG_HAVE_IMA_KEXEC))
202                 return;
203
204         prop = fdt_getprop(fdt, chosen_node, "linux,ima-kexec-buffer", &len);
205         if (!prop)
206                 return;
207
208         ret = do_get_kexec_buffer(prop, len, &addr, &size);
209         fdt_delprop(fdt, chosen_node, "linux,ima-kexec-buffer");
210         if (ret)
211                 return;
212
213         ret = fdt_find_and_del_mem_rsv(fdt, addr, size);
214         if (!ret)
215                 pr_debug("Removed old IMA buffer reservation.\n");
216 }
217
218 #ifdef CONFIG_IMA_KEXEC
219 /**
220  * setup_ima_buffer - add IMA buffer information to the fdt
221  * @image:              kexec image being loaded.
222  * @fdt:                Flattened device tree for the next kernel.
223  * @chosen_node:        Offset to the chosen node.
224  *
225  * Return: 0 on success, or negative errno on error.
226  */
227 static int setup_ima_buffer(const struct kimage *image, void *fdt,
228                             int chosen_node)
229 {
230         int ret;
231
232         if (!image->ima_buffer_size)
233                 return 0;
234
235         ret = fdt_appendprop_addrrange(fdt, 0, chosen_node,
236                                        "linux,ima-kexec-buffer",
237                                        image->ima_buffer_addr,
238                                        image->ima_buffer_size);
239         if (ret < 0)
240                 return -EINVAL;
241
242         ret = fdt_add_mem_rsv(fdt, image->ima_buffer_addr,
243                               image->ima_buffer_size);
244         if (ret)
245                 return -EINVAL;
246
247         pr_debug("IMA buffer at 0x%llx, size = 0x%zx\n",
248                  image->ima_buffer_addr, image->ima_buffer_size);
249
250         return 0;
251 }
252 #else /* CONFIG_IMA_KEXEC */
253 static inline int setup_ima_buffer(const struct kimage *image, void *fdt,
254                                    int chosen_node)
255 {
256         return 0;
257 }
258 #endif /* CONFIG_IMA_KEXEC */
259
260 /*
261  * of_kexec_alloc_and_setup_fdt - Alloc and setup a new Flattened Device Tree
262  *
263  * @image:              kexec image being loaded.
264  * @initrd_load_addr:   Address where the next initrd will be loaded.
265  * @initrd_len:         Size of the next initrd, or 0 if there will be none.
266  * @cmdline:            Command line for the next kernel, or NULL if there will
267  *                      be none.
268  * @extra_fdt_size:     Additional size for the new FDT buffer.
269  *
270  * Return: fdt on success, or NULL errno on error.
271  */
272 void *of_kexec_alloc_and_setup_fdt(const struct kimage *image,
273                                    unsigned long initrd_load_addr,
274                                    unsigned long initrd_len,
275                                    const char *cmdline, size_t extra_fdt_size)
276 {
277         void *fdt;
278         int ret, chosen_node;
279         const void *prop;
280         size_t fdt_size;
281
282         fdt_size = fdt_totalsize(initial_boot_params) +
283                    (cmdline ? strlen(cmdline) : 0) +
284                    FDT_EXTRA_SPACE +
285                    extra_fdt_size;
286         fdt = kvmalloc(fdt_size, GFP_KERNEL);
287         if (!fdt)
288                 return NULL;
289
290         ret = fdt_open_into(initial_boot_params, fdt, fdt_size);
291         if (ret < 0) {
292                 pr_err("Error %d setting up the new device tree.\n", ret);
293                 goto out;
294         }
295
296         /* Remove memory reservation for the current device tree. */
297         ret = fdt_find_and_del_mem_rsv(fdt, __pa(initial_boot_params),
298                                        fdt_totalsize(initial_boot_params));
299         if (ret == -EINVAL) {
300                 pr_err("Error removing memory reservation.\n");
301                 goto out;
302         }
303
304         chosen_node = fdt_path_offset(fdt, "/chosen");
305         if (chosen_node == -FDT_ERR_NOTFOUND)
306                 chosen_node = fdt_add_subnode(fdt, fdt_path_offset(fdt, "/"),
307                                               "chosen");
308         if (chosen_node < 0) {
309                 ret = chosen_node;
310                 goto out;
311         }
312
313         ret = fdt_delprop(fdt, chosen_node, FDT_PROP_KEXEC_ELFHDR);
314         if (ret && ret != -FDT_ERR_NOTFOUND)
315                 goto out;
316         ret = fdt_delprop(fdt, chosen_node, FDT_PROP_MEM_RANGE);
317         if (ret && ret != -FDT_ERR_NOTFOUND)
318                 goto out;
319
320         /* Did we boot using an initrd? */
321         prop = fdt_getprop(fdt, chosen_node, "linux,initrd-start", NULL);
322         if (prop) {
323                 u64 tmp_start, tmp_end, tmp_size;
324
325                 tmp_start = fdt64_to_cpu(*((const fdt64_t *) prop));
326
327                 prop = fdt_getprop(fdt, chosen_node, "linux,initrd-end", NULL);
328                 if (!prop) {
329                         ret = -EINVAL;
330                         goto out;
331                 }
332
333                 tmp_end = fdt64_to_cpu(*((const fdt64_t *) prop));
334
335                 /*
336                  * kexec reserves exact initrd size, while firmware may
337                  * reserve a multiple of PAGE_SIZE, so check for both.
338                  */
339                 tmp_size = tmp_end - tmp_start;
340                 ret = fdt_find_and_del_mem_rsv(fdt, tmp_start, tmp_size);
341                 if (ret == -ENOENT)
342                         ret = fdt_find_and_del_mem_rsv(fdt, tmp_start,
343                                                        round_up(tmp_size, PAGE_SIZE));
344                 if (ret == -EINVAL)
345                         goto out;
346         }
347
348         /* add initrd-* */
349         if (initrd_load_addr) {
350                 ret = fdt_setprop_u64(fdt, chosen_node, FDT_PROP_INITRD_START,
351                                       initrd_load_addr);
352                 if (ret)
353                         goto out;
354
355                 ret = fdt_setprop_u64(fdt, chosen_node, FDT_PROP_INITRD_END,
356                                       initrd_load_addr + initrd_len);
357                 if (ret)
358                         goto out;
359
360                 ret = fdt_add_mem_rsv(fdt, initrd_load_addr, initrd_len);
361                 if (ret)
362                         goto out;
363
364         } else {
365                 ret = fdt_delprop(fdt, chosen_node, FDT_PROP_INITRD_START);
366                 if (ret && (ret != -FDT_ERR_NOTFOUND))
367                         goto out;
368
369                 ret = fdt_delprop(fdt, chosen_node, FDT_PROP_INITRD_END);
370                 if (ret && (ret != -FDT_ERR_NOTFOUND))
371                         goto out;
372         }
373
374         if (image->type == KEXEC_TYPE_CRASH) {
375                 /* add linux,elfcorehdr */
376                 ret = fdt_appendprop_addrrange(fdt, 0, chosen_node,
377                                 FDT_PROP_KEXEC_ELFHDR,
378                                 image->elf_load_addr,
379                                 image->elf_headers_sz);
380                 if (ret)
381                         goto out;
382
383                 /*
384                  * Avoid elfcorehdr from being stomped on in kdump kernel by
385                  * setting up memory reserve map.
386                  */
387                 ret = fdt_add_mem_rsv(fdt, image->elf_load_addr,
388                                       image->elf_headers_sz);
389                 if (ret)
390                         goto out;
391
392                 /* add linux,usable-memory-range */
393                 ret = fdt_appendprop_addrrange(fdt, 0, chosen_node,
394                                 FDT_PROP_MEM_RANGE,
395                                 crashk_res.start,
396                                 crashk_res.end - crashk_res.start + 1);
397                 if (ret)
398                         goto out;
399         }
400
401         /* add bootargs */
402         if (cmdline) {
403                 ret = fdt_setprop_string(fdt, chosen_node, FDT_PROP_BOOTARGS, cmdline);
404                 if (ret)
405                         goto out;
406         } else {
407                 ret = fdt_delprop(fdt, chosen_node, FDT_PROP_BOOTARGS);
408                 if (ret && (ret != -FDT_ERR_NOTFOUND))
409                         goto out;
410         }
411
412         /* add kaslr-seed */
413         ret = fdt_delprop(fdt, chosen_node, FDT_PROP_KASLR_SEED);
414         if (ret == -FDT_ERR_NOTFOUND)
415                 ret = 0;
416         else if (ret)
417                 goto out;
418
419         if (rng_is_initialized()) {
420                 u64 seed = get_random_u64();
421
422                 ret = fdt_setprop_u64(fdt, chosen_node, FDT_PROP_KASLR_SEED, seed);
423                 if (ret)
424                         goto out;
425         } else {
426                 pr_notice("RNG is not initialised: omitting \"%s\" property\n",
427                                 FDT_PROP_KASLR_SEED);
428         }
429
430         /* add rng-seed */
431         if (rng_is_initialized()) {
432                 void *rng_seed;
433
434                 ret = fdt_setprop_placeholder(fdt, chosen_node, FDT_PROP_RNG_SEED,
435                                 RNG_SEED_SIZE, &rng_seed);
436                 if (ret)
437                         goto out;
438                 get_random_bytes(rng_seed, RNG_SEED_SIZE);
439         } else {
440                 pr_notice("RNG is not initialised: omitting \"%s\" property\n",
441                                 FDT_PROP_RNG_SEED);
442         }
443
444         ret = fdt_setprop(fdt, chosen_node, "linux,booted-from-kexec", NULL, 0);
445         if (ret)
446                 goto out;
447
448         remove_ima_buffer(fdt, chosen_node);
449         ret = setup_ima_buffer(image, fdt, fdt_path_offset(fdt, "/chosen"));
450
451 out:
452         if (ret) {
453                 kvfree(fdt);
454                 fdt = NULL;
455         }
456
457         return fdt;
458 }