fdt_check_mem_start.c (4408B)
1// SPDX-License-Identifier: GPL-2.0-only 2 3#include <linux/kernel.h> 4#include <linux/libfdt.h> 5#include <linux/sizes.h> 6 7static const void *get_prop(const void *fdt, const char *node_path, 8 const char *property, int minlen) 9{ 10 const void *prop; 11 int offset, len; 12 13 offset = fdt_path_offset(fdt, node_path); 14 if (offset < 0) 15 return NULL; 16 17 prop = fdt_getprop(fdt, offset, property, &len); 18 if (!prop || len < minlen) 19 return NULL; 20 21 return prop; 22} 23 24static uint32_t get_cells(const void *fdt, const char *name) 25{ 26 const fdt32_t *prop = get_prop(fdt, "/", name, sizeof(fdt32_t)); 27 28 if (!prop) { 29 /* default */ 30 return 1; 31 } 32 33 return fdt32_ld(prop); 34} 35 36static uint64_t get_val(const fdt32_t *cells, uint32_t ncells) 37{ 38 uint64_t r; 39 40 r = fdt32_ld(cells); 41 if (ncells > 1) 42 r = (r << 32) | fdt32_ld(cells + 1); 43 44 return r; 45} 46 47/* 48 * Check the start of physical memory 49 * 50 * Traditionally, the start address of physical memory is obtained by masking 51 * the program counter. However, this does require that this address is a 52 * multiple of 128 MiB, precluding booting Linux on platforms where this 53 * requirement is not fulfilled. 54 * Hence validate the calculated address against the memory information in the 55 * DTB, and, if out-of-range, replace it by the real start address. 56 * To preserve backwards compatibility (systems reserving a block of memory 57 * at the start of physical memory, kdump, ...), the traditional method is 58 * used if it yields a valid address, unless the "linux,usable-memory-range" 59 * property is present. 60 * 61 * Return value: start address of physical memory to use 62 */ 63uint32_t fdt_check_mem_start(uint32_t mem_start, const void *fdt) 64{ 65 uint32_t addr_cells, size_cells, usable_base, base; 66 uint32_t fdt_mem_start = 0xffffffff; 67 const fdt32_t *usable, *reg, *endp; 68 uint64_t size, usable_end, end; 69 const char *type; 70 int offset, len; 71 72 if (!fdt) 73 return mem_start; 74 75 if (fdt_magic(fdt) != FDT_MAGIC) 76 return mem_start; 77 78 /* There may be multiple cells on LPAE platforms */ 79 addr_cells = get_cells(fdt, "#address-cells"); 80 size_cells = get_cells(fdt, "#size-cells"); 81 if (addr_cells > 2 || size_cells > 2) 82 return mem_start; 83 84 /* 85 * Usable memory in case of a crash dump kernel 86 * This property describes a limitation: memory within this range is 87 * only valid when also described through another mechanism 88 */ 89 usable = get_prop(fdt, "/chosen", "linux,usable-memory-range", 90 (addr_cells + size_cells) * sizeof(fdt32_t)); 91 if (usable) { 92 size = get_val(usable + addr_cells, size_cells); 93 if (!size) 94 return mem_start; 95 96 if (addr_cells > 1 && fdt32_ld(usable)) { 97 /* Outside 32-bit address space */ 98 return mem_start; 99 } 100 101 usable_base = fdt32_ld(usable + addr_cells - 1); 102 usable_end = usable_base + size; 103 } 104 105 /* Walk all memory nodes and regions */ 106 for (offset = fdt_next_node(fdt, -1, NULL); offset >= 0; 107 offset = fdt_next_node(fdt, offset, NULL)) { 108 type = fdt_getprop(fdt, offset, "device_type", NULL); 109 if (!type || strcmp(type, "memory")) 110 continue; 111 112 reg = fdt_getprop(fdt, offset, "linux,usable-memory", &len); 113 if (!reg) 114 reg = fdt_getprop(fdt, offset, "reg", &len); 115 if (!reg) 116 continue; 117 118 for (endp = reg + (len / sizeof(fdt32_t)); 119 endp - reg >= addr_cells + size_cells; 120 reg += addr_cells + size_cells) { 121 size = get_val(reg + addr_cells, size_cells); 122 if (!size) 123 continue; 124 125 if (addr_cells > 1 && fdt32_ld(reg)) { 126 /* Outside 32-bit address space, skipping */ 127 continue; 128 } 129 130 base = fdt32_ld(reg + addr_cells - 1); 131 end = base + size; 132 if (usable) { 133 /* 134 * Clip to usable range, which takes precedence 135 * over mem_start 136 */ 137 if (base < usable_base) 138 base = usable_base; 139 140 if (end > usable_end) 141 end = usable_end; 142 143 if (end <= base) 144 continue; 145 } else if (mem_start >= base && mem_start < end) { 146 /* Calculated address is valid, use it */ 147 return mem_start; 148 } 149 150 if (base < fdt_mem_start) 151 fdt_mem_start = base; 152 } 153 } 154 155 if (fdt_mem_start == 0xffffffff) { 156 /* No usable memory found, falling back to default */ 157 return mem_start; 158 } 159 160 /* 161 * The calculated address is not usable, or was overridden by the 162 * "linux,usable-memory-range" property. 163 * Use the lowest usable physical memory address from the DTB instead, 164 * and make sure this is a multiple of 2 MiB for phys/virt patching. 165 */ 166 return round_up(fdt_mem_start, SZ_2M); 167}