NetBSD/sys/dev/fdt/fdt_subr.c

544 lines
12 KiB
C

/* $NetBSD: fdt_subr.c,v 1.38 2020/07/16 16:39:18 jmcneill Exp $ */
/*-
* Copyright (c) 2015 Jared D. McNeill <jmcneill@invisible.ca>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: fdt_subr.c,v 1.38 2020/07/16 16:39:18 jmcneill 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;
static struct fdt_conslist fdt_console_list =
TAILQ_HEAD_INITIALIZER(fdt_console_list);
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) {
/* pass through to parent */
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;
}
/* No mapping found */
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;
}
#if defined(FDT)
const struct fdt_console *
fdtbus_get_console(void)
{
static const struct fdt_console_info *booted_console = NULL;
if (booted_console == NULL) {
__link_set_decl(fdt_consoles, struct fdt_console_info);
struct fdt_console_info * const *info;
const struct fdt_console_info *best_info = NULL;
const int phandle = fdtbus_get_stdout_phandle();
int best_match = 0;
if (phandle == -1) {
printf("WARNING: no console device\n");
return NULL;
}
__link_set_foreach(info, fdt_consoles) {
const int match = (*info)->ops->match(phandle);
if (match > best_match) {
best_match = match;
best_info = *info;
}
}
booted_console = best_info;
}
return booted_console == NULL ? NULL : booted_console->ops;
}
#endif
const char *
fdtbus_get_stdout_path(void)
{
const char *prop;
const int off = fdt_path_offset(fdtbus_get_data(), "/chosen");
if (off >= 0) {
prop = fdt_getprop(fdtbus_get_data(), off, "stdout-path", NULL);
if (prop != NULL)
return prop;
}
/* If the stdout-path property is not found, return the default */
return FDT_DEFAULT_STDOUT_PATH;
}
int
fdtbus_get_stdout_phandle(void)
{
const char *prop, *p;
int off, len;
prop = fdtbus_get_stdout_path();
if (prop == NULL)
return -1;
p = strchr(prop, ':');
len = p == NULL ? strlen(prop) : (p - prop);
if (*prop != '/') {
/* Alias */
prop = fdt_get_alias_namelen(fdtbus_get_data(), prop, len);
if (prop == NULL)
return -1;
len = strlen(prop);
}
off = fdt_path_offset_namelen(fdtbus_get_data(), prop, len);
if (off < 0)
return -1;
return fdtbus_offset2phandle(off);
}
int
fdtbus_get_stdout_speed(void)
{
const char *prop, *p;
prop = fdtbus_get_stdout_path();
if (prop == NULL)
return -1;
p = strchr(prop, ':');
if (p == NULL)
return -1;
return (int)strtoul(p + 1, NULL, 10);
}
tcflag_t
fdtbus_get_stdout_flags(void)
{
const char *prop, *p;
tcflag_t flags = TTYDEF_CFLAG;
char *ep;
prop = fdtbus_get_stdout_path();
if (prop == NULL)
return flags;
p = strchr(prop, ':');
if (p == NULL)
return flags;
ep = NULL;
(void)strtoul(p + 1, &ep, 10);
if (ep == NULL)
return flags;
/* <baud>{<parity>{<bits>{<flow>}}} */
while (*ep) {
switch (*ep) {
/* parity */
case 'n': flags &= ~(PARENB|PARODD); break;
case 'e': flags &= ~PARODD; flags |= PARENB; break;
case 'o': flags |= (PARENB|PARODD); break;
/* bits */
case '5': flags &= ~CSIZE; flags |= CS5; break;
case '6': flags &= ~CSIZE; flags |= CS6; break;
case '7': flags &= ~CSIZE; flags |= CS7; break;
case '8': flags &= ~CSIZE; flags |= CS8; break;
/* flow */
case 'r': flags |= CRTSCTS; break;
}
ep++;
}
return flags;
}
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, *name;
int len, cur;
if ((len = OF_getproplen(phandle, prop)) < 0)
return NULL;
names = fdtbus_get_string(phandle, prop);
for (name = names, cur = 0; len > 0;
len -= strlen(name) + 1, name += strlen(name) + 1, cur++) {
if (index == cur)
return name;
}
return NULL;
}
int
fdtbus_get_index(int phandle, const char *prop, const char *name, u_int *idx)
{
const char *p;
size_t pl;
u_int index;
int len;
p = fdtbus_get_prop(phandle, prop, &len);
if (p == NULL || len <= 0)
return -1;
for (index = 0; len > 0;
pl = strlen(p) + 1, len -= pl, p += pl, index++) {
if (strcmp(p, name) == 0) {
*idx = index;
return 0;
}
}
return -1;
}