linux/drivers/of/kexec.c
<<
>>
Prefs
   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 */
  46static 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 */
  81static 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 */
 107static 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 */
 132int 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 */
 159int 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 */
 194static 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 */
 227static 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 */
 253static 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 */
 272void *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
 451out:
 452        if (ret) {
 453                kvfree(fdt);
 454                fdt = NULL;
 455        }
 456
 457        return fdt;
 458}
 459