Currently, the start address of physical memory is obtained by masking the program counter with a fixed mask of 0xf8000000. This mask value was chosen as a balance between the requirements of different platforms. However, this does require that the start address of physical memory is a multiple of 128 MiB, precluding booting Linux on platforms where this requirement is not fulfilled. Fix this limitation by validating the masked address against the memory information in the passed DTB. Only use the start address from DTB when masking would yield an out-of-range address, prefer the traditional method in all other cases. Note that this applies only to the explicitly passed DTB on modern systems, and not to a DTB appended to the kernel, or to ATAGS. The appended DTB may need to be augmented by information from ATAGS, which may need to rely on knowledge of the start address of physical memory itself. This allows to boot Linux on r7s9210/rza2mevb using the 64 MiB of SDRAM on the RZA2MEVB sub board, which is located at 0x0C000000 (CS3 space), i.e. not at a multiple of 128 MiB. Suggested-by: Nicolas Pitre <nico@fluxnic.net> Suggested-by: Ard Biesheuvel <ardb@kernel.org> Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be> Reviewed-by: Ard Biesheuvel <ardb@kernel.org> Acked-by: Nicolas Pitre <nico@fluxnic.net> Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
131 lines
3.3 KiB
C
131 lines
3.3 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/libfdt.h>
|
|
#include <linux/sizes.h>
|
|
|
|
static const void *get_prop(const void *fdt, const char *node_path,
|
|
const char *property, int minlen)
|
|
{
|
|
const void *prop;
|
|
int offset, len;
|
|
|
|
offset = fdt_path_offset(fdt, node_path);
|
|
if (offset < 0)
|
|
return NULL;
|
|
|
|
prop = fdt_getprop(fdt, offset, property, &len);
|
|
if (!prop || len < minlen)
|
|
return NULL;
|
|
|
|
return prop;
|
|
}
|
|
|
|
static uint32_t get_cells(const void *fdt, const char *name)
|
|
{
|
|
const fdt32_t *prop = get_prop(fdt, "/", name, sizeof(fdt32_t));
|
|
|
|
if (!prop) {
|
|
/* default */
|
|
return 1;
|
|
}
|
|
|
|
return fdt32_ld(prop);
|
|
}
|
|
|
|
static uint64_t get_val(const fdt32_t *cells, uint32_t ncells)
|
|
{
|
|
uint64_t r;
|
|
|
|
r = fdt32_ld(cells);
|
|
if (ncells > 1)
|
|
r = (r << 32) | fdt32_ld(cells + 1);
|
|
|
|
return r;
|
|
}
|
|
|
|
/*
|
|
* Check the start of physical memory
|
|
*
|
|
* Traditionally, the start address of physical memory is obtained by masking
|
|
* the program counter. However, this does require that this address is a
|
|
* multiple of 128 MiB, precluding booting Linux on platforms where this
|
|
* requirement is not fulfilled.
|
|
* Hence validate the calculated address against the memory information in the
|
|
* DTB, and, if out-of-range, replace it by the real start address.
|
|
* To preserve backwards compatibility (systems reserving a block of memory
|
|
* at the start of physical memory, kdump, ...), the traditional method is
|
|
* always used if it yields a valid address.
|
|
*
|
|
* Return value: start address of physical memory to use
|
|
*/
|
|
uint32_t fdt_check_mem_start(uint32_t mem_start, const void *fdt)
|
|
{
|
|
uint32_t addr_cells, size_cells, base;
|
|
uint32_t fdt_mem_start = 0xffffffff;
|
|
const fdt32_t *reg, *endp;
|
|
uint64_t size, end;
|
|
const char *type;
|
|
int offset, len;
|
|
|
|
if (!fdt)
|
|
return mem_start;
|
|
|
|
if (fdt_magic(fdt) != FDT_MAGIC)
|
|
return mem_start;
|
|
|
|
/* There may be multiple cells on LPAE platforms */
|
|
addr_cells = get_cells(fdt, "#address-cells");
|
|
size_cells = get_cells(fdt, "#size-cells");
|
|
if (addr_cells > 2 || size_cells > 2)
|
|
return mem_start;
|
|
|
|
/* Walk all memory nodes and regions */
|
|
for (offset = fdt_next_node(fdt, -1, NULL); offset >= 0;
|
|
offset = fdt_next_node(fdt, offset, NULL)) {
|
|
type = fdt_getprop(fdt, offset, "device_type", NULL);
|
|
if (!type || strcmp(type, "memory"))
|
|
continue;
|
|
|
|
reg = fdt_getprop(fdt, offset, "linux,usable-memory", &len);
|
|
if (!reg)
|
|
reg = fdt_getprop(fdt, offset, "reg", &len);
|
|
if (!reg)
|
|
continue;
|
|
|
|
for (endp = reg + (len / sizeof(fdt32_t));
|
|
endp - reg >= addr_cells + size_cells;
|
|
reg += addr_cells + size_cells) {
|
|
size = get_val(reg + addr_cells, size_cells);
|
|
if (!size)
|
|
continue;
|
|
|
|
if (addr_cells > 1 && fdt32_ld(reg)) {
|
|
/* Outside 32-bit address space, skipping */
|
|
continue;
|
|
}
|
|
|
|
base = fdt32_ld(reg + addr_cells - 1);
|
|
end = base + size;
|
|
if (mem_start >= base && mem_start < end) {
|
|
/* Calculated address is valid, use it */
|
|
return mem_start;
|
|
}
|
|
|
|
if (base < fdt_mem_start)
|
|
fdt_mem_start = base;
|
|
}
|
|
}
|
|
|
|
if (fdt_mem_start == 0xffffffff) {
|
|
/* No usable memory found, falling back to default */
|
|
return mem_start;
|
|
}
|
|
|
|
/*
|
|
* The calculated address is not usable.
|
|
* Use the lowest usable physical memory address from the DTB instead,
|
|
* and make sure this is a multiple of 2 MiB for phys/virt patching.
|
|
*/
|
|
return round_up(fdt_mem_start, SZ_2M);
|
|
}
|