vaseboot/VasEBoot-core/bus/fdt.c

257 lines
7.7 KiB
C

/*
* VAS_EBOOT -- GRand Unified Bootloader
* Copyright (C) 2016 Free Software Foundation, Inc.
*
* VAS_EBOOT is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* VAS_EBOOT is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with VAS_EBOOT. If not, see <http://www.gnu.org/licenses/>.
*/
#include <VasEBoot/fdtbus.h>
#include <VasEBoot/fdt.h>
#include <VasEBoot/term.h>
static const void *dtb;
static VasEBoot_size_t root_address_cells, root_size_cells;
/* Pointer to this symbol signals invalid mapping. */
char VasEBoot_fdtbus_invalid_mapping[1];
struct VasEBoot_fdtbus_dev *devs;
struct VasEBoot_fdtbus_driver *drivers;
int
VasEBoot_fdtbus_is_compatible (const char *compat_string,
const struct VasEBoot_fdtbus_dev *dev)
{
VasEBoot_size_t compatible_size;
const char *compatible = VasEBoot_fdt_get_prop (dtb, dev->node, "compatible",
&compatible_size);
if (!compatible)
return 0;
const char *compatible_end = compatible + compatible_size;
while (compatible < compatible_end)
{
if (VasEBoot_strcmp (compat_string, compatible) == 0)
return 1;
compatible += VasEBoot_strlen (compatible) + 1;
}
return 0;
}
static void
fdtbus_scan (struct VasEBoot_fdtbus_dev *parent)
{
int node;
for (node = VasEBoot_fdt_first_node (dtb, parent ? parent->node : 0); node >= 0;
node = VasEBoot_fdt_next_node (dtb, node))
{
struct VasEBoot_fdtbus_dev *dev;
struct VasEBoot_fdtbus_driver *driver;
dev = VasEBoot_zalloc (sizeof (*dev));
if (!dev)
{
VasEBoot_print_error ();
return;
}
dev->node = node;
dev->next = devs;
dev->parent = parent;
devs = dev;
FOR_LIST_ELEMENTS(driver, drivers)
if (!dev->driver && VasEBoot_fdtbus_is_compatible (driver->compatible, dev))
{
VasEBoot_dprintf ("fdtbus", "Attaching %s\n", driver->compatible);
if (driver->attach (dev) == VAS_EBOOT_ERR_NONE)
{
VasEBoot_dprintf ("fdtbus", "Attached %s\n", driver->compatible);
dev->driver = driver;
break;
}
VasEBoot_print_error ();
}
fdtbus_scan (dev);
}
}
void
VasEBoot_fdtbus_register (struct VasEBoot_fdtbus_driver *driver)
{
struct VasEBoot_fdtbus_dev *dev;
VasEBoot_dprintf ("fdtbus", "Registering %s\n", driver->compatible);
VasEBoot_list_push (VAS_EBOOT_AS_LIST_P (&drivers),
VAS_EBOOT_AS_LIST (driver));
for (dev = devs; dev; dev = dev->next)
if (!dev->driver && VasEBoot_fdtbus_is_compatible (driver->compatible, dev))
{
VasEBoot_dprintf ("fdtbus", "Attaching %s (%p)\n", driver->compatible, dev);
if (driver->attach (dev) == VAS_EBOOT_ERR_NONE)
{
VasEBoot_dprintf ("fdtbus", "Attached %s\n", driver->compatible);
dev->driver = driver;
}
VasEBoot_print_error ();
}
}
void
VasEBoot_fdtbus_unregister (struct VasEBoot_fdtbus_driver *driver)
{
VasEBoot_list_remove (VAS_EBOOT_AS_LIST (driver));
struct VasEBoot_fdtbus_dev *dev;
for (dev = devs; dev; dev = dev->next)
if (dev->driver == driver)
{
if (driver->detach)
driver->detach(dev);
dev->driver = 0;
}
}
void
VasEBoot_fdtbus_init (const void *dtb_in, VasEBoot_size_t size)
{
if (!dtb_in || VasEBoot_fdt_check_header (dtb_in, size) < 0)
VasEBoot_fatal ("invalid FDT");
dtb = dtb_in;
const VasEBoot_uint32_t *prop = VasEBoot_fdt_get_prop (dtb, 0, "#address-cells", 0);
if (prop)
root_address_cells = VasEBoot_be_to_cpu32 (*prop);
else
root_address_cells = 1;
prop = VasEBoot_fdt_get_prop (dtb, 0, "#size-cells", 0);
if (prop)
root_size_cells = VasEBoot_be_to_cpu32 (*prop);
else
root_size_cells = 1;
fdtbus_scan (0);
}
static int
get_address_cells (const struct VasEBoot_fdtbus_dev *dev)
{
const VasEBoot_uint32_t *prop;
if (!dev)
return root_address_cells;
prop = VasEBoot_fdt_get_prop (dtb, dev->node, "#address-cells", 0);
if (prop)
return VasEBoot_be_to_cpu32 (*prop);
return 1;
}
static int
get_size_cells (const struct VasEBoot_fdtbus_dev *dev)
{
const VasEBoot_uint32_t *prop;
if (!dev)
return root_size_cells;
prop = VasEBoot_fdt_get_prop (dtb, dev->node, "#size-cells", 0);
if (prop)
return VasEBoot_be_to_cpu32 (*prop);
return 1;
}
static VasEBoot_uint64_t
get64 (const VasEBoot_uint32_t *reg, VasEBoot_size_t cells)
{
VasEBoot_uint64_t val = 0;
if (cells >= 1)
val = VasEBoot_be_to_cpu32 (reg[cells - 1]);
if (cells >= 2)
val |= ((VasEBoot_uint64_t) VasEBoot_be_to_cpu32 (reg[cells - 2])) << 32;
return val;
}
static volatile void *
translate (const struct VasEBoot_fdtbus_dev *dev, const VasEBoot_uint32_t *reg)
{
volatile void *ret;
const VasEBoot_uint32_t *ranges;
VasEBoot_size_t ranges_size, cells_per_mapping;
VasEBoot_size_t parent_address_cells, child_address_cells, child_size_cells;
VasEBoot_size_t nmappings, i;
if (dev == 0)
{
VasEBoot_uint64_t val;
val = get64 (reg, root_address_cells);
if (sizeof (void *) == 4 && (val >> 32))
return VasEBoot_fdtbus_invalid_mapping;
return (void *) (VasEBoot_addr_t) val;
}
ranges = VasEBoot_fdt_get_prop (dtb, dev->node, "ranges", &ranges_size);
if (!ranges)
return VasEBoot_fdtbus_invalid_mapping;
if (ranges_size == 0)
return translate (dev->parent, reg);
parent_address_cells = get_address_cells (dev->parent);
child_address_cells = get_address_cells (dev);
child_size_cells = get_size_cells (dev);
cells_per_mapping = parent_address_cells + child_address_cells + child_size_cells;
nmappings = ranges_size / 4 / cells_per_mapping;
for (i = 0; i < nmappings; i++)
{
const VasEBoot_uint32_t *child_addr = &ranges[i * cells_per_mapping];
const VasEBoot_uint32_t *parent_addr = child_addr + child_address_cells;
VasEBoot_uint64_t child_size = get64 (parent_addr + parent_address_cells, child_size_cells);
if (child_address_cells > 2 && VasEBoot_memcmp (reg, child_addr, (child_address_cells - 2) * 4) != 0)
continue;
if (get64 (reg, child_address_cells) < get64 (child_addr, child_address_cells))
continue;
VasEBoot_uint64_t offset = get64 (reg, child_address_cells) - get64 (child_addr, child_address_cells);
if (offset >= child_size)
continue;
ret = translate (dev->parent, parent_addr);
if (VasEBoot_fdtbus_is_mapping_valid (ret))
ret = (volatile char *) ret + offset;
return ret;
}
return VasEBoot_fdtbus_invalid_mapping;
}
volatile void *
VasEBoot_fdtbus_map_reg (const struct VasEBoot_fdtbus_dev *dev, int regno, VasEBoot_size_t *size)
{
VasEBoot_size_t address_cells, size_cells;
address_cells = get_address_cells (dev->parent);
size_cells = get_size_cells (dev->parent);
const VasEBoot_uint32_t *reg = VasEBoot_fdt_get_prop (dtb, dev->node, "reg", 0);
if (size && size_cells)
*size = reg[(address_cells + size_cells) * regno + address_cells];
if (size && !size_cells)
*size = 0;
return translate (dev->parent, reg + (address_cells + size_cells) * regno);
}
const char *
VasEBoot_fdtbus_get_name (const struct VasEBoot_fdtbus_dev *dev)
{
return VasEBoot_fdt_get_nodename (dtb, dev->node);
}
const void *
VasEBoot_fdtbus_get_prop (const struct VasEBoot_fdtbus_dev *dev,
const char *name,
VasEBoot_uint32_t *len)
{
return VasEBoot_fdt_get_prop (dtb, dev->node, name, len);
}
const void *
VasEBoot_fdtbus_get_fdt (void)
{
return dtb;
}