#include <linux/kernel.h>
#include <linux/libfdt.h>
#include <linux/sizes.h>
#include "misc.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) {
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;
}
uint32_t fdt_check_mem_start(uint32_t mem_start, const void *fdt)
{
uint32_t addr_cells, size_cells, usable_base, base;
uint32_t fdt_mem_start = 0xffffffff;
const fdt32_t *usable, *reg, *endp;
uint64_t size, usable_end, end;
const char *type;
int offset, len;
if (!fdt)
return mem_start;
if (fdt_magic(fdt) != FDT_MAGIC)
return mem_start;
addr_cells = get_cells(fdt, "#address-cells");
size_cells = get_cells(fdt, "#size-cells");
if (addr_cells > 2 || size_cells > 2)
return mem_start;
usable = get_prop(fdt, "/chosen", "linux,usable-memory-range",
(addr_cells + size_cells) * sizeof(fdt32_t));
if (usable) {
size = get_val(usable + addr_cells, size_cells);
if (!size)
return mem_start;
if (addr_cells > 1 && fdt32_ld(usable)) {
return mem_start;
}
usable_base = fdt32_ld(usable + addr_cells - 1);
usable_end = usable_base + size;
}
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)) {
continue;
}
base = fdt32_ld(reg + addr_cells - 1);
end = base + size;
if (usable) {
if (base < usable_base)
base = usable_base;
if (end > usable_end)
end = usable_end;
if (end <= base)
continue;
} else if (mem_start >= base && mem_start < end) {
return mem_start;
}
if (base < fdt_mem_start)
fdt_mem_start = base;
}
}
if (fdt_mem_start == 0xffffffff) {
return mem_start;
}
return round_up(fdt_mem_start, SZ_2M);
}