#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: ofw_subr.c,v 1.62 2025/10/04 01:12:15 thorpej Exp $");
#include <sys/param.h>
#include <sys/device.h>
#include <sys/kmem.h>
#include <sys/systm.h>
#include <sys/device_calls.h>
#include <dev/ofw/openfirm.h>
#define OFW_MAX_STACK_BUF_SIZE 256
#define OFW_PATH_BUF_SIZE 512
static device_call_t
of_devhandle_lookup_device_call(devhandle_t handle, const char *name,
devhandle_t *call_handlep)
{
__link_set_decl(of_device_calls, struct device_call_descriptor);
struct device_call_descriptor * const *desc;
__link_set_foreach(desc, of_device_calls) {
if (strcmp((*desc)->name, name) == 0) {
return (*desc)->call;
}
}
return NULL;
}
static const struct devhandle_impl of_devhandle_impl = {
.type = DEVHANDLE_TYPE_OF,
.lookup_device_call = of_devhandle_lookup_device_call,
};
devhandle_t
devhandle_from_of(devhandle_t super_handle, int phandle)
{
devhandle_type_t super_type = devhandle_type(super_handle);
devhandle_t handle = { 0 };
if (super_type == DEVHANDLE_TYPE_OF) {
handle.impl = super_handle.impl;
} else {
KASSERT(super_type == DEVHANDLE_TYPE_INVALID);
handle.impl = &of_devhandle_impl;
}
handle.integer = phandle;
return handle;
}
int
devhandle_to_of(devhandle_t const handle)
{
KASSERT(devhandle_type(handle) == DEVHANDLE_TYPE_OF);
return handle.integer;
}
static int
of_device_enumerate_children(device_t dev, devhandle_t call_handle, void *v)
{
struct device_enumerate_children_args *args = v;
int phandle = devhandle_to_of(call_handle);
int child;
for (child = OF_child(phandle); child != 0; child = OF_peer(child)) {
if (!args->callback(dev, devhandle_from_of(call_handle, child),
args->callback_arg)) {
break;
}
}
return 0;
}
OF_DEVICE_CALL_REGISTER(DEVICE_ENUMERATE_CHILDREN_STR,
of_device_enumerate_children)
static int
of_device_register(device_t dev, devhandle_t call_handle, void *v __unused)
{
int phandle = devhandle_to_of(call_handle);
char *path = kmem_zalloc(OFW_PATH_BUF_SIZE, KM_SLEEP);
if (OF_package_to_path(phandle, path, OFW_PATH_BUF_SIZE) > 0) {
device_setprop_string(dev, "device-path", path);
}
kmem_free(path, OFW_PATH_BUF_SIZE);
return 0;
}
OF_DEVICE_CALL_REGISTER(DEVICE_REGISTER_STR,
of_device_register)
static bool
of_is_bool_prop(int node, const char *prop, bool *valp)
{
char propval[8] = { 0 };
#if 0
if (prop[strlen(prop) - 1] != '?') {
return false;
}
#endif
int propsize = OF_getprop(node, prop, propval, sizeof(propval));
if (propsize >= 4 && memcmp(propval, "true", 4) == 0) {
*valp = true;
return true;
}
if (propsize >= 5 && memcmp(propval, "false", 5) == 0) {
*valp = false;
return true;
}
return false;
}
static int
of_device_get_property(device_t dev, devhandle_t call_handle, void *v)
{
struct device_get_property_args *args = v;
int node = devhandle_to_of(call_handle);
int propsize, rv, error = 0;
bool boolval = true;
prop_type_t proptype = PROP_TYPE_UNKNOWN;
propsize = OF_getproplen(node, args->prop);
if (propsize < 0) {
return ENOENT;
}
if (propsize == 0) {
goto done;
}
if (of_is_bool_prop(node, args->prop, &boolval)) {
proptype = PROP_TYPE_BOOL;
}
if (args->buf == NULL) {
goto done;
}
KASSERT(args->buflen != 0);
switch (args->reqtype) {
case PROP_TYPE_NUMBER:
KASSERT(args->buflen == sizeof(uint64_t));
if (propsize == sizeof(uint32_t)) {
uint32_t val32;
if (OF_getprop(node, args->prop, &val32,
sizeof(val32)) != sizeof(val32)) {
error = EIO;
goto done;
}
val32 = be32toh(val32);
*(uint64_t *)args->buf = val32;
} else if (propsize == sizeof(uint64_t)) {
uint64_t val64;
if (OF_getprop(node, args->prop, &val64,
sizeof(val64)) != sizeof(val64)) {
error = EIO;
goto done;
}
val64 = be64toh(val64);
*(uint64_t *)args->buf = val64;
} else {
error = EFTYPE;
}
break;
case PROP_TYPE_STRING:
memset(args->buf, 0, args->buflen);
case PROP_TYPE_DATA:
if (args->buflen < propsize) {
error = EFBIG;
goto done;
}
rv = OF_getprop(node, args->prop, args->buf, args->buflen);
if (rv < 0) {
error = EIO;
} else if (args->buflen < (propsize = rv)) {
error = EFBIG;
}
break;
case PROP_TYPE_BOOL:
KASSERT(args->buflen == sizeof(bool));
if (proptype == PROP_TYPE_BOOL) {
*(bool *)args->buf = boolval;
} else {
error = EFTYPE;
}
break;
default:
error = EFTYPE;
break;
}
done:
args->propsize = propsize;
args->encoding = _BIG_ENDIAN;
args->type = proptype;
return error;
}
OF_DEVICE_CALL_REGISTER(DEVICE_GET_PROPERTY_STR,
of_device_get_property)
int
of_decode_int(const unsigned char *p)
{
unsigned int i = *p++ << 8;
i = (i + *p++) << 8;
i = (i + *p++) << 8;
return (i + *p);
}
int
of_compatible(int phandle, const char * const *strings)
{
char *prop, propbuf[OFW_MAX_STACK_BUF_SIZE];
const char *cp;
int proplen, match = 0;
proplen = OF_getproplen(phandle, "compatible");
if (proplen <= 0) {
return 0;
}
prop = kmem_tmpbuf_alloc(proplen, propbuf, sizeof(propbuf), KM_SLEEP);
if (OF_getprop(phandle, "compatible", prop, proplen) != proplen) {
goto out;
}
for (; (cp = *strings) != NULL; strings++) {
if ((match = strlist_match(prop, proplen, cp)) != 0) {
break;
}
}
out:
kmem_tmpbuf_free(prop, proplen, propbuf);
return match;
}
int
of_compatible_match(int phandle,
const struct device_compatible_entry *compat_data)
{
char *prop, propbuf[OFW_MAX_STACK_BUF_SIZE];
int proplen, match = 0;
proplen = OF_getproplen(phandle, "compatible");
if (proplen <= 0) {
return 0;
}
prop = kmem_tmpbuf_alloc(proplen, propbuf, sizeof(propbuf), KM_SLEEP);
if (OF_getprop(phandle, "compatible", prop, proplen) != proplen) {
goto out;
}
match = device_compatible_match_strlist(prop, proplen, compat_data);
out:
kmem_tmpbuf_free(prop, proplen, propbuf);
return match;
}
const struct device_compatible_entry *
of_compatible_lookup(int phandle,
const struct device_compatible_entry *compat_data)
{
char *prop, propbuf[OFW_MAX_STACK_BUF_SIZE];
const struct device_compatible_entry *match = NULL;
int proplen;
proplen = OF_getproplen(phandle, "compatible");
if (proplen <= 0) {
return 0;
}
prop = kmem_tmpbuf_alloc(proplen, propbuf, sizeof(propbuf), KM_SLEEP);
if (OF_getprop(phandle, "compatible", prop, proplen) != proplen) {
goto out;
}
match = device_compatible_lookup_strlist(prop, proplen, compat_data);
out:
kmem_tmpbuf_free(prop, proplen, propbuf);
return match;
}
int
of_packagename(int phandle, char *buf, int bufsize)
{
char *pbuf;
const char *lastslash;
int l, rv;
pbuf = kmem_alloc(OFW_PATH_BUF_SIZE, KM_SLEEP);
l = OF_package_to_path(phandle, pbuf, OFW_PATH_BUF_SIZE);
if (l < 0 ||
(l == OFW_PATH_BUF_SIZE && pbuf[OFW_PATH_BUF_SIZE - 1] != '\0')) {
if (bufsize >= 25)
snprintf(buf, bufsize, "??? (phandle 0x%x)", phandle);
else if (bufsize >= 4)
strlcpy(buf, "???", bufsize);
else
panic("of_packagename: bufsize = %d is silly",
bufsize);
rv = -1;
} else {
pbuf[l] = '\0';
lastslash = strrchr(pbuf, '/');
strlcpy(buf, (lastslash == NULL) ? pbuf : (lastslash + 1),
bufsize);
rv = 0;
}
kmem_free(pbuf, OFW_PATH_BUF_SIZE);
return (rv);
}
int
of_find_firstchild_byname(int node, const char *name)
{
char namex[32];
int nn;
for (nn = OF_child(node); nn; nn = OF_peer(nn)) {
memset(namex, 0, sizeof(namex));
if (OF_getprop(nn, "name", namex, sizeof(namex)) == -1)
continue;
if (strcmp(name, namex) == 0)
return nn;
}
return -1;
}
int
of_find_bycompat(int node, const char *str)
{
const char * compatible[] = { str, NULL };
int child, ret;
for (child = OF_child(node); child; child = OF_peer(child)) {
if (of_compatible(child, compatible))
return child;
ret = of_find_bycompat(child, str);
if (ret != -1)
return ret;
}
return -1;
}
int
of_getnode_byname(int start, const char *target)
{
int node, next;
char name[64];
if (start == 0)
start = OF_peer(0);
for (node = start; node; node = next) {
memset(name, 0, sizeof name);
OF_getprop(node, "name", name, sizeof name - 1);
if (strcmp(name, target) == 0)
break;
if ((next = OF_child(node)) != 0)
continue;
while (node) {
if ((next = OF_peer(node)) != 0)
break;
node = OF_parent(node);
}
}
return node;
}
bool
of_to_uint32_prop(prop_dictionary_t dict, int node, const char *ofname,
const char *propname)
{
uint32_t prop;
if (OF_getprop(node, ofname, &prop, sizeof(prop)) != sizeof(prop))
return FALSE;
return(prop_dictionary_set_uint32(dict, propname, prop));
}
bool
of_to_dataprop(prop_dictionary_t dict, int node, const char *ofname,
const char *propname)
{
int len;
uint8_t prop[256];
len = OF_getprop(node, ofname, prop, 256);
if (len < 1)
return FALSE;
return prop_dictionary_set_data(dict, propname, prop, len);
}
char *
of_get_mode_string(char *buffer, int len)
{
int options;
char *pos, output_device[256];
options = OF_finddevice("/options");
if ((options == 0) || (options == -1))
return NULL;
if (OF_getprop(options, "output-device", output_device, 256) == 0)
return NULL;
pos = strstr(output_device, ":r");
if (pos == NULL)
return NULL;
strncpy(buffer, pos + 2, len);
return buffer;
}
device_t
of_device_from_phandle(int phandle)
{
devhandle_t devhandle;
deviter_t di;
device_t dev;
for (dev = deviter_first(&di, DEVITER_F_ROOT_FIRST);
dev != NULL;
dev = deviter_next(&di)) {
devhandle = device_handle(dev);
if (devhandle_type(devhandle) == DEVHANDLE_TYPE_OF) {
if (devhandle_to_of(devhandle) == phandle) {
break;
}
}
}
deviter_release(&di);
return dev;
}
bool
of_hasprop(int node, const char *prop)
{
return OF_getproplen(node, prop) >= 0;
}
int
of_getprop_uint32(int node, const char *prop, uint32_t *val)
{
uint32_t v;
int len;
len = OF_getprop(node, prop, &v, sizeof(v));
if (len != sizeof(v))
return -1;
*val = be32toh(v);
return 0;
}
int
of_getprop_uint32_array(int node, const char *prop, uint32_t *array, int n)
{
uint32_t *v = array;
int len;
len = OF_getprop(node, prop, array, n * sizeof(*v));
if (len < (int)(n * sizeof(*v)))
return -1;
for (; n > 0; n--) {
BE32TOH(*v);
v++;
}
return 0;
}
int
of_getprop_uint64(int node, const char *prop, uint64_t *val)
{
uint64_t v;
int len;
len = OF_getprop(node, prop, &v, sizeof(v));
if (len != sizeof(v))
return -1;
*val = be64toh(v);
return 0;
}