#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: fdt_subr.c,v 1.41 2025/09/06 22:53:49 thorpej Exp $");
#include "opt_fdt.h"
#include <sys/param.h>
#include <sys/bus.h>
#include <libfdt.h>
#include <dev/fdt/fdtvar.h>
#include <dev/fdt/fdt_private.h>
#ifndef FDT_DEFAULT_STDOUT_PATH
#define FDT_DEFAULT_STDOUT_PATH "serial0:115200n8"
#endif
static const void *fdt_data;
bool
fdtbus_init(const void *data)
{
KASSERT(fdt_data == NULL);
if (fdt_check_header(data) != 0) {
return false;
}
fdt_data = data;
return true;
}
const void *
fdtbus_get_data(void)
{
return fdt_data;
}
int
fdtbus_offset2phandle(int offset)
{
if (offset < 0)
return 0;
return offset + fdt_off_dt_struct(fdt_data);
}
int
fdtbus_phandle2offset(int phandle)
{
const int dtoff = fdt_off_dt_struct(fdt_data);
if (phandle == -1)
phandle = dtoff;
if (phandle < dtoff)
return -1;
return phandle - dtoff;
}
static bool fdtbus_decoderegprop = true;
void
fdtbus_set_decoderegprop(bool decode)
{
fdtbus_decoderegprop = decode;
}
int
fdtbus_get_addr_cells(int phandle)
{
uint32_t addr_cells;
if (of_getprop_uint32(phandle, "#address-cells", &addr_cells))
addr_cells = 2;
return addr_cells;
}
int
fdtbus_get_size_cells(int phandle)
{
uint32_t size_cells;
if (of_getprop_uint32(phandle, "#size-cells", &size_cells))
size_cells = 0;
return size_cells;
}
int
fdtbus_get_phandle(int phandle, const char *prop)
{
u_int phandle_ref;
const u_int *buf;
int len;
buf = fdt_getprop(fdtbus_get_data(),
fdtbus_phandle2offset(phandle), prop, &len);
if (buf == NULL || len < sizeof(phandle_ref))
return -1;
phandle_ref = be32dec(buf);
return fdtbus_get_phandle_from_native(phandle_ref);
}
int
fdtbus_get_phandle_with_data(int phandle, const char *prop, const char *cells,
int index, struct fdt_phandle_data *data)
{
int len;
const int offset = 1;
const u_int *p = fdtbus_get_prop(phandle, prop, &len);
if (p == NULL || len <= 0)
return EINVAL;
for (int i = 0; len > 0; i++) {
u_int phandle_ref = be32toh(*p);
const u_int iparent = fdtbus_get_phandle_from_native(phandle_ref);
uint32_t cells_num;
of_getprop_uint32(iparent, cells, &cells_num);
if (index == i) {
if (data != NULL) {
data->phandle = iparent;
data->count = cells_num;
data->values = p + offset;
}
goto done;
}
const u_int reclen = offset + cells_num;
len -= reclen * sizeof(u_int);
p += reclen;
}
return EINVAL;
done:
return 0;
}
int
fdtbus_get_phandle_from_native(int phandle)
{
const int off = fdt_node_offset_by_phandle(fdt_data, phandle);
if (off < 0) {
return -1;
}
return fdtbus_offset2phandle(off);
}
bool
fdtbus_get_path(int phandle, char *buf, size_t buflen)
{
const int off = fdtbus_phandle2offset(phandle);
if (off < 0) {
return false;
}
if (fdt_get_path(fdt_data, off, buf, (int)buflen) != 0) {
return false;
}
return true;
}
uint64_t
fdtbus_get_cells(const uint8_t *buf, int cells)
{
switch (cells) {
case 0: return 0;
case 1: return be32dec(buf);
case 2: return ((uint64_t)be32dec(buf)<<32)|be32dec(buf+4);
default: panic("fdtbus_get_cells: bad cells val %d\n", cells);
}
}
static uint64_t
fdtbus_decode_range(int phandle, uint64_t paddr)
{
const int parent = OF_parent(phandle);
if (parent == -1)
return paddr;
if (!fdtbus_decoderegprop)
return paddr;
const uint8_t *buf;
int len;
buf = fdt_getprop(fdtbus_get_data(),
fdtbus_phandle2offset(phandle), "ranges", &len);
if (buf == NULL)
return paddr;
if (len == 0) {
return fdtbus_decode_range(parent, paddr);
}
const int addr_cells = fdtbus_get_addr_cells(phandle);
const int size_cells = fdtbus_get_size_cells(phandle);
const int paddr_cells = fdtbus_get_addr_cells(parent);
if (addr_cells == -1 || size_cells == -1 || paddr_cells == -1)
return paddr;
while (len > 0) {
uint64_t cba, pba, cl;
cba = fdtbus_get_cells(buf, addr_cells);
buf += addr_cells * 4;
pba = fdtbus_get_cells(buf, paddr_cells);
buf += paddr_cells * 4;
cl = fdtbus_get_cells(buf, size_cells);
buf += size_cells * 4;
#ifdef FDTBUS_DEBUG
printf("%s: %s: cba=%#" PRIx64 ", pba=%#" PRIx64 ", cl=%#" PRIx64 "\n", __func__, fdt_get_name(fdtbus_get_data(), fdtbus_phandle2offset(phandle), NULL), cba, pba, cl);
#endif
if (paddr >= cba && paddr < cba + cl)
return fdtbus_decode_range(parent, pba) + (paddr - cba);
len -= (addr_cells + paddr_cells + size_cells) * 4;
}
return paddr;
}
int
fdtbus_get_reg_byname(int phandle, const char *name, bus_addr_t *paddr,
bus_size_t *psize)
{
u_int index;
int error;
error = fdtbus_get_index(phandle, "reg-names", name, &index);
if (error != 0)
return ENOENT;
return fdtbus_get_reg(phandle, index, paddr, psize);
}
int
fdtbus_get_reg(int phandle, u_int index, bus_addr_t *paddr, bus_size_t *psize)
{
uint64_t addr, size;
int error;
error = fdtbus_get_reg64(phandle, index, &addr, &size);
if (error)
return error;
if (sizeof(bus_addr_t) == 4 && (addr + size) > 0x100000000)
return ERANGE;
if (paddr)
*paddr = (bus_addr_t)addr;
if (psize)
*psize = (bus_size_t)size;
return 0;
}
int
fdtbus_get_reg64(int phandle, u_int index, uint64_t *paddr, uint64_t *psize)
{
uint64_t addr, size;
const uint8_t *buf;
int len;
const int addr_cells = fdtbus_get_addr_cells(OF_parent(phandle));
const int size_cells = fdtbus_get_size_cells(OF_parent(phandle));
if (addr_cells == -1 || size_cells == -1)
return EINVAL;
buf = fdt_getprop(fdtbus_get_data(),
fdtbus_phandle2offset(phandle), "reg", &len);
if (buf == NULL || len <= 0)
return EINVAL;
const u_int reglen = size_cells * 4 + addr_cells * 4;
if (reglen == 0)
return EINVAL;
if (index >= len / reglen)
return ENXIO;
buf += index * reglen;
addr = fdtbus_get_cells(buf, addr_cells);
buf += addr_cells * 4;
size = fdtbus_get_cells(buf, size_cells);
if (paddr) {
*paddr = fdtbus_decode_range(OF_parent(phandle), addr);
#ifdef FDTBUS_DEBUG
const char *name = fdt_get_name(fdtbus_get_data(),
fdtbus_phandle2offset(phandle), NULL);
printf("fdt: [%s] decoded addr #%u: %" PRIx64
" -> %" PRIx64 "\n", name, index, addr, *paddr);
#endif
}
if (psize)
*psize = size;
return 0;
}
bool
fdtbus_status_okay(int phandle)
{
const int off = fdtbus_phandle2offset(phandle);
const char *prop = fdt_getprop(fdtbus_get_data(), off, "status", NULL);
if (prop == NULL)
return true;
return strncmp(prop, "ok", 2) == 0;
}
const void *
fdtbus_get_prop(int phandle, const char *prop, int *plen)
{
const int off = fdtbus_phandle2offset(phandle);
return fdt_getprop(fdtbus_get_data(), off, prop, plen);
}
const char *
fdtbus_get_string(int phandle, const char *prop)
{
const int off = fdtbus_phandle2offset(phandle);
if (strcmp(prop, "name") == 0)
return fdt_get_name(fdtbus_get_data(), off, NULL);
else
return fdt_getprop(fdtbus_get_data(), off, prop, NULL);
}
const char *
fdtbus_get_string_index(int phandle, const char *prop, u_int index)
{
const char *names;
int len;
if ((len = OF_getproplen(phandle, prop)) < 0)
return NULL;
names = fdtbus_get_string(phandle, prop);
return strlist_string(names, len, index);
}
int
fdtbus_get_index(int phandle, const char *prop, const char *name, u_int *idx)
{
const char *p;
int len, index;
p = fdtbus_get_prop(phandle, prop, &len);
if (p == NULL || len <= 0)
return -1;
index = strlist_index(p, len, name);
if (index == -1)
return -1;
*idx = index;
return 0;
}