Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
CONFIG_KUNIT=y
CONFIG_OF=y
CONFIG_OF_KUNIT_TEST=y
CONFIG_OF_OVERLAY=y
CONFIG_OF_OVERLAY_KUNIT_TEST=y
+129
View File
@@ -0,0 +1,129 @@
# SPDX-License-Identifier: GPL-2.0
config DTC
bool
config GENERIC_BUILTIN_DTB
bool
config BUILTIN_DTB_ALL
bool
menuconfig OF
bool "Device Tree and Open Firmware support"
help
This option enables the device tree infrastructure.
It is automatically selected by platforms that need it or can
be enabled manually for unittests, overlays or
compile-coverage.
if OF
config OF_UNITTEST
bool "Device Tree runtime unit tests"
depends on OF_EARLY_FLATTREE
select IRQ_DOMAIN
select OF_RESOLVE
help
This option builds in test cases for the device tree infrastructure
that are executed once at boot time, and the results dumped to the
console.
This option should only be enabled for a development kernel. The tests
will taint the kernel with TAINT_TEST. The tests will cause ERROR and
WARNING messages to print on the console. The tests will cause stack
traces to print on the console. It is possible that the tests will
leave the devicetree in a corrupted state.
The unittest output will be verbose. Copy the output to a file
via capturing the console output or via the dmesg command. Process
this file with scripts/dtc/of_unittest_expect to reduce the
verbosity, test whether expected output is present, and to
summarize the results.
If unsure, say N here. This option is not safe to enable.
config OF_KUNIT_TEST
tristate "Devicetree KUnit Test" if !KUNIT_ALL_TESTS
depends on KUNIT
default KUNIT_ALL_TESTS
help
This option builds KUnit unit tests for device tree infrastructure.
If unsure, say N here, but this option is safe to enable.
config OF_ALL_DTBS
bool "Build all Device Tree Blobs"
depends on COMPILE_TEST
select DTC
help
This option builds all possible Device Tree Blobs (DTBs) for the
current architecture.
If unsure, say N here, but this option is safe to enable.
config OF_FLATTREE
bool
select DTC
select LIBFDT
select CRC32
config OF_EARLY_FLATTREE
def_bool OF && !(SPARC || ALPHA || HEXAGON || M68K || PARISC || S390)
select DMA_DECLARE_COHERENT if HAS_DMA && HAS_IOMEM
select OF_FLATTREE
config OF_PROMTREE
bool
config OF_KOBJ
def_bool SYSFS
# Hardly any platforms need this. It is safe to select, but only do so if you
# need it.
config OF_DYNAMIC
bool "Support for dynamic device trees" if OF_UNITTEST
select OF_KOBJ
help
On some platforms, the device tree can be manipulated at runtime.
While this option is selected automatically on such platforms, you
can enable it manually to improve device tree unit test coverage.
config OF_ADDRESS
def_bool y
depends on !SPARC && (HAS_IOMEM || UML)
config OF_IRQ
def_bool y
depends on !SPARC && IRQ_DOMAIN
config OF_RESERVED_MEM
def_bool OF_EARLY_FLATTREE
config OF_RESOLVE
bool
config OF_OVERLAY
bool "Device Tree overlays"
select OF_DYNAMIC
select OF_FLATTREE
select OF_RESOLVE
help
Overlays are a method to dynamically modify part of the kernel's
device tree with dynamically loaded data.
While this option is selected automatically when needed, you can
enable it manually to improve device tree unit test coverage.
config OF_OVERLAY_KUNIT_TEST
tristate "Device Tree overlay KUnit tests" if !KUNIT_ALL_TESTS
depends on KUNIT
default KUNIT_ALL_TESTS
select DTC
help
This option builds KUnit unit tests for the device tree overlay code.
If unsure, say N here, but this option is safe to enable.
config OF_NUMA
bool
endif # OF
+27
View File
@@ -0,0 +1,27 @@
# SPDX-License-Identifier: GPL-2.0
obj-y = base.o cpu.o device.o module.o platform.o property.o
obj-$(CONFIG_OF_KOBJ) += kobj.o
obj-$(CONFIG_OF_DYNAMIC) += dynamic.o
obj-$(CONFIG_OF_FLATTREE) += fdt.o empty_root.dtb.o
obj-$(CONFIG_OF_EARLY_FLATTREE) += fdt_address.o
obj-$(CONFIG_OF_PROMTREE) += pdt.o
obj-$(CONFIG_OF_ADDRESS) += address.o
obj-$(CONFIG_OF_IRQ) += irq.o
obj-$(CONFIG_OF_UNITTEST) += unittest.o
obj-$(CONFIG_OF_RESERVED_MEM) += of_reserved_mem.o
obj-$(CONFIG_OF_RESOLVE) += resolver.o
obj-$(CONFIG_OF_OVERLAY) += overlay.o
obj-$(CONFIG_OF_NUMA) += of_numa.o
ifdef CONFIG_KEXEC_FILE
ifdef CONFIG_OF_FLATTREE
obj-y += kexec.o
endif
endif
obj-$(CONFIG_KUNIT) += of_kunit_helpers.o
obj-$(CONFIG_OF_KUNIT_TEST) += of_test.o
obj-$(CONFIG_OF_OVERLAY_KUNIT_TEST) += overlay-test.o
overlay-test-y := overlay_test.o kunit_overlay_test.dtbo.o
obj-$(CONFIG_OF_UNITTEST) += unittest-data/
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+210
View File
@@ -0,0 +1,210 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/cpu.h>
#include <linux/kernel.h>
#include <linux/of.h>
/**
* of_get_cpu_hwid - Get the hardware ID from a CPU device node
*
* @cpun: CPU number(logical index) for which device node is required
* @thread: The local thread number to get the hardware ID for.
*
* Return: The hardware ID for the CPU node or ~0ULL if not found.
*/
u64 of_get_cpu_hwid(struct device_node *cpun, unsigned int thread)
{
const __be32 *cell;
int ac, len;
ac = of_n_addr_cells(cpun);
cell = of_get_property(cpun, "reg", &len);
if (!cell || !ac || ((sizeof(*cell) * ac * (thread + 1)) > len))
return ~0ULL;
cell += ac * thread;
return of_read_number(cell, ac);
}
/*
* arch_match_cpu_phys_id - Match the given logical CPU and physical id
*
* @cpu: logical cpu index of a core/thread
* @phys_id: physical identifier of a core/thread
*
* CPU logical to physical index mapping is architecture specific.
* However this __weak function provides a default match of physical
* id to logical cpu index. phys_id provided here is usually values read
* from the device tree which must match the hardware internal registers.
*
* Returns true if the physical identifier and the logical cpu index
* correspond to the same core/thread, false otherwise.
*/
bool __weak arch_match_cpu_phys_id(int cpu, u64 phys_id)
{
return (u32)phys_id == cpu;
}
/*
* Checks if the given "prop_name" property holds the physical id of the
* core/thread corresponding to the logical cpu 'cpu'. If 'thread' is not
* NULL, local thread number within the core is returned in it.
*/
static bool __of_find_n_match_cpu_property(struct device_node *cpun,
const char *prop_name, int cpu, unsigned int *thread)
{
const __be32 *cell;
int ac, prop_len, tid;
u64 hwid;
ac = of_n_addr_cells(cpun);
cell = of_get_property(cpun, prop_name, &prop_len);
if (!cell && !ac && arch_match_cpu_phys_id(cpu, 0))
return true;
if (!cell || !ac || ac > 2)
return false;
prop_len /= sizeof(*cell) * ac;
for (tid = 0; tid < prop_len; tid++) {
hwid = of_read_number(cell, ac);
if (arch_match_cpu_phys_id(cpu, hwid)) {
if (thread)
*thread = tid;
return true;
}
cell += ac;
}
return false;
}
/*
* arch_find_n_match_cpu_physical_id - See if the given device node is
* for the cpu corresponding to logical cpu 'cpu'. Return true if so,
* else false. If 'thread' is non-NULL, the local thread number within the
* core is returned in it.
*/
bool __weak arch_find_n_match_cpu_physical_id(struct device_node *cpun,
int cpu, unsigned int *thread)
{
/* Check for non-standard "ibm,ppc-interrupt-server#s" property
* for thread ids on PowerPC. If it doesn't exist fallback to
* standard "reg" property.
*/
if (IS_ENABLED(CONFIG_PPC) &&
__of_find_n_match_cpu_property(cpun,
"ibm,ppc-interrupt-server#s",
cpu, thread))
return true;
return __of_find_n_match_cpu_property(cpun, "reg", cpu, thread);
}
/**
* of_get_cpu_node - Get device node associated with the given logical CPU
*
* @cpu: CPU number(logical index) for which device node is required
* @thread: if not NULL, local thread number within the physical core is
* returned
*
* The main purpose of this function is to retrieve the device node for the
* given logical CPU index. It should be used to initialize the of_node in
* cpu device. Once of_node in cpu device is populated, all the further
* references can use that instead.
*
* CPU logical to physical index mapping is architecture specific and is built
* before booting secondary cores. This function uses arch_match_cpu_phys_id
* which can be overridden by architecture specific implementation.
*
* Return: A node pointer for the logical cpu with refcount incremented, use
* of_node_put() on it when done. Returns NULL if not found.
*/
struct device_node *of_get_cpu_node(int cpu, unsigned int *thread)
{
struct device_node *cpun;
for_each_of_cpu_node(cpun) {
if (arch_find_n_match_cpu_physical_id(cpun, cpu, thread))
return cpun;
}
return NULL;
}
EXPORT_SYMBOL(of_get_cpu_node);
/**
* of_cpu_device_node_get: Get the CPU device_node for a given logical CPU number
*
* @cpu: The logical CPU number
*
* Return: Pointer to the device_node for CPU with its reference count
* incremented of the given logical CPU number or NULL if the CPU device_node
* is not found.
*/
struct device_node *of_cpu_device_node_get(int cpu)
{
struct device *cpu_dev;
cpu_dev = get_cpu_device(cpu);
if (!cpu_dev)
return of_get_cpu_node(cpu, NULL);
return of_node_get(cpu_dev->of_node);
}
EXPORT_SYMBOL(of_cpu_device_node_get);
/**
* of_cpu_node_to_id: Get the logical CPU number for a given device_node
*
* @cpu_node: Pointer to the device_node for CPU.
*
* Return: The logical CPU number of the given CPU device_node or -ENODEV if the
* CPU is not found.
*/
int of_cpu_node_to_id(struct device_node *cpu_node)
{
int cpu;
bool found = false;
struct device_node *np;
for_each_possible_cpu(cpu) {
np = of_cpu_device_node_get(cpu);
found = (cpu_node == np);
of_node_put(np);
if (found)
return cpu;
}
return -ENODEV;
}
EXPORT_SYMBOL(of_cpu_node_to_id);
/**
* of_get_cpu_state_node - Get CPU's idle state node at the given index
*
* @cpu_node: The device node for the CPU
* @index: The index in the list of the idle states
*
* Two generic methods can be used to describe a CPU's idle states, either via
* a flattened description through the "cpu-idle-states" binding or via the
* hierarchical layout, using the "power-domains" and the "domain-idle-states"
* bindings. This function check for both and returns the idle state node for
* the requested index.
*
* Return: An idle state node if found at @index. The refcount is incremented
* for it, so call of_node_put() on it when done. Returns NULL if not found.
*/
struct device_node *of_get_cpu_state_node(const struct device_node *cpu_node,
int index)
{
struct of_phandle_args args;
int err;
err = of_parse_phandle_with_args(cpu_node, "power-domains",
"#power-domain-cells", 0, &args);
if (!err) {
struct device_node *state_node =
of_parse_phandle(args.np, "domain-idle-states", index);
of_node_put(args.np);
if (state_node)
return state_node;
}
return of_parse_phandle(cpu_node, "cpu-idle-states", index);
}
EXPORT_SYMBOL(of_get_cpu_state_node);
+314
View File
@@ -0,0 +1,314 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/of_iommu.h>
#include <linux/of_reserved_mem.h>
#include <linux/dma-direct.h> /* for bus_dma_region */
#include <linux/dma-map-ops.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <asm/errno.h>
#include "of_private.h"
/**
* of_match_device - Tell if a struct device matches an of_device_id list
* @matches: array of of_device_id match structures to search in
* @dev: the OF device structure to match against
*
* Used by a driver to check whether an platform_device present in the
* system is in its list of supported devices.
*/
const struct of_device_id *of_match_device(const struct of_device_id *matches,
const struct device *dev)
{
if (!matches || !dev->of_node || dev_of_node_reused(dev))
return NULL;
return of_match_node(matches, dev->of_node);
}
EXPORT_SYMBOL(of_match_device);
static void
of_dma_set_restricted_buffer(struct device *dev, struct device_node *np)
{
struct device_node *of_node = dev->of_node;
struct of_phandle_iterator it;
int rc, i = 0;
if (!IS_ENABLED(CONFIG_DMA_RESTRICTED_POOL))
return;
/*
* If dev->of_node doesn't exist or doesn't contain memory-region, try
* the OF node having DMA configuration.
*/
if (!of_property_present(of_node, "memory-region"))
of_node = np;
of_for_each_phandle(&it, rc, of_node, "memory-region", NULL, 0) {
/*
* There might be multiple memory regions, but only one
* restricted-dma-pool region is allowed.
*/
if (of_device_is_compatible(it.node, "restricted-dma-pool") &&
of_device_is_available(it.node)) {
if (of_reserved_mem_device_init_by_idx(dev, of_node, i))
dev_warn(dev, "failed to initialise \"restricted-dma-pool\" memory node\n");
of_node_put(it.node);
break;
}
i++;
}
}
/**
* of_dma_configure_id - Setup DMA configuration
* @dev: Device to apply DMA configuration
* @np: Pointer to OF node having DMA configuration
* @force_dma: Whether device is to be set up by of_dma_configure() even if
* DMA capability is not explicitly described by firmware.
* @id: Optional const pointer value input id
*
* Try to get devices's DMA configuration from DT and update it
* accordingly.
*
* If platform code needs to use its own special DMA configuration, it
* can use a platform bus notifier and handle BUS_NOTIFY_ADD_DEVICE events
* to fix up DMA configuration.
*/
int of_dma_configure_id(struct device *dev, struct device_node *np,
bool force_dma, const u32 *id)
{
const struct bus_dma_region *map = NULL;
struct device_node *bus_np;
u64 mask, end = 0;
bool coherent, set_map = false;
int ret;
if (dev->dma_range_map) {
dev_dbg(dev, "dma_range_map already set\n");
goto skip_map;
}
if (np == dev->of_node)
bus_np = __of_get_dma_parent(np);
else
bus_np = of_node_get(np);
ret = of_dma_get_range(bus_np, &map);
of_node_put(bus_np);
if (ret < 0) {
/*
* For legacy reasons, we have to assume some devices need
* DMA configuration regardless of whether "dma-ranges" is
* correctly specified or not.
*/
if (!force_dma)
return ret == -ENODEV ? 0 : ret;
} else {
/* Determine the overall bounds of all DMA regions */
end = dma_range_map_max(map);
set_map = true;
}
skip_map:
/*
* If @dev is expected to be DMA-capable then the bus code that created
* it should have initialised its dma_mask pointer by this point. For
* now, we'll continue the legacy behaviour of coercing it to the
* coherent mask if not, but we'll no longer do so quietly.
*/
if (!dev->dma_mask) {
dev_warn(dev, "DMA mask not set\n");
dev->dma_mask = &dev->coherent_dma_mask;
}
if (!end && dev->coherent_dma_mask)
end = dev->coherent_dma_mask;
else if (!end)
end = (1ULL << 32) - 1;
/*
* Limit coherent and dma mask based on size and default mask
* set by the driver.
*/
mask = DMA_BIT_MASK(ilog2(end) + 1);
dev->coherent_dma_mask &= mask;
*dev->dma_mask &= mask;
/* ...but only set bus limit and range map if we found valid dma-ranges earlier */
if (set_map) {
dev->bus_dma_limit = end;
dev->dma_range_map = map;
}
coherent = of_dma_is_coherent(np);
dev_dbg(dev, "device is%sdma coherent\n",
coherent ? " " : " not ");
ret = of_iommu_configure(dev, np, id);
if (ret == -EPROBE_DEFER) {
/* Don't touch range map if it wasn't set from a valid dma-ranges */
if (set_map)
dev->dma_range_map = NULL;
kfree(map);
return -EPROBE_DEFER;
}
/* Take all other IOMMU errors to mean we'll just carry on without it */
dev_dbg(dev, "device is%sbehind an iommu\n",
!ret ? " " : " not ");
arch_setup_dma_ops(dev, coherent);
if (ret)
of_dma_set_restricted_buffer(dev, np);
return 0;
}
EXPORT_SYMBOL_GPL(of_dma_configure_id);
const void *of_device_get_match_data(const struct device *dev)
{
const struct of_device_id *match;
match = of_match_device(dev->driver->of_match_table, dev);
if (!match)
return NULL;
return match->data;
}
EXPORT_SYMBOL(of_device_get_match_data);
/**
* of_device_modalias - Fill buffer with newline terminated modalias string
* @dev: Calling device
* @str: Modalias string
* @len: Size of @str
*/
ssize_t of_device_modalias(struct device *dev, char *str, ssize_t len)
{
ssize_t sl;
if (!dev || !dev->of_node || dev_of_node_reused(dev))
return -ENODEV;
sl = of_modalias(dev->of_node, str, len - 2);
if (sl < 0)
return sl;
if (sl > len - 2)
return -ENOMEM;
str[sl++] = '\n';
str[sl] = 0;
return sl;
}
EXPORT_SYMBOL_GPL(of_device_modalias);
/**
* of_device_uevent - Display OF related uevent information
* @dev: Device to display the uevent information for
* @env: Kernel object's userspace event reference to fill up
*/
void of_device_uevent(const struct device *dev, struct kobj_uevent_env *env)
{
const char *compat, *type;
struct alias_prop *app;
struct property *p;
int seen = 0;
if ((!dev) || (!dev->of_node))
return;
add_uevent_var(env, "OF_NAME=%pOFn", dev->of_node);
add_uevent_var(env, "OF_FULLNAME=%pOF", dev->of_node);
type = of_node_get_device_type(dev->of_node);
if (type)
add_uevent_var(env, "OF_TYPE=%s", type);
/* Since the compatible field can contain pretty much anything
* it's not really legal to split it out with commas. We split it
* up using a number of environment variables instead. */
of_property_for_each_string(dev->of_node, "compatible", p, compat) {
add_uevent_var(env, "OF_COMPATIBLE_%d=%s", seen, compat);
seen++;
}
add_uevent_var(env, "OF_COMPATIBLE_N=%d", seen);
seen = 0;
mutex_lock(&of_mutex);
list_for_each_entry(app, &aliases_lookup, link) {
if (dev->of_node == app->np) {
add_uevent_var(env, "OF_ALIAS_%d=%s", seen,
app->alias);
seen++;
}
}
mutex_unlock(&of_mutex);
}
EXPORT_SYMBOL_GPL(of_device_uevent);
int of_device_uevent_modalias(const struct device *dev, struct kobj_uevent_env *env)
{
int sl;
if ((!dev) || (!dev->of_node) || dev_of_node_reused(dev))
return -ENODEV;
/* Devicetree modalias is tricky, we add it in 2 steps */
if (add_uevent_var(env, "MODALIAS="))
return -ENOMEM;
sl = of_modalias(dev->of_node, &env->buf[env->buflen-1],
sizeof(env->buf) - env->buflen);
if (sl < 0)
return sl;
if (sl >= (sizeof(env->buf) - env->buflen))
return -ENOMEM;
env->buflen += sl;
return 0;
}
EXPORT_SYMBOL_GPL(of_device_uevent_modalias);
/**
* of_device_make_bus_id - Use the device node data to assign a unique name
* @dev: pointer to device structure that is linked to a device tree node
*
* This routine will first try using the translated bus address to
* derive a unique name. If it cannot, then it will prepend names from
* parent nodes until a unique name can be derived.
*/
void of_device_make_bus_id(struct device *dev)
{
struct device_node *node = dev->of_node;
const __be32 *reg;
u64 addr;
u32 mask;
/* Construct the name, using parent nodes if necessary to ensure uniqueness */
while (node->parent) {
/*
* If the address can be translated, then that is as much
* uniqueness as we need. Make it the first component and return
*/
reg = of_get_property(node, "reg", NULL);
if (reg && (addr = of_translate_address(node, reg)) != OF_BAD_ADDR) {
if (!of_property_read_u32(node, "mask", &mask))
dev_set_name(dev, dev_name(dev) ? "%llx.%x.%pOFn:%s" : "%llx.%x.%pOFn",
addr, ffs(mask) - 1, node, dev_name(dev));
else
dev_set_name(dev, dev_name(dev) ? "%llx.%pOFn:%s" : "%llx.%pOFn",
addr, node, dev_name(dev));
return;
}
/* format arguments only used if dev_name() resolves to NULL */
dev_set_name(dev, dev_name(dev) ? "%s:%s" : "%s",
kbasename(node->full_name), dev_name(dev));
node = node->parent;
}
}
EXPORT_SYMBOL_GPL(of_device_make_bus_id);
File diff suppressed because it is too large Load Diff
+13
View File
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: GPL-2.0-only
/dts-v1/;
/ {
/*
* #address-cells/#size-cells are required properties at root node.
* Use 2 cells for both address cells and size cells in order to fully
* support 64-bit addresses and sizes on systems using this empty root
* node.
*/
#address-cells = <0x02>;
#size-cells = <0x02>;
};
File diff suppressed because it is too large Load Diff
+236
View File
@@ -0,0 +1,236 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* FDT Address translation based on u-boot fdt_support.c which in turn was
* based on the kernel unflattened DT address translation code.
*
* (C) Copyright 2007
* Gerald Van Baren, Custom IDEAS, vanbaren@cideas.com
*
* Copyright 2010-2011 Freescale Semiconductor, Inc.
*/
#define pr_fmt(fmt) "OF: fdt: " fmt
#include <linux/kernel.h>
#include <linux/libfdt.h>
#include <linux/of.h>
#include <linux/of_fdt.h>
#include <linux/sizes.h>
/* Uncomment me to enable of_dump_addr() debugging output */
// #define DEBUG
#include "of_private.h"
/* Callbacks for bus specific translators */
struct of_bus {
void (*count_cells)(const void *blob, int parentoffset,
int *addrc, int *sizec);
u64 (*map)(__be32 *addr, const __be32 *range,
int na, int ns, int pna);
int (*translate)(__be32 *addr, u64 offset, int na);
};
/* Default translator (generic bus) */
static void __init fdt_bus_default_count_cells(const void *blob, int parentoffset,
int *addrc, int *sizec)
{
const __be32 *prop;
if (addrc) {
prop = fdt_getprop(blob, parentoffset, "#address-cells", NULL);
if (prop)
*addrc = be32_to_cpup(prop);
else
*addrc = -1;
}
if (sizec) {
prop = fdt_getprop(blob, parentoffset, "#size-cells", NULL);
if (prop)
*sizec = be32_to_cpup(prop);
else
*sizec = -1;
}
}
static u64 __init fdt_bus_default_map(__be32 *addr, const __be32 *range,
int na, int ns, int pna)
{
u64 cp, s, da;
cp = of_read_number(range, na);
s = of_read_number(range + na + pna, ns);
da = of_read_number(addr, na);
pr_debug("default map, cp=%llx, s=%llx, da=%llx\n",
cp, s, da);
if (da < cp || da >= (cp + s))
return OF_BAD_ADDR;
return da - cp;
}
static int __init fdt_bus_default_translate(__be32 *addr, u64 offset, int na)
{
u64 a = of_read_number(addr, na);
memset(addr, 0, na * 4);
a += offset;
if (na > 1)
addr[na - 2] = cpu_to_fdt32(a >> 32);
addr[na - 1] = cpu_to_fdt32(a & 0xffffffffu);
return 0;
}
/* Array of bus specific translators */
static const struct of_bus of_busses[] __initconst = {
/* Default */
{
.count_cells = fdt_bus_default_count_cells,
.map = fdt_bus_default_map,
.translate = fdt_bus_default_translate,
},
};
static int __init fdt_translate_one(const void *blob, int parent,
const struct of_bus *bus,
const struct of_bus *pbus, __be32 *addr,
int na, int ns, int pna, const char *rprop)
{
const __be32 *ranges;
int rlen;
int rone;
u64 offset = OF_BAD_ADDR;
ranges = fdt_getprop(blob, parent, rprop, &rlen);
if (!ranges)
return 1;
if (rlen == 0) {
offset = of_read_number(addr, na);
memset(addr, 0, pna * 4);
pr_debug("empty ranges, 1:1 translation\n");
goto finish;
}
pr_debug("walking ranges...\n");
/* Now walk through the ranges */
rlen /= 4;
rone = na + pna + ns;
for (; rlen >= rone; rlen -= rone, ranges += rone) {
offset = bus->map(addr, ranges, na, ns, pna);
if (offset != OF_BAD_ADDR)
break;
}
if (offset == OF_BAD_ADDR) {
pr_debug("not found !\n");
return 1;
}
memcpy(addr, ranges + na, 4 * pna);
finish:
of_dump_addr("parent translation for:", addr, pna);
pr_debug("with offset: %llx\n", offset);
/* Translate it into parent bus space */
return pbus->translate(addr, offset, pna);
}
/*
* Translate an address from the device-tree into a CPU physical address,
* this walks up the tree and applies the various bus mappings on the
* way.
*
* Note: We consider that crossing any level with #size-cells == 0 to mean
* that translation is impossible (that is we are not dealing with a value
* that can be mapped to a cpu physical address). This is not really specified
* that way, but this is traditionally the way IBM at least do things
*/
static u64 __init fdt_translate_address(const void *blob, int node_offset)
{
int parent, len;
const struct of_bus *bus, *pbus;
const __be32 *reg;
__be32 addr[OF_MAX_ADDR_CELLS];
int na, ns, pna, pns;
u64 result = OF_BAD_ADDR;
pr_debug("** translation for device %s **\n",
fdt_get_name(blob, node_offset, NULL));
reg = fdt_getprop(blob, node_offset, "reg", &len);
if (!reg) {
pr_err("warning: device tree node '%s' has no address.\n",
fdt_get_name(blob, node_offset, NULL));
goto bail;
}
/* Get parent & match bus type */
parent = fdt_parent_offset(blob, node_offset);
if (parent < 0)
goto bail;
bus = &of_busses[0];
/* Cound address cells & copy address locally */
bus->count_cells(blob, parent, &na, &ns);
if (!OF_CHECK_COUNTS(na, ns)) {
pr_err("Bad cell count for %s\n",
fdt_get_name(blob, node_offset, NULL));
goto bail;
}
memcpy(addr, reg, na * 4);
pr_debug("bus (na=%d, ns=%d) on %s\n",
na, ns, fdt_get_name(blob, parent, NULL));
of_dump_addr("translating address:", addr, na);
/* Translate */
for (;;) {
/* Switch to parent bus */
node_offset = parent;
parent = fdt_parent_offset(blob, node_offset);
/* If root, we have finished */
if (parent < 0) {
pr_debug("reached root node\n");
result = of_read_number(addr, na);
break;
}
/* Get new parent bus and counts */
pbus = &of_busses[0];
pbus->count_cells(blob, parent, &pna, &pns);
if (!OF_CHECK_COUNTS(pna, pns)) {
pr_err("Bad cell count for %s\n",
fdt_get_name(blob, node_offset, NULL));
break;
}
pr_debug("parent bus (na=%d, ns=%d) on %s\n",
pna, pns, fdt_get_name(blob, parent, NULL));
/* Apply bus translation */
if (fdt_translate_one(blob, node_offset, bus, pbus,
addr, na, ns, pna, "ranges"))
break;
/* Complete the move up one level */
na = pna;
ns = pns;
bus = pbus;
of_dump_addr("one level translation:", addr, na);
}
bail:
return result;
}
/**
* of_flat_dt_translate_address - translate DT addr into CPU phys addr
* @node: node in the flat blob
*/
u64 __init of_flat_dt_translate_address(unsigned long node)
{
return fdt_translate_address(initial_boot_params, node);
}
+906
View File
@@ -0,0 +1,906 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Derived from arch/i386/kernel/irq.c
* Copyright (C) 1992 Linus Torvalds
* Adapted from arch/i386 by Gary Thomas
* Copyright (C) 1995-1996 Gary Thomas (gdt@linuxppc.org)
* Updated and modified by Cort Dougan <cort@fsmlabs.com>
* Copyright (C) 1996-2001 Cort Dougan
* Adapted for Power Macintosh by Paul Mackerras
* Copyright (C) 1996 Paul Mackerras (paulus@cs.anu.edu.au)
*
* This file contains the code used to make IRQ descriptions in the
* device tree to actual irq numbers on an interrupt controller
* driver.
*/
#define pr_fmt(fmt) "OF: " fmt
#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/string.h>
#include <linux/slab.h>
#include "of_private.h"
/**
* irq_of_parse_and_map - Parse and map an interrupt into linux virq space
* @dev: Device node of the device whose interrupt is to be mapped
* @index: Index of the interrupt to map
*
* This function is a wrapper that chains of_irq_parse_one() and
* irq_create_of_mapping() to make things easier to callers
*/
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
{
struct of_phandle_args oirq;
unsigned int ret;
if (of_irq_parse_one(dev, index, &oirq))
return 0;
ret = irq_create_of_mapping(&oirq);
of_node_put(oirq.np);
return ret;
}
EXPORT_SYMBOL_GPL(irq_of_parse_and_map);
/**
* of_irq_find_parent - Given a device node, find its interrupt parent node
* @child: pointer to device node
*
* Return: A pointer to the interrupt parent node with refcount increased
* or NULL if the interrupt parent could not be determined.
*/
struct device_node *of_irq_find_parent(struct device_node *child)
{
struct device_node *p;
phandle parent;
if (!of_node_get(child))
return NULL;
do {
if (of_property_read_u32(child, "interrupt-parent", &parent)) {
p = of_get_parent(child);
} else {
if (of_irq_workarounds & OF_IMAP_NO_PHANDLE)
p = of_node_get(of_irq_dflt_pic);
else
p = of_find_node_by_phandle(parent);
}
of_node_put(child);
child = p;
} while (p && of_get_property(p, "#interrupt-cells", NULL) == NULL);
return p;
}
EXPORT_SYMBOL_GPL(of_irq_find_parent);
/*
* These interrupt controllers abuse interrupt-map for unspeakable
* reasons and rely on the core code to *ignore* it (the drivers do
* their own parsing of the property). The PAsemi entry covers a
* non-sensical interrupt-map that is better left ignored.
*
* If you think of adding to the list for something *new*, think
* again. There is a high chance that you will be sent back to the
* drawing board.
*/
static const char * const of_irq_imap_abusers[] = {
"CBEA,platform-spider-pic",
"sti,platform-spider-pic",
"realtek,rtl-intc",
"fsl,ls1021a-extirq",
"fsl,ls1043a-extirq",
"fsl,ls1088a-extirq",
"renesas,rza1-irqc",
"pasemi,rootbus",
NULL,
};
const __be32 *of_irq_parse_imap_parent(const __be32 *imap, int len, struct of_phandle_args *out_irq)
{
u32 intsize, addrsize;
struct device_node *np;
/* Get the interrupt parent */
if (of_irq_workarounds & OF_IMAP_NO_PHANDLE)
np = of_node_get(of_irq_dflt_pic);
else
np = of_find_node_by_phandle(be32_to_cpup(imap));
imap++;
len--;
/* Check if not found */
if (!np) {
pr_debug(" -> imap parent not found !\n");
return NULL;
}
/* Get #interrupt-cells and #address-cells of new parent */
if (of_property_read_u32(np, "#interrupt-cells",
&intsize)) {
pr_debug(" -> parent lacks #interrupt-cells!\n");
of_node_put(np);
return NULL;
}
if (of_property_read_u32(np, "#address-cells",
&addrsize))
addrsize = 0;
pr_debug(" -> intsize=%d, addrsize=%d\n",
intsize, addrsize);
/* Check for malformed properties */
if (WARN_ON(addrsize + intsize > MAX_PHANDLE_ARGS)
|| (len < (addrsize + intsize))) {
of_node_put(np);
return NULL;
}
pr_debug(" -> imaplen=%d\n", len);
imap += addrsize + intsize;
out_irq->np = np;
for (int i = 0; i < intsize; i++)
out_irq->args[i] = be32_to_cpup(imap - intsize + i);
out_irq->args_count = intsize;
return imap;
}
int of_imap_parser_init(struct of_imap_parser *parser, struct device_node *node,
struct of_imap_item *item)
{
int imaplen;
u32 tmp;
int ret;
/*
* parent_offset is the offset where the parent part is starting.
* In other words, the offset where the parent interrupt controller
* phandle is present.
*
* Compute this offset (child #interrupt-cells + child #address-cells)
*/
parser->parent_offset = of_bus_n_addr_cells(node);
ret = of_property_read_u32(node, "#interrupt-cells", &tmp);
if (ret)
return ret;
parser->parent_offset += tmp;
if (WARN(parser->parent_offset > ARRAY_SIZE(item->child_imap),
"child part size = %u, cannot fit in array of %zu items",
parser->parent_offset, ARRAY_SIZE(item->child_imap)))
return -EINVAL;
parser->imap = of_get_property(node, "interrupt-map", &imaplen);
if (!parser->imap)
return -ENOENT;
imaplen /= sizeof(*parser->imap);
parser->imap_end = parser->imap + imaplen;
memset(item, 0, sizeof(*item));
item->child_imap_count = parser->parent_offset;
return 0;
}
EXPORT_SYMBOL_GPL(of_imap_parser_init);
struct of_imap_item *of_imap_parser_one(struct of_imap_parser *parser,
struct of_imap_item *item)
{
const __be32 *imap_parent, *imap_next;
int i;
/* Release previously get parent node */
of_node_put(item->parent_args.np);
if (parser->imap + parser->parent_offset + 1 >= parser->imap_end)
return NULL;
imap_parent = parser->imap + parser->parent_offset;
imap_next = of_irq_parse_imap_parent(imap_parent,
parser->imap_end - imap_parent,
&item->parent_args);
if (!imap_next)
return NULL;
for (i = 0; i < parser->parent_offset; i++)
item->child_imap[i] = be32_to_cpu(*(parser->imap + i));
parser->imap = imap_next;
return item;
}
EXPORT_SYMBOL_GPL(of_imap_parser_one);
/**
* of_irq_parse_raw - Low level interrupt tree parsing
* @addr: address specifier (start of "reg" property of the device) in be32 format
* @out_irq: structure of_phandle_args updated by this function
*
* This function is a low-level interrupt tree walking function. It
* can be used to do a partial walk with synthesized reg and interrupts
* properties, for example when resolving PCI interrupts when no device
* node exist for the parent. It takes an interrupt specifier structure as
* input, walks the tree looking for any interrupt-map properties, translates
* the specifier for each map, and then returns the translated map.
*
* Return: 0 on success and a negative number on error
*
* Note: refcount of node @out_irq->np is increased by 1 on success.
*/
int of_irq_parse_raw(const __be32 *addr, struct of_phandle_args *out_irq)
{
struct device_node *ipar, *tnode, *old = NULL;
__be32 initial_match_array[MAX_PHANDLE_ARGS];
const __be32 *match_array = initial_match_array;
const __be32 *tmp, dummy_imask[] = { [0 ... (MAX_PHANDLE_ARGS - 1)] = cpu_to_be32(~0) };
u32 intsize = 1, addrsize;
int i, rc = -EINVAL;
#ifdef DEBUG
of_print_phandle_args("of_irq_parse_raw: ", out_irq);
#endif
ipar = of_node_get(out_irq->np);
/* First get the #interrupt-cells property of the current cursor
* that tells us how to interpret the passed-in intspec. If there
* is none, we are nice and just walk up the tree
*/
do {
if (!of_property_read_u32(ipar, "#interrupt-cells", &intsize))
break;
tnode = ipar;
ipar = of_irq_find_parent(ipar);
of_node_put(tnode);
} while (ipar);
if (ipar == NULL) {
pr_debug(" -> no parent found !\n");
goto fail;
}
pr_debug("of_irq_parse_raw: ipar=%pOF, size=%d\n", ipar, intsize);
if (out_irq->args_count != intsize)
goto fail;
/* Look for this #address-cells. We have to implement the old linux
* trick of looking for the parent here as some device-trees rely on it
*/
old = of_node_get(ipar);
do {
tmp = of_get_property(old, "#address-cells", NULL);
tnode = of_get_parent(old);
of_node_put(old);
old = tnode;
} while (old && tmp == NULL);
of_node_put(old);
old = NULL;
addrsize = (tmp == NULL) ? 2 : be32_to_cpu(*tmp);
pr_debug(" -> addrsize=%d\n", addrsize);
/* Range check so that the temporary buffer doesn't overflow */
if (WARN_ON(addrsize + intsize > MAX_PHANDLE_ARGS)) {
rc = -EFAULT;
goto fail;
}
/* Precalculate the match array - this simplifies match loop */
for (i = 0; i < addrsize; i++)
initial_match_array[i] = addr ? addr[i] : 0;
for (i = 0; i < intsize; i++)
initial_match_array[addrsize + i] = cpu_to_be32(out_irq->args[i]);
/* Now start the actual "proper" walk of the interrupt tree */
while (ipar != NULL) {
int imaplen, match;
const __be32 *imap, *oldimap, *imask;
struct device_node *newpar;
/*
* Now check if cursor is an interrupt-controller and
* if it is then we are done, unless there is an
* interrupt-map which takes precedence except on one
* of these broken platforms that want to parse
* interrupt-map themselves for $reason.
*/
bool intc = of_property_read_bool(ipar, "interrupt-controller");
imap = of_get_property(ipar, "interrupt-map", &imaplen);
if (intc &&
(!imap || of_device_compatible_match(ipar, of_irq_imap_abusers))) {
pr_debug(" -> got it !\n");
return 0;
}
/*
* interrupt-map parsing does not work without a reg
* property when #address-cells != 0
*/
if (addrsize && !addr) {
pr_debug(" -> no reg passed in when needed !\n");
goto fail;
}
/* No interrupt map, check for an interrupt parent */
if (imap == NULL) {
pr_debug(" -> no map, getting parent\n");
newpar = of_irq_find_parent(ipar);
goto skiplevel;
}
imaplen /= sizeof(u32);
/* Look for a mask */
imask = of_get_property(ipar, "interrupt-map-mask", NULL);
if (!imask)
imask = dummy_imask;
/* Parse interrupt-map */
match = 0;
while (imaplen > (addrsize + intsize + 1)) {
/* Compare specifiers */
match = 1;
for (i = 0; i < (addrsize + intsize); i++, imaplen--)
match &= !((match_array[i] ^ *imap++) & imask[i]);
pr_debug(" -> match=%d (imaplen=%d)\n", match, imaplen);
oldimap = imap;
imap = of_irq_parse_imap_parent(oldimap, imaplen, out_irq);
if (!imap)
goto fail;
match &= of_device_is_available(out_irq->np);
if (match)
break;
of_node_put(out_irq->np);
imaplen -= imap - oldimap;
pr_debug(" -> imaplen=%d\n", imaplen);
}
if (!match)
goto fail;
/*
* Successfully parsed an interrupt-map translation; copy new
* interrupt specifier into the out_irq structure
*/
match_array = oldimap + 1;
newpar = out_irq->np;
intsize = out_irq->args_count;
addrsize = (imap - match_array) - intsize;
if (ipar == newpar) {
/*
* We got @ipar's refcount, but the refcount was
* gotten again by of_irq_parse_imap_parent() via its
* alias @newpar.
*/
of_node_put(ipar);
pr_debug("%pOF interrupt-map entry to self\n", ipar);
return 0;
}
skiplevel:
/* Iterate again with new parent */
pr_debug(" -> new parent: %pOF\n", newpar);
of_node_put(ipar);
ipar = newpar;
newpar = NULL;
}
rc = -ENOENT; /* No interrupt-map found */
fail:
of_node_put(ipar);
return rc;
}
EXPORT_SYMBOL_GPL(of_irq_parse_raw);
/**
* of_irq_parse_one - Resolve an interrupt for a device
* @device: the device whose interrupt is to be resolved
* @index: index of the interrupt to resolve
* @out_irq: structure of_phandle_args filled by this function
*
* This function resolves an interrupt for a node by walking the interrupt tree,
* finding which interrupt controller node it is attached to, and returning the
* interrupt specifier that can be used to retrieve a Linux IRQ number.
*
* Note: refcount of node @out_irq->np is increased by 1 on success.
*/
int of_irq_parse_one(struct device_node *device, int index, struct of_phandle_args *out_irq)
{
struct device_node __free(device_node) *p = NULL;
const __be32 *addr;
u32 intsize;
int i, res, addr_len;
__be32 addr_buf[3] = { 0 };
pr_debug("of_irq_parse_one: dev=%pOF, index=%d\n", device, index);
/* OldWorld mac stuff is "special", handle out of line */
if (of_irq_workarounds & OF_IMAP_OLDWORLD_MAC)
return of_irq_parse_oldworld(device, index, out_irq);
/* Get the reg property (if any) */
addr_len = 0;
addr = of_get_property(device, "reg", &addr_len);
/* Prevent out-of-bounds read in case of longer interrupt parent address size */
if (addr_len > sizeof(addr_buf))
addr_len = sizeof(addr_buf);
if (addr)
memcpy(addr_buf, addr, addr_len);
/* Try the new-style interrupts-extended first */
res = of_parse_phandle_with_args(device, "interrupts-extended",
"#interrupt-cells", index, out_irq);
if (!res) {
p = out_irq->np;
} else {
/* Look for the interrupt parent. */
p = of_irq_find_parent(device);
/* Get size of interrupt specifier */
if (!p || of_property_read_u32(p, "#interrupt-cells", &intsize))
return -EINVAL;
pr_debug(" parent=%pOF, intsize=%d\n", p, intsize);
/* Copy intspec into irq structure */
out_irq->np = p;
out_irq->args_count = intsize;
for (i = 0; i < intsize; i++) {
res = of_property_read_u32_index(device, "interrupts",
(index * intsize) + i,
out_irq->args + i);
if (res)
return res;
}
pr_debug(" intspec=%d\n", *out_irq->args);
}
/* Check if there are any interrupt-map translations to process */
return of_irq_parse_raw(addr_buf, out_irq);
}
EXPORT_SYMBOL_GPL(of_irq_parse_one);
/**
* of_irq_to_resource - Decode a node's IRQ and return it as a resource
* @dev: pointer to device tree node
* @index: zero-based index of the irq
* @r: pointer to resource structure to return result into.
*/
int of_irq_to_resource(struct device_node *dev, int index, struct resource *r)
{
int irq = of_irq_get(dev, index);
if (irq < 0)
return irq;
/* Only dereference the resource if both the
* resource and the irq are valid. */
if (r && irq) {
const char *name = NULL;
memset(r, 0, sizeof(*r));
/*
* Get optional "interrupt-names" property to add a name
* to the resource.
*/
of_property_read_string_index(dev, "interrupt-names", index,
&name);
*r = DEFINE_RES_IRQ_NAMED(irq, name ?: of_node_full_name(dev));
r->flags |= irq_get_trigger_type(irq);
}
return irq;
}
EXPORT_SYMBOL_GPL(of_irq_to_resource);
/**
* of_irq_get - Decode a node's IRQ and return it as a Linux IRQ number
* @dev: pointer to device tree node
* @index: zero-based index of the IRQ
*
* Return: Linux IRQ number on success, or 0 on the IRQ mapping failure, or
* -EPROBE_DEFER if the IRQ domain is not yet created, or error code in case
* of any other failure.
*/
int of_irq_get(struct device_node *dev, int index)
{
int rc;
struct of_phandle_args oirq;
struct irq_domain *domain;
rc = of_irq_parse_one(dev, index, &oirq);
if (rc)
return rc;
domain = irq_find_host(oirq.np);
if (!domain) {
rc = -EPROBE_DEFER;
goto out;
}
rc = irq_create_of_mapping(&oirq);
out:
of_node_put(oirq.np);
return rc;
}
EXPORT_SYMBOL_GPL(of_irq_get);
const struct cpumask *of_irq_get_affinity(struct device_node *dev, int index)
{
struct of_phandle_args oirq;
struct irq_fwspec_info info;
struct irq_fwspec fwspec;
int rc;
rc = of_irq_parse_one(dev, index, &oirq);
if (rc)
return NULL;
of_phandle_args_to_fwspec(oirq.np, oirq.args, oirq.args_count,
&fwspec);
if (irq_populate_fwspec_info(&fwspec, &info))
return NULL;
return info.affinity;
}
/**
* of_irq_get_byname - Decode a node's IRQ and return it as a Linux IRQ number
* @dev: pointer to device tree node
* @name: IRQ name
*
* Return: Linux IRQ number on success, or 0 on the IRQ mapping failure, or
* -EPROBE_DEFER if the IRQ domain is not yet created, or error code in case
* of any other failure.
*/
int of_irq_get_byname(struct device_node *dev, const char *name)
{
int index;
if (unlikely(!name))
return -EINVAL;
index = of_property_match_string(dev, "interrupt-names", name);
if (index < 0)
return index;
return of_irq_get(dev, index);
}
EXPORT_SYMBOL_GPL(of_irq_get_byname);
/**
* of_irq_count - Count the number of IRQs a node uses
* @dev: pointer to device tree node
*/
int of_irq_count(struct device_node *dev)
{
struct of_phandle_args irq;
int nr = 0;
while (of_irq_parse_one(dev, nr, &irq) == 0) {
of_node_put(irq.np);
nr++;
}
return nr;
}
EXPORT_SYMBOL_GPL(of_irq_count);
/**
* of_irq_to_resource_table - Fill in resource table with node's IRQ info
* @dev: pointer to device tree node
* @res: array of resources to fill in
* @nr_irqs: the number of IRQs (and upper bound for num of @res elements)
*
* Return: The size of the filled in table (up to @nr_irqs).
*/
int of_irq_to_resource_table(struct device_node *dev, struct resource *res,
int nr_irqs)
{
int i;
for (i = 0; i < nr_irqs; i++, res++)
if (of_irq_to_resource(dev, i, res) <= 0)
break;
return i;
}
EXPORT_SYMBOL_GPL(of_irq_to_resource_table);
struct of_intc_desc {
struct list_head list;
of_irq_init_cb_t irq_init_cb;
struct device_node *dev;
struct device_node *interrupt_parent;
};
/**
* of_irq_init - Scan and init matching interrupt controllers in DT
* @matches: 0 terminated array of nodes to match and init function to call
*
* This function scans the device tree for matching interrupt controller nodes,
* and calls their initialization functions in order with parents first.
*/
void __init of_irq_init(const struct of_device_id *matches)
{
const struct of_device_id *match;
struct device_node *np, *parent = NULL;
struct of_intc_desc *desc, *temp_desc;
struct list_head intc_desc_list, intc_parent_list;
INIT_LIST_HEAD(&intc_desc_list);
INIT_LIST_HEAD(&intc_parent_list);
for_each_matching_node_and_match(np, matches, &match) {
if (!of_property_read_bool(np, "interrupt-controller") ||
!of_device_is_available(np))
continue;
if (WARN(!match->data, "of_irq_init: no init function for %s\n",
match->compatible))
continue;
/*
* Here, we allocate and populate an of_intc_desc with the node
* pointer, interrupt-parent device_node etc.
*/
desc = kzalloc_obj(*desc);
if (!desc) {
of_node_put(np);
goto err;
}
desc->irq_init_cb = match->data;
desc->dev = of_node_get(np);
/*
* interrupts-extended can reference multiple parent domains.
* Arbitrarily pick the first one; assume any other parents
* are the same distance away from the root irq controller.
*/
desc->interrupt_parent = of_parse_phandle(np, "interrupts-extended", 0);
if (!desc->interrupt_parent && of_property_present(np, "interrupts"))
desc->interrupt_parent = of_irq_find_parent(np);
else if (!desc->interrupt_parent)
desc->interrupt_parent = of_parse_phandle(np, "interrupt-parent", 0);
if (desc->interrupt_parent == np) {
of_node_put(desc->interrupt_parent);
desc->interrupt_parent = NULL;
}
list_add_tail(&desc->list, &intc_desc_list);
}
/*
* The root irq controller is the one without an interrupt-parent.
* That one goes first, followed by the controllers that reference it,
* followed by the ones that reference the 2nd level controllers, etc.
*/
while (!list_empty(&intc_desc_list)) {
/*
* Process all controllers with the current 'parent'.
* First pass will be looking for NULL as the parent.
* The assumption is that NULL parent means a root controller.
*/
list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
int ret;
if (desc->interrupt_parent != parent)
continue;
list_del(&desc->list);
of_node_set_flag(desc->dev, OF_POPULATED);
pr_debug("of_irq_init: init %pOF (%p), parent %p\n",
desc->dev,
desc->dev, desc->interrupt_parent);
ret = desc->irq_init_cb(desc->dev,
desc->interrupt_parent);
if (ret) {
pr_err("%s: Failed to init %pOF (%p), parent %p\n",
__func__, desc->dev, desc->dev,
desc->interrupt_parent);
of_node_clear_flag(desc->dev, OF_POPULATED);
of_node_put(desc->interrupt_parent);
of_node_put(desc->dev);
kfree(desc);
continue;
}
/*
* This one is now set up; add it to the parent list so
* its children can get processed in a subsequent pass.
*/
list_add_tail(&desc->list, &intc_parent_list);
}
/* Get the next pending parent that might have children */
desc = list_first_entry_or_null(&intc_parent_list,
typeof(*desc), list);
if (!desc) {
pr_err("of_irq_init: children remain, but no parents\n");
break;
}
list_del(&desc->list);
parent = desc->dev;
kfree(desc);
}
list_for_each_entry_safe(desc, temp_desc, &intc_parent_list, list) {
list_del(&desc->list);
kfree(desc);
}
err:
list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
list_del(&desc->list);
of_node_put(desc->interrupt_parent);
of_node_put(desc->dev);
kfree(desc);
}
}
static int of_check_msi_parent(struct device_node *dev_node, struct device_node **msi_node)
{
struct of_phandle_args msi_spec;
int ret;
/*
* An msi-parent phandle with a missing or == 0 #msi-cells
* property identifies a 1:1 ID translation mapping.
*
* Set the msi controller node if the firmware matches this
* condition.
*/
ret = of_parse_phandle_with_optional_args(dev_node, "msi-parent", "#msi-cells",
0, &msi_spec);
if (ret)
return ret;
if ((*msi_node && *msi_node != msi_spec.np) || msi_spec.args_count != 0)
ret = -EINVAL;
if (!ret) {
/* Return with a node reference held */
*msi_node = msi_spec.np;
return 0;
}
of_node_put(msi_spec.np);
return ret;
}
/**
* of_msi_xlate - map a MSI ID and find relevant MSI controller node
* @dev: device for which the mapping is to be done.
* @msi_np: Pointer to target MSI controller node, or NULL if the caller
* only needs the translated ID without receiving the controller node.
* If non-NULL and pointing to a non-NULL node, only entries targeting
* that node will be matched. If non-NULL and pointing to NULL, it will
* receive the first matching target node with a reference held.
* @id_in: Device ID.
*
* Walk up the device hierarchy looking for devices with a "msi-map"
* or "msi-parent" property. If found, apply the mapping to @id_in.
*
* Returns: The mapped MSI id.
*/
u32 of_msi_xlate(struct device *dev, struct device_node **msi_np, u32 id_in)
{
struct device *parent_dev;
u32 id_out = id_in;
/*
* Walk up the device parent links looking for one with a
* "msi-map" or an "msi-parent" property.
*/
for (parent_dev = dev; parent_dev; parent_dev = parent_dev->parent) {
struct of_phandle_args msi_spec = {};
if (!of_map_msi_id(parent_dev->of_node, id_in, msi_np, &msi_spec)) {
if (msi_spec.np) {
/* msi-map matched: use the translated ID and target node */
if (msi_spec.args_count > 0)
id_out = msi_spec.args[0];
if (msi_np && !*msi_np)
*msi_np = of_node_get(msi_spec.np);
of_node_put(msi_spec.np);
}
/* msi-map present but no match → stop walking */
break;
}
/* -ENODEV: msi-map absent → check for msi-parent */
if (msi_np && !of_check_msi_parent(parent_dev->of_node, msi_np))
break;
}
return id_out;
}
EXPORT_SYMBOL_GPL(of_msi_xlate);
/**
* of_msi_map_get_device_domain - Use msi-map to find the relevant MSI domain
* @dev: device for which the mapping is to be done.
* @id: Device ID.
* @bus_token: Bus token
*
* Walk up the device hierarchy looking for devices with a "msi-map"
* property.
*
* Returns: the MSI domain for this device (or NULL on failure)
*/
struct irq_domain *of_msi_map_get_device_domain(struct device *dev, u32 id,
u32 bus_token)
{
struct device_node *np = NULL;
struct irq_domain *d;
of_msi_xlate(dev, &np, id);
d = irq_find_matching_host(np, bus_token);
of_node_put(np);
return d;
}
/**
* of_msi_get_domain - Use msi-parent to find the relevant MSI domain
* @dev: device for which the domain is requested
* @np: device node for @dev
* @token: bus type for this domain
*
* Parse the msi-parent property and returns the corresponding MSI domain.
*
* Returns: the MSI domain for this device (or NULL on failure).
*/
struct irq_domain *of_msi_get_domain(struct device *dev,
const struct device_node *np,
enum irq_domain_bus_token token)
{
struct of_phandle_iterator it;
struct irq_domain *d;
int err;
of_for_each_phandle(&it, err, np, "msi-parent", "#msi-cells", 0) {
d = irq_find_matching_host(it.node, token);
if (d) {
of_node_put(it.node);
return d;
}
}
return NULL;
}
EXPORT_SYMBOL_GPL(of_msi_get_domain);
/**
* of_msi_configure - Set the msi_domain field of a device
* @dev: device structure to associate with an MSI irq domain
* @np: device node for that device
*/
void of_msi_configure(struct device *dev, const struct device_node *np)
{
dev_set_msi_domain(dev,
of_msi_get_domain(dev, np, DOMAIN_BUS_PLATFORM_MSI));
}
EXPORT_SYMBOL_GPL(of_msi_configure);
+526
View File
@@ -0,0 +1,526 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2020 Arm Limited
*
* Based on arch/arm64/kernel/machine_kexec_file.c:
* Copyright (C) 2018 Linaro Limited
*
* And arch/powerpc/kexec/file_load.c:
* Copyright (C) 2016 IBM Corporation
*/
#include <linux/ima.h>
#include <linux/kernel.h>
#include <linux/kexec.h>
#include <linux/memblock.h>
#include <linux/libfdt.h>
#include <linux/of.h>
#include <linux/of_fdt.h>
#include <linux/random.h>
#include <linux/slab.h>
#include <linux/types.h>
#define RNG_SEED_SIZE 128
/*
* Additional space needed for the FDT buffer so that we can add initrd,
* bootargs, kaslr-seed, rng-seed, useable-memory-range and elfcorehdr.
*/
#define FDT_EXTRA_SPACE 0x1000
/**
* fdt_find_and_del_mem_rsv - delete memory reservation with given address and size
*
* @fdt: Flattened device tree for the current kernel.
* @start: Starting address of the reserved memory.
* @size: Size of the reserved memory.
*
* Return: 0 on success, or negative errno on error.
*/
static int fdt_find_and_del_mem_rsv(void *fdt, unsigned long start, unsigned long size)
{
int i, ret, num_rsvs = fdt_num_mem_rsv(fdt);
for (i = 0; i < num_rsvs; i++) {
u64 rsv_start, rsv_size;
ret = fdt_get_mem_rsv(fdt, i, &rsv_start, &rsv_size);
if (ret) {
pr_err("Malformed device tree.\n");
return -EINVAL;
}
if (rsv_start == start && rsv_size == size) {
ret = fdt_del_mem_rsv(fdt, i);
if (ret) {
pr_err("Error deleting device tree reservation.\n");
return -EINVAL;
}
return 0;
}
}
return -ENOENT;
}
/**
* get_addr_size_cells - Get address and size of root node
*
* @addr_cells: Return address of the root node
* @size_cells: Return size of the root node
*
* Return: 0 on success, or negative errno on error.
*/
static int get_addr_size_cells(int *addr_cells, int *size_cells)
{
struct device_node *root;
root = of_find_node_by_path("/");
if (!root)
return -EINVAL;
*addr_cells = of_n_addr_cells(root);
*size_cells = of_n_size_cells(root);
of_node_put(root);
return 0;
}
/**
* do_get_kexec_buffer - Get address and size of device tree property
*
* @prop: Device tree property
* @len: Size of @prop
* @addr: Return address of the node
* @size: Return size of the node
*
* Return: 0 on success, or negative errno on error.
*/
static int do_get_kexec_buffer(const void *prop, int len, unsigned long *addr,
size_t *size)
{
int ret, addr_cells, size_cells;
ret = get_addr_size_cells(&addr_cells, &size_cells);
if (ret)
return ret;
if (len < 4 * (addr_cells + size_cells))
return -ENOENT;
*addr = of_read_number(prop, addr_cells);
*size = of_read_number(prop + 4 * addr_cells, size_cells);
return 0;
}
#ifdef CONFIG_HAVE_IMA_KEXEC
/**
* ima_get_kexec_buffer - get IMA buffer from the previous kernel
* @addr: On successful return, set to point to the buffer contents.
* @size: On successful return, set to the buffer size.
*
* Return: 0 on success, negative errno on error.
*/
int __init ima_get_kexec_buffer(void **addr, size_t *size)
{
int ret, len;
unsigned long tmp_addr;
size_t tmp_size;
const void *prop;
prop = of_get_property(of_chosen, "linux,ima-kexec-buffer", &len);
if (!prop)
return -ENOENT;
ret = do_get_kexec_buffer(prop, len, &tmp_addr, &tmp_size);
if (ret)
return ret;
/* Do some sanity on the returned size for the ima-kexec buffer */
if (!tmp_size)
return -ENOENT;
ret = ima_validate_range(tmp_addr, tmp_size);
if (ret)
return ret;
*addr = __va(tmp_addr);
*size = tmp_size;
return 0;
}
/**
* ima_free_kexec_buffer - free memory used by the IMA buffer
*/
int __init ima_free_kexec_buffer(void)
{
int ret;
unsigned long addr;
size_t size;
struct property *prop;
prop = of_find_property(of_chosen, "linux,ima-kexec-buffer", NULL);
if (!prop)
return -ENOENT;
ret = do_get_kexec_buffer(prop->value, prop->length, &addr, &size);
if (ret)
return ret;
ret = of_remove_property(of_chosen, prop);
if (ret)
return ret;
memblock_phys_free(addr, size);
return 0;
}
#endif
/**
* remove_ima_buffer - remove the IMA buffer property and reservation from @fdt
*
* @fdt: Flattened Device Tree to update
* @chosen_node: Offset to the chosen node in the device tree
*
* The IMA measurement buffer is of no use to a subsequent kernel, so we always
* remove it from the device tree.
*/
static void remove_ima_buffer(void *fdt, int chosen_node)
{
int ret, len;
unsigned long addr;
size_t size;
const void *prop;
if (!IS_ENABLED(CONFIG_HAVE_IMA_KEXEC))
return;
prop = fdt_getprop(fdt, chosen_node, "linux,ima-kexec-buffer", &len);
if (!prop)
return;
ret = do_get_kexec_buffer(prop, len, &addr, &size);
fdt_delprop(fdt, chosen_node, "linux,ima-kexec-buffer");
if (ret)
return;
ret = fdt_find_and_del_mem_rsv(fdt, addr, size);
if (!ret)
pr_debug("Removed old IMA buffer reservation.\n");
}
#ifdef CONFIG_IMA_KEXEC
/**
* setup_ima_buffer - add IMA buffer information to the fdt
* @image: kexec image being loaded.
* @fdt: Flattened device tree for the next kernel.
* @chosen_node: Offset to the chosen node.
*
* Return: 0 on success, or negative errno on error.
*/
static int setup_ima_buffer(const struct kimage *image, void *fdt,
int chosen_node)
{
int ret;
if (!image->ima_buffer_size)
return 0;
ret = fdt_appendprop_addrrange(fdt, 0, chosen_node,
"linux,ima-kexec-buffer",
image->ima_buffer_addr,
image->ima_buffer_size);
if (ret < 0)
return -EINVAL;
ret = fdt_add_mem_rsv(fdt, image->ima_buffer_addr,
image->ima_buffer_size);
if (ret)
return -EINVAL;
pr_debug("IMA buffer at 0x%llx, size = 0x%zx\n",
image->ima_buffer_addr, image->ima_buffer_size);
return 0;
}
#else /* CONFIG_IMA_KEXEC */
static inline int setup_ima_buffer(const struct kimage *image, void *fdt,
int chosen_node)
{
return 0;
}
#endif /* CONFIG_IMA_KEXEC */
static int kho_add_chosen(const struct kimage *image, void *fdt, int chosen_node)
{
int ret = 0;
#ifdef CONFIG_KEXEC_HANDOVER
phys_addr_t fdt_mem = 0;
phys_addr_t fdt_len = 0;
phys_addr_t scratch_mem = 0;
phys_addr_t scratch_len = 0;
ret = fdt_delprop(fdt, chosen_node, "linux,kho-fdt");
if (ret && ret != -FDT_ERR_NOTFOUND)
return ret;
ret = fdt_delprop(fdt, chosen_node, "linux,kho-scratch");
if (ret && ret != -FDT_ERR_NOTFOUND)
return ret;
if (!image->kho.fdt || !image->kho.scratch)
return 0;
fdt_mem = image->kho.fdt;
fdt_len = PAGE_SIZE;
scratch_mem = image->kho.scratch->mem;
scratch_len = image->kho.scratch->bufsz;
pr_debug("Adding kho metadata to DT");
ret = fdt_appendprop_addrrange(fdt, 0, chosen_node, "linux,kho-fdt",
fdt_mem, fdt_len);
if (ret)
return ret;
ret = fdt_appendprop_addrrange(fdt, 0, chosen_node, "linux,kho-scratch",
scratch_mem, scratch_len);
#endif /* CONFIG_KEXEC_HANDOVER */
return ret;
}
/*
* of_kexec_alloc_and_setup_fdt - Alloc and setup a new Flattened Device Tree
*
* @image: kexec image being loaded.
* @initrd_load_addr: Address where the next initrd will be loaded.
* @initrd_len: Size of the next initrd, or 0 if there will be none.
* @cmdline: Command line for the next kernel, or NULL if there will
* be none.
* @extra_fdt_size: Additional size for the new FDT buffer.
*
* Return: fdt on success, or NULL errno on error.
*/
void *of_kexec_alloc_and_setup_fdt(const struct kimage *image,
unsigned long initrd_load_addr,
unsigned long initrd_len,
const char *cmdline, size_t extra_fdt_size)
{
void *fdt;
int ret, chosen_node, len;
const void *prop;
size_t fdt_size;
fdt_size = fdt_totalsize(initial_boot_params) +
(cmdline ? strlen(cmdline) : 0) +
FDT_EXTRA_SPACE +
extra_fdt_size;
fdt = kvmalloc(fdt_size, GFP_KERNEL);
if (!fdt)
return NULL;
ret = fdt_open_into(initial_boot_params, fdt, fdt_size);
if (ret < 0) {
pr_err("Error %d setting up the new device tree.\n", ret);
goto out;
}
/* Remove memory reservation for the current device tree. */
ret = fdt_find_and_del_mem_rsv(fdt, initial_boot_params_pa,
fdt_totalsize(initial_boot_params));
if (ret == -EINVAL) {
pr_err("Error removing memory reservation.\n");
goto out;
}
chosen_node = fdt_path_offset(fdt, "/chosen");
if (chosen_node == -FDT_ERR_NOTFOUND)
chosen_node = fdt_add_subnode(fdt, fdt_path_offset(fdt, "/"),
"chosen");
if (chosen_node < 0) {
ret = chosen_node;
goto out;
}
ret = fdt_delprop(fdt, chosen_node, "linux,elfcorehdr");
if (ret && ret != -FDT_ERR_NOTFOUND)
goto out;
ret = fdt_delprop(fdt, chosen_node, "linux,usable-memory-range");
if (ret && ret != -FDT_ERR_NOTFOUND)
goto out;
/* Did we boot using an initrd? */
prop = fdt_getprop(fdt, chosen_node, "linux,initrd-start", &len);
if (prop) {
u64 tmp_start, tmp_end, tmp_size;
tmp_start = of_read_number(prop, len / 4);
prop = fdt_getprop(fdt, chosen_node, "linux,initrd-end", &len);
if (!prop) {
ret = -EINVAL;
goto out;
}
tmp_end = of_read_number(prop, len / 4);
/*
* kexec reserves exact initrd size, while firmware may
* reserve a multiple of PAGE_SIZE, so check for both.
*/
tmp_size = tmp_end - tmp_start;
ret = fdt_find_and_del_mem_rsv(fdt, tmp_start, tmp_size);
if (ret == -ENOENT)
ret = fdt_find_and_del_mem_rsv(fdt, tmp_start,
round_up(tmp_size, PAGE_SIZE));
if (ret == -EINVAL)
goto out;
}
/* add initrd-* */
if (initrd_load_addr) {
ret = fdt_setprop_u64(fdt, chosen_node, "linux,initrd-start",
initrd_load_addr);
if (ret)
goto out;
ret = fdt_setprop_u64(fdt, chosen_node, "linux,initrd-end",
initrd_load_addr + initrd_len);
if (ret)
goto out;
ret = fdt_add_mem_rsv(fdt, initrd_load_addr, initrd_len);
if (ret)
goto out;
} else {
ret = fdt_delprop(fdt, chosen_node, "linux,initrd-start");
if (ret && (ret != -FDT_ERR_NOTFOUND))
goto out;
ret = fdt_delprop(fdt, chosen_node, "linux,initrd-end");
if (ret && (ret != -FDT_ERR_NOTFOUND))
goto out;
}
if (image->type == KEXEC_TYPE_CRASH) {
/* add linux,elfcorehdr */
ret = fdt_appendprop_addrrange(fdt, 0, chosen_node,
"linux,elfcorehdr", image->elf_load_addr,
image->elf_headers_sz);
if (ret)
goto out;
/*
* Avoid elfcorehdr from being stomped on in kdump kernel by
* setting up memory reserve map.
*/
ret = fdt_add_mem_rsv(fdt, image->elf_load_addr,
image->elf_headers_sz);
if (ret)
goto out;
if (image->dm_crypt_keys_addr != 0) {
ret = fdt_appendprop_addrrange(fdt, 0, chosen_node,
"linux,dmcryptkeys",
image->dm_crypt_keys_addr,
image->dm_crypt_keys_sz);
if (ret)
goto out;
/*
* Avoid dmcryptkeys from being stomped on in kdump kernel by
* setting up memory reserve map.
*/
ret = fdt_add_mem_rsv(fdt, image->dm_crypt_keys_addr,
image->dm_crypt_keys_sz);
if (ret)
goto out;
}
#ifdef CONFIG_CRASH_DUMP
/* add linux,usable-memory-range */
ret = fdt_appendprop_addrrange(fdt, 0, chosen_node,
"linux,usable-memory-range", crashk_res.start,
crashk_res.end - crashk_res.start + 1);
if (ret)
goto out;
if (crashk_low_res.end) {
ret = fdt_appendprop_addrrange(fdt, 0, chosen_node,
"linux,usable-memory-range",
crashk_low_res.start,
crashk_low_res.end - crashk_low_res.start + 1);
if (ret)
goto out;
}
#endif
}
/* Add kho metadata if this is a KHO image */
ret = kho_add_chosen(image, fdt, chosen_node);
if (ret)
goto out;
/* add bootargs */
if (cmdline) {
ret = fdt_setprop_string(fdt, chosen_node, "bootargs", cmdline);
if (ret)
goto out;
} else {
ret = fdt_delprop(fdt, chosen_node, "bootargs");
if (ret && (ret != -FDT_ERR_NOTFOUND))
goto out;
}
/* add kaslr-seed */
ret = fdt_delprop(fdt, chosen_node, "kaslr-seed");
if (ret == -FDT_ERR_NOTFOUND)
ret = 0;
else if (ret)
goto out;
if (rng_is_initialized()) {
u64 seed = get_random_u64();
ret = fdt_setprop_u64(fdt, chosen_node, "kaslr-seed", seed);
if (ret)
goto out;
} else {
pr_notice("RNG is not initialised: omitting \"%s\" property\n",
"kaslr-seed");
}
/* add rng-seed */
if (rng_is_initialized()) {
void *rng_seed;
ret = fdt_setprop_placeholder(fdt, chosen_node, "rng-seed",
RNG_SEED_SIZE, &rng_seed);
if (ret)
goto out;
get_random_bytes(rng_seed, RNG_SEED_SIZE);
} else {
pr_notice("RNG is not initialised: omitting \"%s\" property\n",
"rng-seed");
}
ret = fdt_setprop(fdt, chosen_node, "linux,booted-from-kexec", NULL, 0);
if (ret)
goto out;
remove_ima_buffer(fdt, chosen_node);
ret = setup_ima_buffer(image, fdt, fdt_path_offset(fdt, "/chosen"));
out:
if (ret) {
kvfree(fdt);
fdt = NULL;
}
return fdt;
}
+165
View File
@@ -0,0 +1,165 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/of.h>
#include <linux/slab.h>
#include "of_private.h"
/* true when node is initialized */
static int of_node_is_initialized(const struct device_node *node)
{
return node && node->kobj.state_initialized;
}
/* true when node is attached (i.e. present on sysfs) */
int of_node_is_attached(const struct device_node *node)
{
return node && node->kobj.state_in_sysfs;
}
#ifndef CONFIG_OF_DYNAMIC
static void of_node_release(struct kobject *kobj)
{
/* Without CONFIG_OF_DYNAMIC, no nodes gets freed */
}
#endif /* CONFIG_OF_DYNAMIC */
const struct kobj_type of_node_ktype = {
.release = of_node_release,
};
static ssize_t of_node_property_read(struct file *filp, struct kobject *kobj,
const struct bin_attribute *bin_attr, char *buf,
loff_t offset, size_t count)
{
struct property *pp = container_of(bin_attr, struct property, attr);
return memory_read_from_buffer(buf, count, &offset, pp->value, pp->length);
}
/* always return newly allocated name, caller must free after use */
static const char *safe_name(const struct kobject *kobj, const char *orig_name)
{
const char *name = orig_name;
struct kernfs_node *kn;
int i = 0;
/* don't be a hero. After 16 tries give up */
while (i < 16 && (kn = sysfs_get_dirent(kobj->sd, name))) {
sysfs_put(kn);
if (name != orig_name)
kfree(name);
name = kasprintf(GFP_KERNEL, "%s#%i", orig_name, ++i);
}
if (name == orig_name) {
name = kstrdup(orig_name, GFP_KERNEL);
} else {
pr_warn("Duplicate name in %s, renamed to \"%s\"\n",
kobject_name(kobj), name);
}
return name;
}
int __of_add_property_sysfs(struct device_node *np, struct property *pp)
{
int rc;
/* Important: Don't leak passwords */
bool secure = strncmp(pp->name, "security-", 9) == 0;
if (!IS_ENABLED(CONFIG_SYSFS))
return 0;
if (!of_kset || !of_node_is_attached(np))
return 0;
sysfs_bin_attr_init(&pp->attr);
pp->attr.attr.name = safe_name(&np->kobj, pp->name);
pp->attr.attr.mode = secure ? 0400 : 0444;
pp->attr.size = secure ? 0 : pp->length;
pp->attr.read = of_node_property_read;
rc = sysfs_create_bin_file(&np->kobj, &pp->attr);
WARN(rc, "error adding attribute %s to node %pOF\n", pp->name, np);
return rc;
}
void __of_sysfs_remove_bin_file(struct device_node *np, const struct property *prop)
{
if (!IS_ENABLED(CONFIG_SYSFS))
return;
sysfs_remove_bin_file(&np->kobj, &prop->attr);
kfree(prop->attr.attr.name);
}
void __of_remove_property_sysfs(struct device_node *np, const struct property *prop)
{
/* at early boot, bail here and defer setup to of_init() */
if (of_kset && of_node_is_attached(np))
__of_sysfs_remove_bin_file(np, prop);
}
void __of_update_property_sysfs(struct device_node *np, struct property *newprop,
const struct property *oldprop)
{
/* At early boot, bail out and defer setup to of_init() */
if (!of_kset)
return;
if (oldprop)
__of_sysfs_remove_bin_file(np, oldprop);
__of_add_property_sysfs(np, newprop);
}
int __of_attach_node_sysfs(struct device_node *np)
{
const char *name;
struct kobject *parent;
struct property *pp;
int rc;
if (!IS_ENABLED(CONFIG_SYSFS) || !of_kset)
return 0;
np->kobj.kset = of_kset;
if (!np->parent) {
/* Nodes without parents are new top level trees */
name = safe_name(&of_kset->kobj, "base");
parent = NULL;
} else {
name = safe_name(&np->parent->kobj, kbasename(np->full_name));
parent = &np->parent->kobj;
}
if (!name)
return -ENOMEM;
rc = kobject_add(&np->kobj, parent, "%s", name);
kfree(name);
if (rc)
return rc;
for_each_property_of_node(np, pp)
__of_add_property_sysfs(np, pp);
of_node_get(np);
return 0;
}
void __of_detach_node_sysfs(struct device_node *np)
{
struct property *pp;
BUG_ON(!of_node_is_initialized(np));
if (!of_kset)
return;
/* only remove properties if on sysfs */
if (of_node_is_attached(np)) {
for_each_property_of_node(np, pp)
__of_sysfs_remove_bin_file(np, pp);
kobject_del(&np->kobj);
}
of_node_put(np);
}
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
&{/} {
kunit-test {
compatible = "test,empty";
};
};
+81
View File
@@ -0,0 +1,81 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Linux kernel module helpers.
*/
#include <linux/of.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/string.h>
ssize_t of_modalias(const struct device_node *np, char *str, ssize_t len)
{
const char *compat;
char *c;
struct property *p;
ssize_t csize;
ssize_t tsize;
/*
* Prevent a kernel oops in vsnprintf() -- it only allows passing a
* NULL ptr when the length is also 0. Also filter out the negative
* lengths...
*/
if ((len > 0 && !str) || len < 0)
return -EINVAL;
/* Name & Type */
/* %p eats all alphanum characters, so %c must be used here */
csize = snprintf(str, len, "of:N%pOFn%c%s", np, 'T',
of_node_get_device_type(np));
tsize = csize;
if (csize >= len)
csize = len > 0 ? len - 1 : 0;
len -= csize;
str += csize;
of_property_for_each_string(np, "compatible", p, compat) {
csize = snprintf(str, len, "C%s", compat);
tsize += csize;
if (csize >= len)
continue;
for (c = str; c; ) {
c = strchr(c, ' ');
if (c)
*c++ = '_';
}
len -= csize;
str += csize;
}
return tsize;
}
int of_request_module(const struct device_node *np)
{
char *str;
ssize_t size;
int ret;
if (!np)
return -ENODEV;
size = of_modalias(np, NULL, 0);
if (size < 0)
return size;
/* Reserve an additional byte for the trailing '\0' */
size++;
str = kmalloc(size, GFP_KERNEL);
if (!str)
return -ENOMEM;
of_modalias(np, str, size);
str[size - 1] = '\0';
ret = request_module(str);
kfree(str);
return ret;
}
EXPORT_SYMBOL_GPL(of_request_module);
+93
View File
@@ -0,0 +1,93 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Test managed DeviceTree APIs
*/
#include <linux/of.h>
#include <linux/of_fdt.h>
#include <kunit/of.h>
#include <kunit/test.h>
#include <kunit/resource.h>
#include "of_private.h"
/**
* of_root_kunit_skip() - Skip test if the root node isn't populated
* @test: test to skip if the root node isn't populated
*/
void of_root_kunit_skip(struct kunit *test)
{
if ((IS_ENABLED(CONFIG_ARM64) || IS_ENABLED(CONFIG_RISCV)) &&
IS_ENABLED(CONFIG_ACPI) && !of_root)
kunit_skip(test, "arm64/riscv+acpi doesn't populate a root node");
}
EXPORT_SYMBOL_GPL(of_root_kunit_skip);
#if defined(CONFIG_OF_OVERLAY) && defined(CONFIG_OF_EARLY_FLATTREE)
static void of_overlay_fdt_apply_kunit_exit(void *ovcs_id)
{
of_overlay_remove(ovcs_id);
}
/**
* of_overlay_fdt_apply_kunit() - Test managed of_overlay_fdt_apply()
* @test: test context
* @overlay_fdt: device tree overlay to apply
* @overlay_fdt_size: size in bytes of @overlay_fdt
* @ovcs_id: identifier of overlay, used to remove the overlay
*
* Just like of_overlay_fdt_apply(), except the overlay is managed by the test
* case and is automatically removed with of_overlay_remove() after the test
* case concludes.
*
* Return: 0 on success, negative errno on failure
*/
int of_overlay_fdt_apply_kunit(struct kunit *test, void *overlay_fdt,
u32 overlay_fdt_size, int *ovcs_id)
{
int ret;
int *copy_id;
of_root_kunit_skip(test);
copy_id = kunit_kmalloc(test, sizeof(*copy_id), GFP_KERNEL);
if (!copy_id)
return -ENOMEM;
ret = of_overlay_fdt_apply(overlay_fdt, overlay_fdt_size,
ovcs_id, NULL);
if (ret)
return ret;
*copy_id = *ovcs_id;
return kunit_add_action_or_reset(test, of_overlay_fdt_apply_kunit_exit,
copy_id);
}
EXPORT_SYMBOL_GPL(of_overlay_fdt_apply_kunit);
#endif
KUNIT_DEFINE_ACTION_WRAPPER(of_node_put_wrapper, of_node_put, struct device_node *);
/**
* of_node_put_kunit() - Test managed of_node_put()
* @test: test context
* @node: node to pass to `of_node_put()`
*
* Just like of_node_put(), except the node is managed by the test case and is
* automatically put with of_node_put() after the test case concludes.
*/
void of_node_put_kunit(struct kunit *test, struct device_node *node)
{
if (kunit_add_action(test, of_node_put_wrapper, node)) {
KUNIT_FAIL(test,
"Can't allocate a kunit resource to put of_node\n");
}
}
EXPORT_SYMBOL_GPL(of_node_put_kunit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Test managed DeviceTree APIs");
+187
View File
@@ -0,0 +1,187 @@
// SPDX-License-Identifier: GPL-2.0
/*
* OF NUMA Parsing support.
*
* Copyright (C) 2015 - 2016 Cavium Inc.
*/
#define pr_fmt(fmt) "OF: NUMA: " fmt
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/nodemask.h>
#include <linux/numa_memblks.h>
#include <asm/numa.h>
/*
* Even though we connect cpus to numa domains later in SMP
* init, we need to know the node ids now for all cpus.
*/
static void __init of_numa_parse_cpu_nodes(void)
{
u32 nid;
int r;
struct device_node *np;
for_each_of_cpu_node(np) {
r = of_property_read_u32(np, "numa-node-id", &nid);
if (r)
continue;
pr_debug("CPU on %u\n", nid);
if (nid >= MAX_NUMNODES)
pr_warn("Node id %u exceeds maximum value\n", nid);
else
node_set(nid, numa_nodes_parsed);
}
}
static int __init of_numa_parse_memory_nodes(void)
{
struct device_node *np = NULL;
struct resource rsrc;
u32 nid;
int i, r = -EINVAL;
for_each_node_by_type(np, "memory") {
r = of_property_read_u32(np, "numa-node-id", &nid);
if (r == -EINVAL)
/*
* property doesn't exist if -EINVAL, continue
* looking for more memory nodes with
* "numa-node-id" property
*/
continue;
if (nid >= MAX_NUMNODES) {
pr_warn("Node id %u exceeds maximum value\n", nid);
r = -EINVAL;
}
for (i = 0; !r && !of_address_to_resource(np, i, &rsrc); i++) {
r = numa_add_memblk(nid, rsrc.start, rsrc.end + 1);
if (!r)
node_set(nid, numa_nodes_parsed);
}
if (!i || r) {
of_node_put(np);
pr_err("bad property in memory node\n");
return r ? : -EINVAL;
}
}
return r;
}
static int __init of_numa_parse_distance_map_v1(struct device_node *map)
{
const __be32 *matrix;
int entry_count;
int i;
pr_info("parsing numa-distance-map-v1\n");
matrix = of_get_property(map, "distance-matrix", NULL);
if (!matrix) {
pr_err("No distance-matrix property in distance-map\n");
return -EINVAL;
}
entry_count = of_property_count_u32_elems(map, "distance-matrix");
if (entry_count <= 0) {
pr_err("Invalid distance-matrix\n");
return -EINVAL;
}
for (i = 0; i + 2 < entry_count; i += 3) {
u32 nodea, nodeb, distance;
nodea = of_read_number(matrix, 1);
matrix++;
nodeb = of_read_number(matrix, 1);
matrix++;
distance = of_read_number(matrix, 1);
matrix++;
if ((nodea == nodeb && distance != LOCAL_DISTANCE) ||
(nodea != nodeb && distance <= LOCAL_DISTANCE)) {
pr_err("Invalid distance[node%d -> node%d] = %d\n",
nodea, nodeb, distance);
return -EINVAL;
}
node_set(nodea, numa_nodes_parsed);
numa_set_distance(nodea, nodeb, distance);
/* Set default distance of node B->A same as A->B */
if (nodeb > nodea)
numa_set_distance(nodeb, nodea, distance);
}
return 0;
}
static int __init of_numa_parse_distance_map(void)
{
int ret = 0;
struct device_node *np;
np = of_find_compatible_node(NULL, NULL,
"numa-distance-map-v1");
if (np)
ret = of_numa_parse_distance_map_v1(np);
of_node_put(np);
return ret;
}
int of_node_to_nid(struct device_node *device)
{
struct device_node *np;
u32 nid;
int r = -ENODATA;
np = of_node_get(device);
while (np) {
r = of_property_read_u32(np, "numa-node-id", &nid);
/*
* -EINVAL indicates the property was not found, and
* we walk up the tree trying to find a parent with a
* "numa-node-id". Any other type of error indicates
* a bad device tree and we give up.
*/
if (r != -EINVAL)
break;
np = of_get_next_parent(np);
}
if (np && r)
pr_warn("Invalid \"numa-node-id\" property in node %pOFn\n",
np);
of_node_put(np);
/*
* If numa=off passed on command line, or with a defective
* device tree, the nid may not be in the set of possible
* nodes. Check for this case and return NUMA_NO_NODE.
*/
if (!r && nid < MAX_NUMNODES && node_possible(nid))
return nid;
return NUMA_NO_NODE;
}
int __init of_numa_init(void)
{
int r;
of_numa_parse_cpu_nodes();
r = of_numa_parse_memory_nodes();
if (r)
return r;
return of_numa_parse_distance_map();
}
+222
View File
@@ -0,0 +1,222 @@
/* SPDX-License-Identifier: GPL-2.0+ */
#ifndef _LINUX_OF_PRIVATE_H
#define _LINUX_OF_PRIVATE_H
/*
* Private symbols used by OF support code
*
* Paul Mackerras August 1996.
* Copyright (C) 1996-2005 Paul Mackerras.
*/
#define FDT_ALIGN_SIZE 8
#define MAX_RESERVED_REGIONS 64
/**
* struct alias_prop - Alias property in 'aliases' node
* @link: List node to link the structure in aliases_lookup list
* @alias: Alias property name
* @np: Pointer to device_node that the alias stands for
* @id: Index value from end of alias name
* @stem: Alias string without the index
*
* The structure represents one alias property of 'aliases' node as
* an entry in aliases_lookup list.
*/
struct alias_prop {
struct list_head link;
const char *alias;
struct device_node *np;
int id;
char stem[];
};
#if defined(CONFIG_SPARC)
#define OF_ROOT_NODE_ADDR_CELLS_DEFAULT 2
#else
#define OF_ROOT_NODE_ADDR_CELLS_DEFAULT 1
#endif
#define OF_ROOT_NODE_SIZE_CELLS_DEFAULT 1
extern struct mutex of_mutex;
extern raw_spinlock_t devtree_lock;
extern struct list_head aliases_lookup;
extern struct kset *of_kset;
struct kunit;
extern void of_root_kunit_skip(struct kunit *test);
#if defined(CONFIG_OF_DYNAMIC)
extern int of_property_notify(int action, struct device_node *np,
struct property *prop, struct property *old_prop);
extern void of_node_release(struct kobject *kobj);
extern int __of_changeset_apply_entries(struct of_changeset *ocs,
int *ret_revert);
extern int __of_changeset_apply_notify(struct of_changeset *ocs);
extern int __of_changeset_revert_entries(struct of_changeset *ocs,
int *ret_apply);
extern int __of_changeset_revert_notify(struct of_changeset *ocs);
#else /* CONFIG_OF_DYNAMIC */
static inline int of_property_notify(int action, struct device_node *np,
struct property *prop, struct property *old_prop)
{
return 0;
}
#endif /* CONFIG_OF_DYNAMIC */
#if defined(CONFIG_OF_DYNAMIC) && defined(CONFIG_OF_ADDRESS)
void of_platform_register_reconfig_notifier(void);
#else
static inline void of_platform_register_reconfig_notifier(void) { }
#endif
#if defined(CONFIG_OF_KOBJ)
int of_node_is_attached(const struct device_node *node);
int __of_add_property_sysfs(struct device_node *np, struct property *pp);
void __of_remove_property_sysfs(struct device_node *np, const struct property *prop);
void __of_update_property_sysfs(struct device_node *np, struct property *newprop,
const struct property *oldprop);
int __of_attach_node_sysfs(struct device_node *np);
void __of_detach_node_sysfs(struct device_node *np);
#else
static inline int __of_add_property_sysfs(struct device_node *np, struct property *pp)
{
return 0;
}
static inline void __of_remove_property_sysfs(struct device_node *np, const struct property *prop) {}
static inline void __of_update_property_sysfs(struct device_node *np,
struct property *newprop, const struct property *oldprop) {}
static inline int __of_attach_node_sysfs(struct device_node *np)
{
return 0;
}
static inline void __of_detach_node_sysfs(struct device_node *np) {}
#endif
#if defined(CONFIG_OF_RESOLVE)
int of_resolve_phandles(struct device_node *tree);
#endif
void __of_phandle_cache_inv_entry(phandle handle);
#if defined(CONFIG_OF_OVERLAY)
void of_overlay_mutex_lock(void);
void of_overlay_mutex_unlock(void);
#else
static inline void of_overlay_mutex_lock(void) {};
static inline void of_overlay_mutex_unlock(void) {};
#endif
#if defined(CONFIG_OF_UNITTEST) && defined(CONFIG_OF_OVERLAY)
extern void __init unittest_unflatten_overlay_base(void);
#else
static inline void unittest_unflatten_overlay_base(void) {};
#endif
extern void *__unflatten_device_tree(const void *blob,
struct device_node *dad,
struct device_node **mynodes,
void *(*dt_alloc)(u64 size, u64 align),
bool detached);
void of_alias_scan(void * (*dt_alloc)(u64 size, u64 align));
/**
* General utilities for working with live trees.
*
* All functions with two leading underscores operate
* without taking node references, so you either have to
* own the devtree lock or work on detached trees only.
*/
struct property *__of_prop_dup(const struct property *prop, gfp_t allocflags);
void __of_prop_free(struct property *prop);
struct device_node *__of_node_dup(const struct device_node *np,
const char *full_name);
struct device_node *__of_find_node_by_path(const struct device_node *parent,
const char *path);
struct device_node *__of_find_node_by_full_path(struct device_node *node,
const char *path);
extern const void *__of_get_property(const struct device_node *np,
const char *name, int *lenp);
extern int __of_add_property(struct device_node *np, struct property *prop);
extern int __of_remove_property(struct device_node *np, struct property *prop);
extern int __of_update_property(struct device_node *np,
struct property *newprop, struct property **oldprop);
extern void __of_detach_node(struct device_node *np);
extern void __of_sysfs_remove_bin_file(struct device_node *np,
const struct property *prop);
/* illegal phandle value (set when unresolved) */
#define OF_PHANDLE_ILLEGAL 0xdeadbeef
/* iterators for transactions, used for overlays */
/* forward iterator */
#define for_each_transaction_entry(_oft, _te) \
list_for_each_entry(_te, &(_oft)->te_list, node)
/* reverse iterator */
#define for_each_transaction_entry_reverse(_oft, _te) \
list_for_each_entry_reverse(_te, &(_oft)->te_list, node)
extern int of_bus_n_addr_cells(struct device_node *np);
extern int of_bus_n_size_cells(struct device_node *np);
const __be32 *of_irq_parse_imap_parent(const __be32 *imap, int len,
struct of_phandle_args *out_irq);
struct bus_dma_region;
#if defined(CONFIG_OF_ADDRESS) && defined(CONFIG_HAS_DMA)
int of_dma_get_range(struct device_node *np,
const struct bus_dma_region **map);
struct device_node *__of_get_dma_parent(const struct device_node *np);
#else
static inline int of_dma_get_range(struct device_node *np,
const struct bus_dma_region **map)
{
return -ENODEV;
}
static inline struct device_node *__of_get_dma_parent(const struct device_node *np)
{
return of_get_parent(np);
}
#endif
int fdt_scan_reserved_mem(void);
void __init fdt_scan_reserved_mem_late(void);
bool of_fdt_device_is_available(const void *blob, unsigned long node);
/* Max address size we deal with */
#define OF_MAX_ADDR_CELLS 4
#define OF_CHECK_ADDR_COUNT(na) ((na) > 0 && (na) <= OF_MAX_ADDR_CELLS)
#define OF_CHECK_COUNTS(na, ns) (OF_CHECK_ADDR_COUNT(na) && (ns) > 0)
/* Debug utility */
#ifdef DEBUG
static void __maybe_unused of_dump_addr(const char *s, const __be32 *addr, int na)
{
pr_debug("%s", s);
while (na--)
pr_cont(" %08x", be32_to_cpu(*(addr++)));
pr_cont("\n");
}
#else
static void __maybe_unused of_dump_addr(const char *s, const __be32 *addr, int na) { }
#endif
static inline bool is_pseudo_property(const char *prop_name)
{
return !of_prop_cmp(prop_name, "name") ||
!of_prop_cmp(prop_name, "phandle") ||
!of_prop_cmp(prop_name, "linux,phandle");
}
#if IS_ENABLED(CONFIG_KUNIT)
int __of_address_resource_bounds(struct resource *r, u64 start, u64 size);
#endif
#endif /* _LINUX_OF_PRIVATE_H */
+903
View File
@@ -0,0 +1,903 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Device tree based initialization code for reserved memory.
*
* Copyright (c) 2013, 2015 The Linux Foundation. All Rights Reserved.
* Copyright (c) 2013,2014 Samsung Electronics Co., Ltd.
* http://www.samsung.com
* Author: Marek Szyprowski <m.szyprowski@samsung.com>
* Author: Josh Cartwright <joshc@codeaurora.org>
*/
#define pr_fmt(fmt) "OF: reserved mem: " fmt
#include <linux/err.h>
#include <linux/ioport.h>
#include <linux/libfdt.h>
#include <linux/of.h>
#include <linux/of_fdt.h>
#include <linux/of_platform.h>
#include <linux/mm.h>
#include <linux/sizes.h>
#include <linux/of_reserved_mem.h>
#include <linux/sort.h>
#include <linux/slab.h>
#include <linux/memblock.h>
#include <linux/kmemleak.h>
#include "of_private.h"
static struct reserved_mem reserved_mem_array[MAX_RESERVED_REGIONS] __initdata;
static struct reserved_mem *reserved_mem __refdata = reserved_mem_array;
static int total_reserved_mem_cnt = MAX_RESERVED_REGIONS;
static int reserved_mem_count;
static int __init early_init_dt_alloc_reserved_memory_arch(phys_addr_t size,
phys_addr_t align, phys_addr_t start, phys_addr_t end, bool nomap,
phys_addr_t *res_base)
{
phys_addr_t base;
int err = 0;
end = !end ? MEMBLOCK_ALLOC_ANYWHERE : end;
align = !align ? SMP_CACHE_BYTES : align;
base = memblock_phys_alloc_range(size, align, start, end);
if (!base)
return -ENOMEM;
*res_base = base;
if (nomap) {
err = memblock_mark_nomap(base, size);
if (err)
memblock_phys_free(base, size);
}
if (!err)
kmemleak_ignore_phys(base);
return err;
}
/*
* alloc_reserved_mem_array() - allocate memory for the reserved_mem
* array using memblock
*
* This function is used to allocate memory for the reserved_mem
* array according to the total number of reserved memory regions
* defined in the DT.
* After the new array is allocated, the information stored in
* the initial static array is copied over to this new array and
* the new array is used from this point on.
*/
static int __init alloc_reserved_mem_array(void)
{
struct reserved_mem *new_array;
size_t alloc_size, copy_size, memset_size;
int ret;
if (!total_reserved_mem_cnt)
return 0;
alloc_size = array_size(total_reserved_mem_cnt, sizeof(*new_array));
if (alloc_size == SIZE_MAX) {
ret = -EOVERFLOW;
goto fail;
}
new_array = memblock_alloc(alloc_size, SMP_CACHE_BYTES);
if (!new_array) {
ret = -ENOMEM;
goto fail;
}
copy_size = array_size(reserved_mem_count, sizeof(*new_array));
if (copy_size == SIZE_MAX) {
memblock_free(new_array, alloc_size);
ret = -EOVERFLOW;
goto fail;
}
memset_size = alloc_size - copy_size;
memcpy(new_array, reserved_mem, copy_size);
memset(new_array + reserved_mem_count, 0, memset_size);
reserved_mem = new_array;
return 0;
fail:
pr_err("Failed to allocate memory for reserved_mem array with err: %d", ret);
reserved_mem_count = 0;
return ret;
}
static void fdt_init_reserved_mem_node(unsigned long node, const char *uname,
phys_addr_t base, phys_addr_t size);
static int fdt_validate_reserved_mem_node(unsigned long node,
phys_addr_t *align);
static int fdt_fixup_reserved_mem_node(unsigned long node,
phys_addr_t base, phys_addr_t size);
static int __init early_init_dt_reserve_memory(phys_addr_t base,
phys_addr_t size, bool nomap)
{
if (nomap) {
/*
* If the memory is already reserved (by another region), we
* should not allow it to be marked nomap, but don't worry
* if the region isn't memory as it won't be mapped.
*/
if (memblock_overlaps_region(&memblock.memory, base, size) &&
memblock_is_region_reserved(base, size))
return -EBUSY;
return memblock_mark_nomap(base, size);
}
return memblock_reserve(base, size);
}
/*
* __reserved_mem_reserve_reg() - reserve memory described in the
* first entry in 'reg' property
*/
static int __init __reserved_mem_reserve_reg(unsigned long node,
const char *uname)
{
phys_addr_t base, size;
int len, err;
const __be32 *prop;
bool nomap;
u64 b, s;
prop = of_flat_dt_get_addr_size_prop(node, "reg", &len);
if (!prop || !len)
return -ENOENT;
if (len > 1)
pr_warn("Reserved memory: node '%s' has %d <base size> entries, only the first is used\n",
uname, len);
nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL;
err = fdt_validate_reserved_mem_node(node, NULL);
if (err && err != -ENODEV)
return err;
of_flat_dt_read_addr_size(prop, 0, &b, &s);
base = b;
size = s;
if (size && early_init_dt_reserve_memory(base, size, nomap) == 0) {
fdt_fixup_reserved_mem_node(node, base, size);
pr_debug("Reserved memory: reserved region for node '%s': base %pa, size %lu MiB\n",
uname, &base, (unsigned long)(size / SZ_1M));
} else {
pr_err("Reserved memory: failed to reserve memory for node '%s': base %pa, size %lu MiB\n",
uname, &base, (unsigned long)(size / SZ_1M));
}
return 0;
}
/*
* __reserved_mem_check_root() - check if #size-cells, #address-cells provided
* in /reserved-memory matches the values supported by the current implementation,
* also check if ranges property has been provided
*/
static int __init __reserved_mem_check_root(unsigned long node)
{
const __be32 *prop;
prop = of_get_flat_dt_prop(node, "#size-cells", NULL);
if (!prop || be32_to_cpup(prop) != dt_root_size_cells)
return -EINVAL;
prop = of_get_flat_dt_prop(node, "#address-cells", NULL);
if (!prop || be32_to_cpup(prop) != dt_root_addr_cells)
return -EINVAL;
prop = of_get_flat_dt_prop(node, "ranges", NULL);
if (!prop)
return -EINVAL;
return 0;
}
static int __init __rmem_cmp(const void *a, const void *b)
{
const struct reserved_mem *ra = a, *rb = b;
if (ra->base < rb->base)
return -1;
if (ra->base > rb->base)
return 1;
/*
* Put the dynamic allocations (address == 0, size == 0) before static
* allocations at address 0x0 so that overlap detection works
* correctly.
*/
if (ra->size < rb->size)
return -1;
if (ra->size > rb->size)
return 1;
return 0;
}
static void __init __rmem_check_for_overlap(void)
{
int i;
if (reserved_mem_count < 2)
return;
sort(reserved_mem, reserved_mem_count, sizeof(reserved_mem[0]),
__rmem_cmp, NULL);
for (i = 0; i < reserved_mem_count - 1; i++) {
struct reserved_mem *this, *next;
this = &reserved_mem[i];
next = &reserved_mem[i + 1];
if (this->base + this->size > next->base) {
phys_addr_t this_end, next_end;
this_end = this->base + this->size;
next_end = next->base + next->size;
pr_err("OVERLAP DETECTED!\n%s (%pa--%pa) overlaps with %s (%pa--%pa)\n",
this->name, &this->base, &this_end,
next->name, &next->base, &next_end);
}
}
}
/**
* fdt_scan_reserved_mem_late() - Scan FDT and initialize remaining reserved
* memory regions.
*
* This function is used to scan again through the DT and initialize the
* "static" reserved memory regions, that are defined using the "reg"
* property. Each such region is then initialized with its specific init
* function and stored in the global reserved_mem array.
*/
void __init fdt_scan_reserved_mem_late(void)
{
const void *fdt = initial_boot_params;
phys_addr_t base, size;
int node, child;
if (!fdt)
return;
node = fdt_path_offset(fdt, "/reserved-memory");
if (node < 0) {
pr_info("Reserved memory: No reserved-memory node in the DT\n");
return;
}
/* Attempt dynamic allocation of a new reserved_mem array */
if (alloc_reserved_mem_array())
return;
if (__reserved_mem_check_root(node)) {
pr_err("Reserved memory: unsupported node format, ignoring\n");
return;
}
fdt_for_each_subnode(child, fdt, node) {
const __be32 *prop;
const char *uname;
u64 b, s;
int ret;
int len;
if (!of_fdt_device_is_available(fdt, child))
continue;
prop = of_flat_dt_get_addr_size_prop(child, "reg", &len);
if (!prop || !len)
continue;
ret = fdt_validate_reserved_mem_node(child, NULL);
if (ret && ret != -ENODEV)
continue;
of_flat_dt_read_addr_size(prop, 0, &b, &s);
base = b;
size = s;
if (size) {
uname = fdt_get_name(fdt, child, NULL);
fdt_init_reserved_mem_node(child, uname, base, size);
}
}
/* check for overlapping reserved regions */
__rmem_check_for_overlap();
}
static int __init __reserved_mem_alloc_size(unsigned long node, const char *uname);
/*
* fdt_scan_reserved_mem() - reserve and allocate memory occupied by
* reserved memory regions.
*
* This function is used to scan through the FDT and mark memory occupied
* by all static (defined by the "reg" property) reserved memory regions.
* Then memory for all dynamic regions (defined by size & alignment) is
* allocated, a region specific init function is called and region information
* is stored in the reserved_mem array.
*/
int __init fdt_scan_reserved_mem(void)
{
int node, child;
int dynamic_nodes_cnt = 0, count = 0;
int dynamic_nodes[MAX_RESERVED_REGIONS];
const void *fdt = initial_boot_params;
node = fdt_path_offset(fdt, "/reserved-memory");
if (node < 0) {
total_reserved_mem_cnt = 0;
return -ENODEV;
}
if (__reserved_mem_check_root(node) != 0) {
pr_err("Reserved memory: unsupported node format, ignoring\n");
total_reserved_mem_cnt = 0;
return -EINVAL;
}
fdt_for_each_subnode(child, fdt, node) {
const char *uname;
int err;
if (!of_fdt_device_is_available(fdt, child))
continue;
uname = fdt_get_name(fdt, child, NULL);
err = __reserved_mem_reserve_reg(child, uname);
if (!err)
count++;
/*
* Save the nodes for the dynamically-placed regions
* into an array which will be used for allocation right
* after all the statically-placed regions are reserved
* or marked as no-map. This is done to avoid dynamically
* allocating from one of the statically-placed regions.
*/
if (err != -ENOENT || !of_get_flat_dt_prop(child, "size", NULL))
continue;
if (dynamic_nodes_cnt == MAX_RESERVED_REGIONS) {
pr_err("too many defined dynamic regions, skip '%s'\n",
uname);
continue;
}
dynamic_nodes[dynamic_nodes_cnt] = child;
dynamic_nodes_cnt++;
}
for (int i = 0; i < dynamic_nodes_cnt; i++) {
const char *uname;
int err;
child = dynamic_nodes[i];
uname = fdt_get_name(fdt, child, NULL);
err = __reserved_mem_alloc_size(child, uname);
if (!err)
count++;
}
total_reserved_mem_cnt = count;
return 0;
}
/*
* __reserved_mem_alloc_in_range() - allocate reserved memory described with
* 'alloc-ranges'. Choose bottom-up/top-down depending on nearby existing
* reserved regions to keep the reserved memory contiguous if possible.
*/
static int __init __reserved_mem_alloc_in_range(phys_addr_t size,
phys_addr_t align, phys_addr_t start, phys_addr_t end, bool nomap,
phys_addr_t *res_base)
{
bool prev_bottom_up = memblock_bottom_up();
bool bottom_up = false, top_down = false;
int ret, i;
for (i = 0; i < reserved_mem_count; i++) {
struct reserved_mem *rmem = &reserved_mem[i];
/* Skip regions that were not reserved yet */
if (rmem->size == 0)
continue;
/*
* If range starts next to an existing reservation, use bottom-up:
* |....RRRR................RRRRRRRR..............|
* --RRRR------
*/
if (start >= rmem->base && start <= (rmem->base + rmem->size))
bottom_up = true;
/*
* If range ends next to an existing reservation, use top-down:
* |....RRRR................RRRRRRRR..............|
* -------RRRR-----
*/
if (end >= rmem->base && end <= (rmem->base + rmem->size))
top_down = true;
}
/* Change setting only if either bottom-up or top-down was selected */
if (bottom_up != top_down)
memblock_set_bottom_up(bottom_up);
ret = early_init_dt_alloc_reserved_memory_arch(size, align,
start, end, nomap, res_base);
/* Restore old setting if needed */
if (bottom_up != top_down)
memblock_set_bottom_up(prev_bottom_up);
return ret;
}
/*
* __reserved_mem_alloc_size() - allocate reserved memory described by
* 'size', 'alignment' and 'alloc-ranges' properties.
*/
static int __init __reserved_mem_alloc_size(unsigned long node, const char *uname)
{
phys_addr_t start = 0, end = 0;
phys_addr_t base = 0, align = 0, size;
int i, len;
const __be32 *prop;
bool nomap;
int ret;
prop = of_get_flat_dt_prop(node, "size", &len);
if (!prop)
return -EINVAL;
if (len != dt_root_size_cells * sizeof(__be32)) {
pr_err("invalid size property in '%s' node.\n", uname);
return -EINVAL;
}
size = dt_mem_next_cell(dt_root_size_cells, &prop);
prop = of_get_flat_dt_prop(node, "alignment", &len);
if (prop) {
if (len != dt_root_addr_cells * sizeof(__be32)) {
pr_err("invalid alignment property in '%s' node.\n",
uname);
return -EINVAL;
}
align = dt_mem_next_cell(dt_root_addr_cells, &prop);
}
nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL;
ret = fdt_validate_reserved_mem_node(node, &align);
if (ret && ret != -ENODEV)
return ret;
prop = of_flat_dt_get_addr_size_prop(node, "alloc-ranges", &len);
if (prop) {
for (i = 0; i < len; i++) {
u64 b, s;
of_flat_dt_read_addr_size(prop, i, &b, &s);
start = b;
end = b + s;
base = 0;
ret = __reserved_mem_alloc_in_range(size, align,
start, end, nomap, &base);
if (ret == 0) {
pr_debug("allocated memory for '%s' node: base %pa, size %lu MiB\n",
uname, &base,
(unsigned long)(size / SZ_1M));
break;
}
}
} else {
ret = early_init_dt_alloc_reserved_memory_arch(size, align,
0, 0, nomap, &base);
if (ret == 0)
pr_debug("allocated memory for '%s' node: base %pa, size %lu MiB\n",
uname, &base, (unsigned long)(size / SZ_1M));
}
if (base == 0) {
pr_err("failed to allocate memory for node '%s': size %lu MiB\n",
uname, (unsigned long)(size / SZ_1M));
return -ENOMEM;
}
fdt_fixup_reserved_mem_node(node, base, size);
fdt_init_reserved_mem_node(node, uname, base, size);
return 0;
}
extern const struct of_device_id __reservedmem_of_table[];
static const struct of_device_id __rmem_of_table_sentinel
__used __section("__reservedmem_of_table_end");
/**
* fdt_fixup_reserved_mem_node() - call fixup function for a reserved memory node
* @node: FDT node to fixup
* @base: base address of the reserved memory region
* @size: size of the reserved memory region
*
* This function iterates through the reserved memory drivers and calls
* the node_fixup callback for the compatible entry matching the node.
*
* Return: 0 on success, -ENODEV if no compatible match found
*/
static int __init fdt_fixup_reserved_mem_node(unsigned long node,
phys_addr_t base, phys_addr_t size)
{
const struct of_device_id *i;
int ret = -ENODEV;
for (i = __reservedmem_of_table; ret == -ENODEV &&
i < &__rmem_of_table_sentinel; i++) {
const struct reserved_mem_ops *ops = i->data;
if (!of_flat_dt_is_compatible(node, i->compatible))
continue;
if (ops->node_fixup)
ret = ops->node_fixup(node, base, size);
}
return ret;
}
/**
* fdt_validate_reserved_mem_node() - validate a reserved memory node
* @node: FDT node to validate
* @align: pointer to store the validated alignment (may be modified by callback)
*
* This function iterates through the reserved memory drivers and calls
* the node_validate callback for the compatible entry matching the node.
*
* Return: 0 on success, -ENODEV if no compatible match found
*/
static int __init fdt_validate_reserved_mem_node(unsigned long node, phys_addr_t *align)
{
const struct of_device_id *i;
int ret = -ENODEV;
for (i = __reservedmem_of_table; ret == -ENODEV &&
i < &__rmem_of_table_sentinel; i++) {
const struct reserved_mem_ops *ops = i->data;
if (!of_flat_dt_is_compatible(node, i->compatible))
continue;
if (ops->node_validate)
ret = ops->node_validate(node, align);
}
return ret;
}
/**
* __reserved_mem_init_node() - initialize a reserved memory region
* @rmem: reserved_mem structure to initialize
* @node: FDT node describing the reserved memory region
*
* This function iterates through the reserved memory drivers and calls the
* node_init callback for the compatible entry matching the node. On success,
* the operations pointer is stored in the reserved_mem structure.
*
* Return: 0 on success, -ENODEV if no compatible match found
*/
static int __init __reserved_mem_init_node(struct reserved_mem *rmem,
unsigned long node)
{
const struct of_device_id *i;
int ret = -ENODEV;
for (i = __reservedmem_of_table; ret == -ENODEV &&
i < &__rmem_of_table_sentinel; i++) {
const struct reserved_mem_ops *ops = i->data;
const char *compat = i->compatible;
if (!of_flat_dt_is_compatible(node, compat))
continue;
ret = ops->node_init(node, rmem);
if (ret == 0) {
rmem->ops = ops;
pr_info("initialized node %s, compatible id %s\n",
rmem->name, compat);
return ret;
}
}
return ret;
}
/**
* fdt_init_reserved_mem_node() - Initialize a reserved memory region
* @node: fdt node of the initialized region
* @uname: name of the reserved memory node
* @base: base address of the reserved memory region
* @size: size of the reserved memory region
*
* This function calls the region-specific initialization function for a
* reserved memory region and saves all region-specific data to the
* reserved_mem array to allow of_reserved_mem_lookup() to find it.
*/
static void __init fdt_init_reserved_mem_node(unsigned long node, const char *uname,
phys_addr_t base, phys_addr_t size)
{
int err = 0;
bool nomap;
struct reserved_mem *rmem = &reserved_mem[reserved_mem_count];
if (reserved_mem_count == total_reserved_mem_cnt) {
pr_err("not enough space for all defined regions.\n");
return;
}
rmem->name = uname;
rmem->base = base;
rmem->size = size;
nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL;
err = __reserved_mem_init_node(rmem, node);
if (err != 0 && err != -ENODEV) {
pr_info("node %s compatible matching fail\n", rmem->name);
rmem->name = NULL;
if (nomap)
memblock_clear_nomap(rmem->base, rmem->size);
else
memblock_phys_free(rmem->base, rmem->size);
return;
} else {
phys_addr_t end = rmem->base + rmem->size - 1;
bool reusable =
(of_get_flat_dt_prop(node, "reusable", NULL)) != NULL;
pr_info("%pa..%pa (%lu KiB) %s %s %s\n",
&rmem->base, &end, (unsigned long)(rmem->size / SZ_1K),
nomap ? "nomap" : "map",
reusable ? "reusable" : "non-reusable",
rmem->name ? rmem->name : "unknown");
}
reserved_mem_count++;
}
struct rmem_assigned_device {
struct device *dev;
struct reserved_mem *rmem;
struct list_head list;
};
static LIST_HEAD(of_rmem_assigned_device_list);
static DEFINE_MUTEX(of_rmem_assigned_device_mutex);
/**
* of_reserved_mem_device_init_by_idx() - assign reserved memory region to
* given device
* @dev: Pointer to the device to configure
* @np: Pointer to the device_node with 'reserved-memory' property
* @idx: Index of selected region
*
* This function assigns respective DMA-mapping operations based on reserved
* memory region specified by 'memory-region' property in @np node to the @dev
* device. When driver needs to use more than one reserved memory region, it
* should allocate child devices and initialize regions by name for each of
* child device.
*
* Returns error code or zero on success.
*/
int of_reserved_mem_device_init_by_idx(struct device *dev,
struct device_node *np, int idx)
{
struct rmem_assigned_device *rd;
struct device_node *target;
struct reserved_mem *rmem;
int ret;
if (!np || !dev)
return -EINVAL;
target = of_parse_phandle(np, "memory-region", idx);
if (!target)
return -ENODEV;
if (!of_device_is_available(target)) {
of_node_put(target);
return 0;
}
rmem = of_reserved_mem_lookup(target);
of_node_put(target);
if (!rmem || !rmem->ops || !rmem->ops->device_init)
return -EINVAL;
rd = kmalloc_obj(struct rmem_assigned_device);
if (!rd)
return -ENOMEM;
ret = rmem->ops->device_init(rmem, dev);
if (ret == 0) {
rd->dev = dev;
rd->rmem = rmem;
mutex_lock(&of_rmem_assigned_device_mutex);
list_add(&rd->list, &of_rmem_assigned_device_list);
mutex_unlock(&of_rmem_assigned_device_mutex);
dev_info(dev, "assigned reserved memory node %s\n", rmem->name);
} else {
kfree(rd);
}
return ret;
}
EXPORT_SYMBOL_GPL(of_reserved_mem_device_init_by_idx);
/**
* of_reserved_mem_device_init_by_name() - assign named reserved memory region
* to given device
* @dev: pointer to the device to configure
* @np: pointer to the device node with 'memory-region' property
* @name: name of the selected memory region
*
* Returns: 0 on success or a negative error-code on failure.
*/
int of_reserved_mem_device_init_by_name(struct device *dev,
struct device_node *np,
const char *name)
{
int idx = of_property_match_string(np, "memory-region-names", name);
return of_reserved_mem_device_init_by_idx(dev, np, idx);
}
EXPORT_SYMBOL_GPL(of_reserved_mem_device_init_by_name);
/**
* of_reserved_mem_device_release() - release reserved memory device structures
* @dev: Pointer to the device to deconfigure
*
* This function releases structures allocated for memory region handling for
* the given device.
*/
void of_reserved_mem_device_release(struct device *dev)
{
struct rmem_assigned_device *rd, *tmp;
LIST_HEAD(release_list);
mutex_lock(&of_rmem_assigned_device_mutex);
list_for_each_entry_safe(rd, tmp, &of_rmem_assigned_device_list, list) {
if (rd->dev == dev)
list_move_tail(&rd->list, &release_list);
}
mutex_unlock(&of_rmem_assigned_device_mutex);
list_for_each_entry_safe(rd, tmp, &release_list, list) {
if (rd->rmem && rd->rmem->ops && rd->rmem->ops->device_release)
rd->rmem->ops->device_release(rd->rmem, dev);
kfree(rd);
}
}
EXPORT_SYMBOL_GPL(of_reserved_mem_device_release);
/**
* of_reserved_mem_lookup() - acquire reserved_mem from a device node
* @np: node pointer of the desired reserved-memory region
*
* This function allows drivers to acquire a reference to the reserved_mem
* struct based on a device node handle.
*
* Returns a reserved_mem reference, or NULL on error.
*/
struct reserved_mem *of_reserved_mem_lookup(struct device_node *np)
{
const char *name;
int i;
if (!np->full_name)
return NULL;
name = kbasename(np->full_name);
for (i = 0; i < reserved_mem_count; i++)
if (reserved_mem[i].name &&
!strcmp(reserved_mem[i].name, name))
return &reserved_mem[i];
return NULL;
}
EXPORT_SYMBOL_GPL(of_reserved_mem_lookup);
/**
* of_reserved_mem_region_to_resource() - Get a reserved memory region as a resource
* @np: node containing 'memory-region' property
* @idx: index of 'memory-region' property to lookup
* @res: Pointer to a struct resource to fill in with reserved region
*
* This function allows drivers to lookup a node's 'memory-region' property
* entries by index and return a struct resource for the entry.
*
* Returns 0 on success with @res filled in. Returns -ENODEV if 'memory-region'
* is missing or unavailable, -EINVAL for any other error.
*/
int of_reserved_mem_region_to_resource(const struct device_node *np,
unsigned int idx, struct resource *res)
{
struct reserved_mem *rmem;
if (!np)
return -EINVAL;
struct device_node *target __free(device_node) = of_parse_phandle(np, "memory-region", idx);
if (!target || !of_device_is_available(target))
return -ENODEV;
rmem = of_reserved_mem_lookup(target);
if (!rmem)
return -EINVAL;
resource_set_range(res, rmem->base, rmem->size);
res->flags = IORESOURCE_MEM;
res->name = rmem->name;
return 0;
}
EXPORT_SYMBOL_GPL(of_reserved_mem_region_to_resource);
/**
* of_reserved_mem_region_to_resource_byname() - Get a reserved memory region as a resource
* @np: node containing 'memory-region' property
* @name: name of 'memory-region' property entry to lookup
* @res: Pointer to a struct resource to fill in with reserved region
*
* This function allows drivers to lookup a node's 'memory-region' property
* entries by name and return a struct resource for the entry.
*
* Returns 0 on success with @res filled in, or a negative error-code on
* failure.
*/
int of_reserved_mem_region_to_resource_byname(const struct device_node *np,
const char *name,
struct resource *res)
{
int idx;
if (!name)
return -EINVAL;
idx = of_property_match_string(np, "memory-region-names", name);
if (idx < 0)
return idx;
return of_reserved_mem_region_to_resource(np, idx, res);
}
EXPORT_SYMBOL_GPL(of_reserved_mem_region_to_resource_byname);
/**
* of_reserved_mem_region_count() - Return the number of 'memory-region' entries
* @np: node containing 'memory-region' property
*
* This function allows drivers to retrieve the number of entries for a node's
* 'memory-region' property.
*
* Returns the number of entries on success, or negative error code on a
* malformed property.
*/
int of_reserved_mem_region_count(const struct device_node *np)
{
return of_count_phandle_with_args(np, "memory-region", NULL);
}
EXPORT_SYMBOL_GPL(of_reserved_mem_region_count);
+178
View File
@@ -0,0 +1,178 @@
// SPDX-License-Identifier: GPL-2.0
/*
* KUnit tests for OF APIs
*/
#include <linux/ioport.h>
#include <linux/module.h>
#include <linux/of.h>
#include <kunit/test.h>
#include "of_private.h"
/*
* Test that the root node "/" can be found by path.
*/
static void of_dtb_root_node_found_by_path(struct kunit *test)
{
struct device_node *np;
np = of_find_node_by_path("/");
KUNIT_EXPECT_NOT_ERR_OR_NULL(test, np);
of_node_put(np);
}
/*
* Test that the 'of_root' global variable is always populated when DT code is
* enabled. Remove this test once of_root is removed from global access.
*/
static void of_dtb_root_node_populates_of_root(struct kunit *test)
{
KUNIT_EXPECT_NOT_ERR_OR_NULL(test, of_root);
}
static struct kunit_case of_dtb_test_cases[] = {
KUNIT_CASE(of_dtb_root_node_found_by_path),
KUNIT_CASE(of_dtb_root_node_populates_of_root),
{}
};
static int of_dtb_test_init(struct kunit *test)
{
of_root_kunit_skip(test);
if (!IS_ENABLED(CONFIG_OF_EARLY_FLATTREE))
kunit_skip(test, "requires CONFIG_OF_EARLY_FLATTREE");
return 0;
}
/*
* Test suite to confirm a DTB is loaded.
*/
static struct kunit_suite of_dtb_suite = {
.name = "of_dtb",
.test_cases = of_dtb_test_cases,
.init = of_dtb_test_init,
};
struct of_address_resource_bounds_case {
u64 start;
u64 size;
int ret;
u64 res_start;
u64 res_end;
};
static void of_address_resource_bounds_case_desc(const struct of_address_resource_bounds_case *p,
char *name)
{
snprintf(name, KUNIT_PARAM_DESC_SIZE, "start=0x%016llx,size=0x%016llx", p->start, p->size);
}
static const struct of_address_resource_bounds_case of_address_resource_bounds_cases[] = {
{
.start = 0,
.size = 0,
.ret = 0,
.res_start = 0,
.res_end = -1,
},
{
.start = 0,
.size = 0x1000,
.ret = 0,
.res_start = 0,
.res_end = 0xfff,
},
{
.start = 0x1000,
.size = 0,
.ret = 0,
.res_start = 0x1000,
.res_end = 0xfff,
},
{
.start = 0x1000,
.size = 0x1000,
.ret = 0,
.res_start = 0x1000,
.res_end = 0x1fff,
},
{
.start = 1,
.size = RESOURCE_SIZE_MAX,
.ret = 0,
.res_start = 1,
.res_end = RESOURCE_SIZE_MAX,
},
{
.start = RESOURCE_SIZE_MAX,
.size = 1,
.ret = 0,
.res_start = RESOURCE_SIZE_MAX,
.res_end = RESOURCE_SIZE_MAX,
},
{
.start = 2,
.size = RESOURCE_SIZE_MAX,
.ret = -EOVERFLOW,
},
{
.start = RESOURCE_SIZE_MAX,
.size = 2,
.ret = -EOVERFLOW,
},
{
.start = ULL(0x100000000),
.size = 1,
.ret = sizeof(resource_size_t) > sizeof(u32) ? 0 : -EOVERFLOW,
.res_start = ULL(0x100000000),
.res_end = ULL(0x100000000),
},
{
.start = 0x1000,
.size = 0xffffffff,
.ret = sizeof(resource_size_t) > sizeof(u32) ? 0 : -EOVERFLOW,
.res_start = 0x1000,
.res_end = ULL(0x100000ffe),
},
};
KUNIT_ARRAY_PARAM(of_address_resource_bounds,
of_address_resource_bounds_cases, of_address_resource_bounds_case_desc);
static void of_address_resource_bounds(struct kunit *test)
{
const struct of_address_resource_bounds_case *param = test->param_value;
struct resource r; /* Intentionally uninitialized */
int ret;
if (!IS_ENABLED(CONFIG_OF_ADDRESS))
kunit_skip(test, "CONFIG_OF_ADDRESS not enabled\n");
ret = __of_address_resource_bounds(&r, param->start, param->size);
KUNIT_EXPECT_EQ(test, param->ret, ret);
if (ret == 0) {
KUNIT_EXPECT_EQ(test, (resource_size_t)param->res_start, r.start);
KUNIT_EXPECT_EQ(test, (resource_size_t)param->res_end, r.end);
KUNIT_EXPECT_EQ(test, param->size, resource_size(&r));
}
}
static struct kunit_case of_address_test_cases[] = {
KUNIT_CASE_PARAM(of_address_resource_bounds, of_address_resource_bounds_gen_params),
{}
};
static struct kunit_suite of_address_suite = {
.name = "of_address",
.test_cases = of_address_test_cases,
};
kunit_test_suites(
&of_dtb_suite, &of_address_suite,
);
MODULE_DESCRIPTION("KUnit tests for OF APIs");
MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
MODULE_LICENSE("GPL");
File diff suppressed because it is too large Load Diff
+120
View File
@@ -0,0 +1,120 @@
// SPDX-License-Identifier: GPL-2.0
/*
* KUnit tests for device tree overlays
*/
#include <linux/device/bus.h>
#include <linux/kconfig.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <kunit/of.h>
#include <kunit/test.h>
#include "of_private.h"
static const char * const kunit_node_name = "kunit-test";
static const char * const kunit_compatible = "test,empty";
/* Test that of_overlay_apply_kunit() adds a node to the live tree */
static void of_overlay_apply_kunit_apply(struct kunit *test)
{
struct device_node *np;
KUNIT_ASSERT_EQ(test, 0,
of_overlay_apply_kunit(test, kunit_overlay_test));
np = of_find_node_by_name(NULL, kunit_node_name);
KUNIT_EXPECT_NOT_ERR_OR_NULL(test, np);
of_node_put(np);
}
/*
* Test that of_overlay_apply_kunit() creates platform devices with the
* expected device_node
*/
static void of_overlay_apply_kunit_platform_device(struct kunit *test)
{
struct platform_device *pdev;
struct device_node *np;
KUNIT_ASSERT_EQ(test, 0,
of_overlay_apply_kunit(test, kunit_overlay_test));
np = of_find_node_by_name(NULL, kunit_node_name);
of_node_put_kunit(test, np);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, np);
pdev = of_find_device_by_node(np);
KUNIT_EXPECT_NOT_ERR_OR_NULL(test, pdev);
if (pdev)
put_device(&pdev->dev);
}
static int of_overlay_bus_match_compatible(struct device *dev, const void *data)
{
return of_device_is_compatible(dev->of_node, data);
}
/* Test that of_overlay_apply_kunit() cleans up after the test is finished */
static void of_overlay_apply_kunit_cleanup(struct kunit *test)
{
struct kunit fake;
struct platform_device *pdev;
struct device *dev;
struct device_node *np;
of_root_kunit_skip(test);
if (!IS_ENABLED(CONFIG_OF_OVERLAY))
kunit_skip(test, "requires CONFIG_OF_OVERLAY to apply overlay");
if (!IS_ENABLED(CONFIG_OF_EARLY_FLATTREE))
kunit_skip(test, "requires CONFIG_OF_EARLY_FLATTREE for root node");
kunit_init_test(&fake, "fake test", NULL);
KUNIT_ASSERT_EQ(test, fake.status, KUNIT_SUCCESS);
KUNIT_ASSERT_EQ(test, 0,
of_overlay_apply_kunit(&fake, kunit_overlay_test));
np = of_find_node_by_name(NULL, kunit_node_name);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, np);
of_node_put_kunit(&fake, np);
pdev = of_find_device_by_node(np);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev);
put_device(&pdev->dev); /* Not derefing 'pdev' after this */
/* Remove overlay */
kunit_cleanup(&fake);
/* The node and device should be removed */
np = of_find_node_by_name(NULL, kunit_node_name);
KUNIT_EXPECT_PTR_EQ(test, NULL, np);
of_node_put(np);
dev = bus_find_device(&platform_bus_type, NULL, kunit_compatible,
of_overlay_bus_match_compatible);
KUNIT_EXPECT_PTR_EQ(test, NULL, dev);
put_device(dev);
}
static struct kunit_case of_overlay_apply_kunit_test_cases[] = {
KUNIT_CASE(of_overlay_apply_kunit_apply),
KUNIT_CASE(of_overlay_apply_kunit_platform_device),
KUNIT_CASE(of_overlay_apply_kunit_cleanup),
{}
};
/*
* Test suite for test managed device tree overlays.
*/
static struct kunit_suite of_overlay_apply_kunit_suite = {
.name = "of_overlay_apply_kunit",
.test_cases = of_overlay_apply_kunit_test_cases,
};
kunit_test_suites(
&of_overlay_apply_kunit_suite,
);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("KUnit tests for device tree overlays");
+215
View File
@@ -0,0 +1,215 @@
// SPDX-License-Identifier: GPL-2.0+
/* pdt.c: OF PROM device tree support code.
*
* Paul Mackerras August 1996.
* Copyright (C) 1996-2005 Paul Mackerras.
*
* Adapted for 64bit PowerPC by Dave Engebretsen and Peter Bergner.
* {engebret|bergner}@us.ibm.com
*
* Adapted for sparc by David S. Miller davem@davemloft.net
* Adapted for multiple architectures by Andres Salomon <dilinger@queued.net>
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_pdt.h>
#include "of_private.h"
static struct of_pdt_ops *of_pdt_prom_ops __initdata;
#if defined(CONFIG_SPARC)
unsigned int of_pdt_unique_id __initdata;
#define of_pdt_incr_unique_id(p) do { \
(p)->unique_id = of_pdt_unique_id++; \
} while (0)
static char * __init of_pdt_build_full_name(struct device_node *dp)
{
return build_path_component(dp);
}
#else /* CONFIG_SPARC */
static inline void of_pdt_incr_unique_id(void *p) { }
static inline void irq_trans_init(struct device_node *dp) { }
static char * __init of_pdt_build_full_name(struct device_node *dp)
{
static int failsafe_id = 0; /* for generating unique names on failure */
const char *name;
char path[256];
char *buf;
int len;
if (!of_pdt_prom_ops->pkg2path(dp->phandle, path, sizeof(path), &len)) {
name = kbasename(path);
buf = prom_early_alloc(strlen(name) + 1);
strcpy(buf, name);
return buf;
}
name = of_get_property(dp, "name", &len);
buf = prom_early_alloc(len + 16);
sprintf(buf, "%s@unknown%i", name, failsafe_id++);
pr_err("%s: pkg2path failed; assigning %s\n", __func__, buf);
return buf;
}
#endif /* !CONFIG_SPARC */
static struct property * __init of_pdt_build_one_prop(phandle node, char *prev,
char *special_name,
void *special_val,
int special_len)
{
static struct property *tmp = NULL;
struct property *p;
int err;
if (tmp) {
p = tmp;
memset(p, 0, sizeof(*p) + 32);
tmp = NULL;
} else {
p = prom_early_alloc(sizeof(struct property) + 32);
of_pdt_incr_unique_id(p);
}
p->name = (char *) (p + 1);
if (special_name) {
strcpy(p->name, special_name);
p->length = special_len;
p->value = prom_early_alloc(special_len);
memcpy(p->value, special_val, special_len);
} else {
err = of_pdt_prom_ops->nextprop(node, prev, p->name);
if (err) {
tmp = p;
return NULL;
}
p->length = of_pdt_prom_ops->getproplen(node, p->name);
if (p->length <= 0) {
p->length = 0;
} else {
int len;
p->value = prom_early_alloc(p->length + 1);
len = of_pdt_prom_ops->getproperty(node, p->name,
p->value, p->length);
if (len <= 0)
p->length = 0;
((unsigned char *)p->value)[p->length] = '\0';
}
}
return p;
}
static struct property * __init of_pdt_build_prop_list(phandle node)
{
struct property *head, *tail;
head = tail = of_pdt_build_one_prop(node, NULL,
".node", &node, sizeof(node));
tail->next = of_pdt_build_one_prop(node, NULL, NULL, NULL, 0);
tail = tail->next;
while(tail) {
tail->next = of_pdt_build_one_prop(node, tail->name,
NULL, NULL, 0);
tail = tail->next;
}
return head;
}
static char * __init of_pdt_get_one_property(phandle node, const char *name)
{
char *buf = "<NULL>";
int len;
len = of_pdt_prom_ops->getproplen(node, name);
if (len > 0) {
buf = prom_early_alloc(len);
len = of_pdt_prom_ops->getproperty(node, name, buf, len);
}
return buf;
}
static struct device_node * __init of_pdt_create_node(phandle node,
struct device_node *parent)
{
struct device_node *dp;
if (!node)
return NULL;
dp = prom_early_alloc(sizeof(*dp));
of_node_init(dp);
of_pdt_incr_unique_id(dp);
dp->parent = parent;
dp->name = of_pdt_get_one_property(node, "name");
dp->phandle = node;
dp->properties = of_pdt_build_prop_list(node);
dp->full_name = of_pdt_build_full_name(dp);
irq_trans_init(dp);
return dp;
}
static struct device_node * __init of_pdt_build_tree(struct device_node *parent,
phandle node)
{
struct device_node *ret = NULL, *prev_sibling = NULL;
struct device_node *dp;
while (1) {
dp = of_pdt_create_node(node, parent);
if (!dp)
break;
if (prev_sibling)
prev_sibling->sibling = dp;
if (!ret)
ret = dp;
prev_sibling = dp;
dp->child = of_pdt_build_tree(dp, of_pdt_prom_ops->getchild(node));
node = of_pdt_prom_ops->getsibling(node);
}
return ret;
}
static void * __init kernel_tree_alloc(u64 size, u64 align)
{
return prom_early_alloc(size);
}
void __init of_pdt_build_devicetree(phandle root_node, struct of_pdt_ops *ops)
{
BUG_ON(!ops);
of_pdt_prom_ops = ops;
of_root = of_pdt_create_node(root_node, NULL);
of_root->full_name = "/";
of_root->child = of_pdt_build_tree(of_root,
of_pdt_prom_ops->getchild(of_root->phandle));
/* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */
of_alias_scan(kernel_tree_alloc);
}
+793
View File
@@ -0,0 +1,793 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2006 Benjamin Herrenschmidt, IBM Corp.
* <benh@kernel.crashing.org>
* and Arnd Bergmann, IBM Corp.
* Merged from powerpc/kernel/of_platform.c and
* sparc{,64}/kernel/of_device.c by Stephen Rothwell
*/
#define pr_fmt(fmt) "OF: " fmt
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/amba/bus.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/sysfb.h>
#include "of_private.h"
/**
* of_find_device_by_node - Find the platform_device associated with a node
* @np: Pointer to device tree node
*
* Takes a reference to the embedded struct device which needs to be dropped
* after use.
*
* Return: platform_device pointer, or NULL if not found
*/
struct platform_device *of_find_device_by_node(struct device_node *np)
{
struct device *dev;
dev = bus_find_device_by_of_node(&platform_bus_type, np);
return dev ? to_platform_device(dev) : NULL;
}
EXPORT_SYMBOL(of_find_device_by_node);
int of_device_add(struct platform_device *ofdev)
{
BUG_ON(ofdev->dev.of_node == NULL);
/* name and id have to be set so that the platform bus doesn't get
* confused on matching */
ofdev->name = dev_name(&ofdev->dev);
ofdev->id = PLATFORM_DEVID_NONE;
/*
* If this device has not binding numa node in devicetree, that is
* of_node_to_nid returns NUMA_NO_NODE. device_add will assume that this
* device is on the same node as the parent.
*/
set_dev_node(&ofdev->dev, of_node_to_nid(ofdev->dev.of_node));
return device_add(&ofdev->dev);
}
int of_device_register(struct platform_device *pdev)
{
device_initialize(&pdev->dev);
return of_device_add(pdev);
}
EXPORT_SYMBOL(of_device_register);
void of_device_unregister(struct platform_device *ofdev)
{
device_unregister(&ofdev->dev);
}
EXPORT_SYMBOL(of_device_unregister);
#ifdef CONFIG_OF_ADDRESS
static const struct of_device_id of_skipped_node_table[] = {
{ .compatible = "operating-points-v2", },
{} /* Empty terminated list */
};
/*
* The following routines scan a subtree and registers a device for
* each applicable node.
*
* Note: sparc doesn't use these routines because it has a different
* mechanism for creating devices from device tree nodes.
*/
/**
* of_device_alloc - Allocate and initialize an of_device
* @np: device node to assign to device
* @bus_id: Name to assign to the device. May be null to use default name.
* @parent: Parent device.
*/
struct platform_device *of_device_alloc(struct device_node *np,
const char *bus_id,
struct device *parent)
{
struct platform_device *dev;
int rc, i, num_reg = 0;
struct resource *res;
dev = platform_device_alloc("", PLATFORM_DEVID_NONE);
if (!dev)
return NULL;
/* count the io resources */
num_reg = of_address_count(np);
/* Populate the resource table */
if (num_reg) {
res = kzalloc_objs(*res, num_reg);
if (!res) {
platform_device_put(dev);
return NULL;
}
dev->num_resources = num_reg;
dev->resource = res;
for (i = 0; i < num_reg; i++, res++) {
rc = of_address_to_resource(np, i, res);
WARN_ON(rc);
}
}
/* setup generic device info */
device_set_node(&dev->dev, of_fwnode_handle(of_node_get(np)));
dev->dev.parent = parent ? : &platform_bus;
if (bus_id)
dev_set_name(&dev->dev, "%s", bus_id);
else
of_device_make_bus_id(&dev->dev);
return dev;
}
EXPORT_SYMBOL(of_device_alloc);
/**
* of_platform_device_create_pdata - Alloc, initialize and register an of_device
* @np: pointer to node to create device for
* @bus_id: name to assign device
* @platform_data: pointer to populate platform_data pointer with
* @parent: Linux device model parent device.
*
* Return: Pointer to created platform device, or NULL if a device was not
* registered. Unavailable devices will not get registered.
*/
static struct platform_device *of_platform_device_create_pdata(
struct device_node *np,
const char *bus_id,
void *platform_data,
struct device *parent)
{
struct platform_device *dev;
pr_debug("create platform device: %pOF\n", np);
if (!of_device_is_available(np) ||
of_node_test_and_set_flag(np, OF_POPULATED))
return NULL;
dev = of_device_alloc(np, bus_id, parent);
if (!dev)
goto err_clear_flag;
dev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
if (!dev->dev.dma_mask)
dev->dev.dma_mask = &dev->dev.coherent_dma_mask;
dev->dev.bus = &platform_bus_type;
dev->dev.platform_data = platform_data;
of_msi_configure(&dev->dev, dev->dev.of_node);
if (of_device_add(dev) != 0) {
platform_device_put(dev);
goto err_clear_flag;
}
return dev;
err_clear_flag:
of_node_clear_flag(np, OF_POPULATED);
return NULL;
}
/**
* of_platform_device_create - Alloc, initialize and register an of_device
* @np: pointer to node to create device for
* @bus_id: name to assign device
* @parent: Linux device model parent device.
*
* Return: Pointer to created platform device, or NULL if a device was not
* registered. Unavailable devices will not get registered.
*/
struct platform_device *of_platform_device_create(struct device_node *np,
const char *bus_id,
struct device *parent)
{
return of_platform_device_create_pdata(np, bus_id, NULL, parent);
}
EXPORT_SYMBOL(of_platform_device_create);
#ifdef CONFIG_ARM_AMBA
static struct amba_device *of_amba_device_create(struct device_node *node,
const char *bus_id,
void *platform_data,
struct device *parent)
{
struct amba_device *dev;
int ret;
pr_debug("Creating amba device %pOF\n", node);
if (!of_device_is_available(node) ||
of_node_test_and_set_flag(node, OF_POPULATED))
return NULL;
dev = amba_device_alloc(NULL, 0, 0);
if (!dev)
goto err_clear_flag;
/* AMBA devices only support a single DMA mask */
dev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
dev->dev.dma_mask = &dev->dev.coherent_dma_mask;
/* setup generic device info */
device_set_node(&dev->dev, of_fwnode_handle(node));
dev->dev.parent = parent ? : &platform_bus;
dev->dev.platform_data = platform_data;
if (bus_id)
dev_set_name(&dev->dev, "%s", bus_id);
else
of_device_make_bus_id(&dev->dev);
/* Allow the HW Peripheral ID to be overridden */
of_property_read_u32(node, "arm,primecell-periphid", &dev->periphid);
ret = of_address_to_resource(node, 0, &dev->res);
if (ret) {
pr_err("amba: of_address_to_resource() failed (%d) for %pOF\n",
ret, node);
goto err_free;
}
ret = amba_device_add(dev, &iomem_resource);
if (ret) {
pr_err("amba_device_add() failed (%d) for %pOF\n",
ret, node);
goto err_free;
}
return dev;
err_free:
amba_device_put(dev);
err_clear_flag:
of_node_clear_flag(node, OF_POPULATED);
return NULL;
}
#else /* CONFIG_ARM_AMBA */
static struct amba_device *of_amba_device_create(struct device_node *node,
const char *bus_id,
void *platform_data,
struct device *parent)
{
return NULL;
}
#endif /* CONFIG_ARM_AMBA */
/*
* of_dev_lookup() - Given a device node, lookup the preferred Linux name
*/
static const struct of_dev_auxdata *of_dev_lookup(const struct of_dev_auxdata *lookup,
struct device_node *np)
{
const struct of_dev_auxdata *auxdata;
struct resource res;
int compatible = 0;
if (!lookup)
return NULL;
auxdata = lookup;
for (; auxdata->compatible; auxdata++) {
if (!of_device_is_compatible(np, auxdata->compatible))
continue;
compatible++;
if (!of_address_to_resource(np, 0, &res))
if (res.start != auxdata->phys_addr)
continue;
pr_debug("%pOF: devname=%s\n", np, auxdata->name);
return auxdata;
}
if (!compatible)
return NULL;
/* Try compatible match if no phys_addr and name are specified */
auxdata = lookup;
for (; auxdata->compatible; auxdata++) {
if (!of_device_is_compatible(np, auxdata->compatible))
continue;
if (!auxdata->phys_addr && !auxdata->name) {
pr_debug("%pOF: compatible match\n", np);
return auxdata;
}
}
return NULL;
}
/**
* of_platform_bus_create() - Create a device for a node and its children.
* @bus: device node of the bus to instantiate
* @matches: match table for bus nodes
* @lookup: auxdata table for matching id and platform_data with device nodes
* @parent: parent for new device, or NULL for top level.
* @strict: require compatible property
*
* Creates a platform_device for the provided device_node, and optionally
* recursively create devices for all the child nodes.
*/
static int of_platform_bus_create(struct device_node *bus,
const struct of_device_id *matches,
const struct of_dev_auxdata *lookup,
struct device *parent, bool strict)
{
const struct of_dev_auxdata *auxdata;
struct platform_device *dev;
const char *bus_id = NULL;
void *platform_data = NULL;
int rc = 0;
/* Make sure it has a compatible property */
if (strict && (!of_property_present(bus, "compatible"))) {
pr_debug("%s() - skipping %pOF, no compatible prop\n",
__func__, bus);
return 0;
}
/* Skip nodes for which we don't want to create devices */
if (unlikely(of_match_node(of_skipped_node_table, bus))) {
pr_debug("%s() - skipping %pOF node\n", __func__, bus);
return 0;
}
if (of_node_check_flag(bus, OF_POPULATED_BUS)) {
pr_debug("%s() - skipping %pOF, already populated\n",
__func__, bus);
return 0;
}
auxdata = of_dev_lookup(lookup, bus);
if (auxdata) {
bus_id = auxdata->name;
platform_data = auxdata->platform_data;
}
if (of_device_is_compatible(bus, "arm,primecell")) {
/*
* Don't return an error here to keep compatibility with older
* device tree files.
*/
of_amba_device_create(bus, bus_id, platform_data, parent);
return 0;
}
dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
if (!dev || !of_match_node(matches, bus))
return 0;
for_each_child_of_node_scoped(bus, child) {
pr_debug(" create child: %pOF\n", child);
rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
if (rc)
break;
}
of_node_set_flag(bus, OF_POPULATED_BUS);
return rc;
}
/**
* of_platform_bus_probe() - Probe the device-tree for platform buses
* @root: parent of the first level to probe or NULL for the root of the tree
* @matches: match table for bus nodes
* @parent: parent to hook devices from, NULL for toplevel
*
* Note that children of the provided root are not instantiated as devices
* unless the specified root itself matches the bus list and is not NULL.
*/
int of_platform_bus_probe(struct device_node *root,
const struct of_device_id *matches,
struct device *parent)
{
int rc = 0;
root = root ? of_node_get(root) : of_find_node_by_path("/");
if (!root)
return -EINVAL;
pr_debug("%s()\n", __func__);
pr_debug(" starting at: %pOF\n", root);
/* Do a self check of bus type, if there's a match, create children */
if (of_match_node(matches, root)) {
rc = of_platform_bus_create(root, matches, NULL, parent, false);
} else {
for_each_child_of_node_scoped(root, child) {
if (!of_match_node(matches, child))
continue;
rc = of_platform_bus_create(child, matches, NULL, parent, false);
if (rc)
break;
}
}
of_node_put(root);
return rc;
}
EXPORT_SYMBOL(of_platform_bus_probe);
/**
* of_platform_populate() - Populate platform_devices from device tree data
* @root: parent of the first level to probe or NULL for the root of the tree
* @matches: match table, NULL to use the default
* @lookup: auxdata table for matching id and platform_data with device nodes
* @parent: parent to hook devices from, NULL for toplevel
*
* Similar to of_platform_bus_probe(), this function walks the device tree
* and creates devices from nodes. It differs in that it follows the modern
* convention of requiring all device nodes to have a 'compatible' property,
* and it is suitable for creating devices which are children of the root
* node (of_platform_bus_probe will only create children of the root which
* are selected by the @matches argument).
*
* New board support should be using this function instead of
* of_platform_bus_probe().
*
* Return: 0 on success, < 0 on failure.
*/
int of_platform_populate(struct device_node *root,
const struct of_device_id *matches,
const struct of_dev_auxdata *lookup,
struct device *parent)
{
int rc = 0;
root = root ? of_node_get(root) : of_find_node_by_path("/");
if (!root)
return -EINVAL;
pr_debug("%s()\n", __func__);
pr_debug(" starting at: %pOF\n", root);
device_links_supplier_sync_state_pause();
for_each_child_of_node_scoped(root, child) {
rc = of_platform_bus_create(child, matches, lookup, parent, true);
if (rc)
break;
}
device_links_supplier_sync_state_resume();
of_node_set_flag(root, OF_POPULATED_BUS);
of_node_put(root);
return rc;
}
EXPORT_SYMBOL_GPL(of_platform_populate);
int of_platform_default_populate(struct device_node *root,
const struct of_dev_auxdata *lookup,
struct device *parent)
{
static const struct of_device_id match_table[] = {
{ .compatible = "simple-bus", },
{ .compatible = "simple-mfd", },
{ .compatible = "isa", },
#ifdef CONFIG_ARM_AMBA
{ .compatible = "arm,amba-bus", },
#endif /* CONFIG_ARM_AMBA */
{} /* Empty terminated list */
};
return of_platform_populate(root, match_table, lookup, parent);
}
EXPORT_SYMBOL_GPL(of_platform_default_populate);
static const struct of_device_id reserved_mem_matches[] = {
{ .compatible = "phram" },
{ .compatible = "qcom,rmtfs-mem" },
{ .compatible = "qcom,cmd-db" },
{ .compatible = "qcom,smem" },
{ .compatible = "ramoops" },
{ .compatible = "nvmem-rmem" },
{ .compatible = "google,open-dice" },
{}
};
static int __init of_platform_default_populate_init(void)
{
struct device_node *node, *reserved;
device_links_supplier_sync_state_pause();
if (IS_ENABLED(CONFIG_PPC)) {
struct device_node *boot_display = NULL;
struct platform_device *dev;
int display_number = 0;
int ret;
/* Check if we have a MacOS display without a node spec */
if (of_property_present(of_chosen, "linux,bootx-noscreen")) {
/*
* The old code tried to work out which node was the MacOS
* display based on the address. I'm dropping that since the
* lack of a node spec only happens with old BootX versions
* (users can update) and with this code, they'll still get
* a display (just not the palette hacks).
*/
dev = platform_device_alloc("bootx-noscreen", 0);
if (WARN_ON(!dev))
return -ENOMEM;
ret = platform_device_add(dev);
if (WARN_ON(ret)) {
platform_device_put(dev);
return ret;
}
}
/*
* For OF framebuffers, first create the device for the boot display,
* then for the other framebuffers. Only fail for the boot display;
* ignore errors for the rest.
*/
for_each_node_by_type(node, "display") {
if (!of_property_read_bool(node, "linux,opened") ||
!of_property_read_bool(node, "linux,boot-display"))
continue;
dev = of_platform_device_create(node, "of-display", NULL);
of_node_put(node);
if (WARN_ON(!dev))
return -ENOMEM;
boot_display = node;
display_number++;
break;
}
for_each_node_by_type(node, "display") {
char buf[14];
const char *of_display_format = "of-display.%d";
if (!of_property_read_bool(node, "linux,opened") || node == boot_display)
continue;
ret = snprintf(buf, sizeof(buf), of_display_format, display_number++);
if (ret < sizeof(buf))
of_platform_device_create(node, buf, NULL);
}
} else {
/*
* Handle certain compatibles explicitly, since we don't want to create
* platform_devices for every node in /reserved-memory with a
* "compatible",
*/
reserved = of_find_node_by_path("/reserved-memory");
if (reserved) {
for_each_child_of_node(reserved, node) {
if (of_match_node(reserved_mem_matches, node))
of_platform_device_create(node, NULL, NULL);
}
of_node_put(reserved);
}
node = of_find_node_by_path("/firmware");
if (node) {
of_platform_default_populate(node, NULL, NULL);
of_node_put(node);
}
node = of_get_compatible_child(of_chosen, "simple-framebuffer");
if (node) {
/*
* Since a "simple-framebuffer" device is already added
* here, disable the Generic System Framebuffers (sysfb)
* to prevent it from registering another device for the
* system framebuffer later (e.g: using the screen_info
* data that may had been filled as well).
*
* This can happen for example on DT systems that do EFI
* booting and may provide a GOP handle to the EFI stub.
*/
sysfb_disable(NULL);
of_platform_device_create(node, NULL, NULL);
of_node_put(node);
}
/* Populate everything else. */
of_platform_default_populate(NULL, NULL, NULL);
}
return 0;
}
arch_initcall_sync(of_platform_default_populate_init);
static int __init of_platform_sync_state_init(void)
{
device_links_supplier_sync_state_resume();
return 0;
}
late_initcall_sync(of_platform_sync_state_init);
int of_platform_device_destroy(struct device *dev, void *data)
{
/* Do not touch devices not populated from the device tree */
if (!dev->of_node || !of_node_check_flag(dev->of_node, OF_POPULATED))
return 0;
/* Recurse for any nodes that were treated as busses */
if (of_node_check_flag(dev->of_node, OF_POPULATED_BUS))
device_for_each_child(dev, NULL, of_platform_device_destroy);
of_node_clear_flag(dev->of_node, OF_POPULATED);
of_node_clear_flag(dev->of_node, OF_POPULATED_BUS);
if (dev->bus == &platform_bus_type)
platform_device_unregister(to_platform_device(dev));
#ifdef CONFIG_ARM_AMBA
else if (dev->bus == &amba_bustype)
amba_device_unregister(to_amba_device(dev));
#endif
return 0;
}
EXPORT_SYMBOL_GPL(of_platform_device_destroy);
/**
* of_platform_depopulate() - Remove devices populated from device tree
* @parent: device which children will be removed
*
* Complementary to of_platform_populate(), this function removes children
* of the given device (and, recursively, their children) that have been
* created from their respective device tree nodes (and only those,
* leaving others - eg. manually created - unharmed).
*/
void of_platform_depopulate(struct device *parent)
{
if (parent->of_node && of_node_check_flag(parent->of_node, OF_POPULATED_BUS)) {
device_for_each_child_reverse(parent, NULL, of_platform_device_destroy);
of_node_clear_flag(parent->of_node, OF_POPULATED_BUS);
}
}
EXPORT_SYMBOL_GPL(of_platform_depopulate);
static void devm_of_platform_populate_release(struct device *dev, void *res)
{
of_platform_depopulate(*(struct device **)res);
}
/**
* devm_of_platform_populate() - Populate platform_devices from device tree data
* @dev: device that requested to populate from device tree data
*
* Similar to of_platform_populate(), but will automatically call
* of_platform_depopulate() when the device is unbound from the bus.
*
* Return: 0 on success, < 0 on failure.
*/
int devm_of_platform_populate(struct device *dev)
{
struct device **ptr;
int ret;
if (!dev)
return -EINVAL;
ptr = devres_alloc(devm_of_platform_populate_release,
sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return -ENOMEM;
ret = of_platform_populate(dev->of_node, NULL, NULL, dev);
if (ret) {
devres_free(ptr);
} else {
*ptr = dev;
devres_add(dev, ptr);
}
return ret;
}
EXPORT_SYMBOL_GPL(devm_of_platform_populate);
static int devm_of_platform_match(struct device *dev, void *res, void *data)
{
struct device **ptr = res;
if (!ptr) {
WARN_ON(!ptr);
return 0;
}
return *ptr == data;
}
/**
* devm_of_platform_depopulate() - Remove devices populated from device tree
* @dev: device that requested to depopulate from device tree data
*
* Complementary to devm_of_platform_populate(), this function removes children
* of the given device (and, recursively, their children) that have been
* created from their respective device tree nodes (and only those,
* leaving others - eg. manually created - unharmed).
*/
void devm_of_platform_depopulate(struct device *dev)
{
int ret;
ret = devres_release(dev, devm_of_platform_populate_release,
devm_of_platform_match, dev);
WARN_ON(ret);
}
EXPORT_SYMBOL_GPL(devm_of_platform_depopulate);
#ifdef CONFIG_OF_DYNAMIC
static int of_platform_notify(struct notifier_block *nb,
unsigned long action, void *arg)
{
struct of_reconfig_data *rd = arg;
struct platform_device *pdev_parent, *pdev;
bool children_left;
struct device_node *parent;
switch (of_reconfig_get_state_change(action, rd)) {
case OF_RECONFIG_CHANGE_ADD:
parent = rd->dn->parent;
/* verify that the parent is a bus (or the root node) */
if (!of_node_is_root(parent) &&
!of_node_check_flag(parent, OF_POPULATED_BUS))
return NOTIFY_OK; /* not for us */
/* already populated? (driver using of_populate manually) */
if (of_node_check_flag(rd->dn, OF_POPULATED))
return NOTIFY_OK;
/* pdev_parent may be NULL when no bus platform device */
pdev_parent = of_find_device_by_node(parent);
pdev = of_platform_device_create(rd->dn, NULL,
pdev_parent ? &pdev_parent->dev : NULL);
platform_device_put(pdev_parent);
if (pdev == NULL) {
pr_err("%s: failed to create for '%pOF'\n",
__func__, rd->dn);
/* of_platform_device_create tosses the error code */
return notifier_from_errno(-EINVAL);
}
break;
case OF_RECONFIG_CHANGE_REMOVE:
/* already depopulated? */
if (!of_node_check_flag(rd->dn, OF_POPULATED))
return NOTIFY_OK;
/* find our device by node */
pdev = of_find_device_by_node(rd->dn);
if (pdev == NULL)
return NOTIFY_OK; /* no? not meant for us */
/* unregister takes one ref away */
of_platform_device_destroy(&pdev->dev, &children_left);
/* and put the reference of the find */
platform_device_put(pdev);
break;
}
return NOTIFY_OK;
}
static struct notifier_block platform_of_notifier = {
.notifier_call = of_platform_notify,
};
void of_platform_register_reconfig_notifier(void)
{
WARN_ON(of_reconfig_notifier_register(&platform_of_notifier));
}
#endif /* CONFIG_OF_DYNAMIC */
#endif /* CONFIG_OF_ADDRESS */
File diff suppressed because it is too large Load Diff
+325
View File
@@ -0,0 +1,325 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Functions for dealing with DT resolution
*
* Copyright (C) 2012 Pantelis Antoniou <panto@antoniou-consulting.com>
* Copyright (C) 2012 Texas Instruments Inc.
*/
#define pr_fmt(fmt) "OF: resolver: " fmt
#include <linux/cleanup.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/string.h>
#include <linux/ctype.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include "of_private.h"
static phandle live_tree_max_phandle(void)
{
struct device_node *node;
phandle phandle;
unsigned long flags;
raw_spin_lock_irqsave(&devtree_lock, flags);
phandle = 0;
for_each_of_allnodes(node) {
if (node->phandle != OF_PHANDLE_ILLEGAL &&
node->phandle > phandle)
phandle = node->phandle;
}
raw_spin_unlock_irqrestore(&devtree_lock, flags);
return phandle;
}
static void adjust_overlay_phandles(struct device_node *overlay,
int phandle_delta)
{
struct device_node *child;
const struct property *prop;
phandle phandle;
/* adjust node's phandle in node */
if (overlay->phandle != 0 && overlay->phandle != OF_PHANDLE_ILLEGAL)
overlay->phandle += phandle_delta;
/* copy adjusted phandle into *phandle properties */
for_each_property_of_node(overlay, prop) {
if (of_prop_cmp(prop->name, "phandle") &&
of_prop_cmp(prop->name, "linux,phandle"))
continue;
if (prop->length < 4)
continue;
phandle = be32_to_cpup(prop->value);
if (phandle == OF_PHANDLE_ILLEGAL)
continue;
*(__be32 *)prop->value = cpu_to_be32(overlay->phandle);
}
for_each_child_of_node(overlay, child)
adjust_overlay_phandles(child, phandle_delta);
}
static int update_usages_of_a_phandle_reference(struct device_node *overlay,
const struct property *prop_fixup, phandle phandle)
{
struct device_node *refnode;
const struct property *prop;
char *value __free(kfree) = kmemdup(prop_fixup->value, prop_fixup->length, GFP_KERNEL);
char *cur, *end, *node_path, *prop_name, *s;
int offset, len;
int err = 0;
if (!value)
return -ENOMEM;
/* prop_fixup contains a list of tuples of path:property_name:offset */
end = value + prop_fixup->length;
for (cur = value; cur < end; cur += len + 1) {
len = strlen(cur);
node_path = cur;
s = strchr(cur, ':');
if (!s)
return -EINVAL;
*s++ = '\0';
prop_name = s;
s = strchr(s, ':');
if (!s)
return -EINVAL;
*s++ = '\0';
err = kstrtoint(s, 10, &offset);
if (err)
return err;
refnode = __of_find_node_by_full_path(of_node_get(overlay), node_path);
if (!refnode)
continue;
for_each_property_of_node(refnode, prop) {
if (!of_prop_cmp(prop->name, prop_name))
break;
}
of_node_put(refnode);
if (!prop)
return -ENOENT;
if (offset < 0 || offset + sizeof(__be32) > prop->length)
return -EINVAL;
*(__be32 *)(prop->value + offset) = cpu_to_be32(phandle);
}
return 0;
}
/* compare nodes taking into account that 'name' strips out the @ part */
static int node_name_cmp(const struct device_node *dn1,
const struct device_node *dn2)
{
const char *n1 = kbasename(dn1->full_name);
const char *n2 = kbasename(dn2->full_name);
return of_node_cmp(n1, n2);
}
/*
* Adjust the local phandle references by the given phandle delta.
*
* Subtree @local_fixups, which is overlay node __local_fixups__,
* mirrors the fragment node structure at the root of the overlay.
*
* For each property in the fragments that contains a phandle reference,
* @local_fixups has a property of the same name that contains a list
* of offsets of the phandle reference(s) within the respective property
* value(s). The values at these offsets will be fixed up.
*/
static int adjust_local_phandle_references(const struct device_node *local_fixups,
const struct device_node *overlay, int phandle_delta)
{
struct device_node *overlay_child;
const struct property *prop_fix, *prop;
int err, i, count;
unsigned int off;
if (!local_fixups)
return 0;
for_each_property_of_node(local_fixups, prop_fix) {
/* skip properties added automatically */
if (is_pseudo_property(prop_fix->name))
continue;
if ((prop_fix->length % 4) != 0 || prop_fix->length == 0)
return -EINVAL;
count = prop_fix->length / sizeof(__be32);
for_each_property_of_node(overlay, prop) {
if (!of_prop_cmp(prop->name, prop_fix->name))
break;
}
if (!prop)
return -EINVAL;
for (i = 0; i < count; i++) {
off = be32_to_cpu(((__be32 *)prop_fix->value)[i]);
if ((off + 4) > prop->length)
return -EINVAL;
be32_add_cpu(prop->value + off, phandle_delta);
}
}
/*
* These nested loops recurse down two subtrees in parallel, where the
* node names in the two subtrees match.
*
* The roots of the subtrees are the overlay's __local_fixups__ node
* and the overlay's root node.
*/
for_each_child_of_node_scoped(local_fixups, child) {
for_each_child_of_node(overlay, overlay_child)
if (!node_name_cmp(child, overlay_child)) {
of_node_put(overlay_child);
break;
}
if (!overlay_child)
return -EINVAL;
err = adjust_local_phandle_references(child, overlay_child,
phandle_delta);
if (err)
return err;
}
return 0;
}
/**
* of_resolve_phandles - Relocate and resolve overlay against live tree
*
* @overlay: Pointer to devicetree overlay to relocate and resolve
*
* Modify (relocate) values of local phandles in @overlay to a range that
* does not conflict with the live expanded devicetree. Update references
* to the local phandles in @overlay. Update (resolve) phandle references
* in @overlay that refer to the live expanded devicetree.
*
* Phandle values in the live tree are in the range of
* 1 .. live_tree_max_phandle(). The range of phandle values in the overlay
* also begin with at 1. Adjust the phandle values in the overlay to begin
* at live_tree_max_phandle() + 1. Update references to the phandles to
* the adjusted phandle values.
*
* The name of each property in the "__fixups__" node in the overlay matches
* the name of a symbol (a label) in the live tree. The values of each
* property in the "__fixups__" node is a list of the property values in the
* overlay that need to be updated to contain the phandle reference
* corresponding to that symbol in the live tree. Update the references in
* the overlay with the phandle values in the live tree.
*
* @overlay must be detached.
*
* Resolving and applying @overlay to the live expanded devicetree must be
* protected by a mechanism to ensure that multiple overlays are processed
* in a single threaded manner so that multiple overlays will not relocate
* phandles to overlapping ranges. The mechanism to enforce this is not
* yet implemented.
*
* Return: %0 on success or a negative error value on error.
*/
int of_resolve_phandles(struct device_node *overlay)
{
struct device_node *child, *refnode;
struct device_node *overlay_fixups;
struct device_node __free(device_node) *local_fixups = NULL;
struct property *prop;
const char *refpath;
phandle phandle, phandle_delta;
int err;
if (!overlay) {
pr_err("null overlay\n");
return -EINVAL;
}
if (!of_node_check_flag(overlay, OF_DETACHED)) {
pr_err("overlay not detached\n");
return -EINVAL;
}
phandle_delta = live_tree_max_phandle() + 1;
adjust_overlay_phandles(overlay, phandle_delta);
for_each_child_of_node(overlay, local_fixups)
if (of_node_name_eq(local_fixups, "__local_fixups__"))
break;
err = adjust_local_phandle_references(local_fixups, overlay, phandle_delta);
if (err)
return err;
overlay_fixups = NULL;
for_each_child_of_node(overlay, child) {
if (of_node_name_eq(child, "__fixups__"))
overlay_fixups = child;
}
if (!overlay_fixups)
return 0;
struct device_node __free(device_node) *tree_symbols = of_find_node_by_path("/__symbols__");
if (!tree_symbols) {
pr_err("no symbols in root of device tree.\n");
return -EINVAL;
}
for_each_property_of_node(overlay_fixups, prop) {
/* skip properties added automatically */
if (!of_prop_cmp(prop->name, "name"))
continue;
err = of_property_read_string(tree_symbols,
prop->name, &refpath);
if (err) {
pr_err("node label '%s' not found in live devicetree symbols table\n",
prop->name);
return err;
}
refnode = of_find_node_by_path(refpath);
if (!refnode)
return -ENOENT;
phandle = refnode->phandle;
of_node_put(refnode);
err = update_usages_of_a_phandle_reference(overlay, prop, phandle);
if (err)
break;
}
if (err)
pr_err("overlay phandle fixup failed: %d\n", err);
return err;
}
EXPORT_SYMBOL_GPL(of_resolve_phandles);
@@ -0,0 +1,106 @@
# SPDX-License-Identifier: GPL-2.0
obj-y += testcases.dtbo.o
obj-$(CONFIG_OF_OVERLAY) += overlay.dtbo.o \
overlay_0.dtbo.o \
overlay_1.dtbo.o \
overlay_2.dtbo.o \
overlay_3.dtbo.o \
overlay_4.dtbo.o \
overlay_5.dtbo.o \
overlay_6.dtbo.o \
overlay_7.dtbo.o \
overlay_8.dtbo.o \
overlay_9.dtbo.o \
overlay_10.dtbo.o \
overlay_11.dtbo.o \
overlay_12.dtbo.o \
overlay_13.dtbo.o \
overlay_15.dtbo.o \
overlay_16.dtbo.o \
overlay_17.dtbo.o \
overlay_18.dtbo.o \
overlay_19.dtbo.o \
overlay_20.dtbo.o \
overlay_bad_add_dup_node.dtbo.o \
overlay_bad_add_dup_prop.dtbo.o \
overlay_bad_phandle.dtbo.o \
overlay_bad_symbol.dtbo.o \
overlay_base.dtbo.o \
overlay_gpio_01.dtbo.o \
overlay_gpio_02a.dtbo.o \
overlay_gpio_02b.dtbo.o \
overlay_gpio_03.dtbo.o \
overlay_gpio_04a.dtbo.o \
overlay_gpio_04b.dtbo.o \
overlay_pci_node.dtbo.o \
overlay_bad_unresolved.dtbo.o
# enable creation of __symbols__ node
DTC_FLAGS_overlay += -@
DTC_FLAGS_overlay_bad_phandle += -@
DTC_FLAGS_overlay_bad_symbol += -@
DTC_FLAGS_overlay_base += -@
DTC_FLAGS_testcases += -@
# suppress warnings about intentional errors
DTC_FLAGS_testcases += -Wno-interrupts_property \
-Wno-node_name_vs_property_name \
-Wno-interrupt_map
# Apply overlays statically with fdtoverlay. This is a build time test that
# the overlays can be applied successfully by fdtoverlay. This does not
# guarantee that the overlays can be applied successfully at run time by
# unittest, but it provides a bit of build time test coverage for those
# who do not execute unittest.
#
# The overlays are applied on top of static_base_1.dtb and static_base_2.dtb to
# create static_test_1.dtb and static_test_2.dtb. If fdtoverlay detects an
# error than the kernel build will fail. static_test_1.dtb and
# static_test_2.dtb are not consumed by unittest.
#
# Some unittest overlays deliberately contain errors that unittest checks for.
# These overlays will cause fdtoverlay to fail, and are thus not included
# in the static test:
# overlay_bad_add_dup_node.dtbo \
# overlay_bad_add_dup_prop.dtbo \
# overlay_bad_phandle.dtbo \
# overlay_bad_symbol.dtbo \
apply_static_overlay_1 := overlay_0.dtbo \
overlay_1.dtbo \
overlay_2.dtbo \
overlay_3.dtbo \
overlay_4.dtbo \
overlay_5.dtbo \
overlay_6.dtbo \
overlay_7.dtbo \
overlay_8.dtbo \
overlay_9.dtbo \
overlay_10.dtbo \
overlay_11.dtbo \
overlay_12.dtbo \
overlay_13.dtbo \
overlay_15.dtbo \
overlay_16.dtbo \
overlay_17.dtbo \
overlay_18.dtbo \
overlay_19.dtbo \
overlay_20.dtbo \
overlay_gpio_01.dtbo \
overlay_gpio_02a.dtbo \
overlay_gpio_02b.dtbo \
overlay_gpio_03.dtbo \
overlay_gpio_04a.dtbo \
overlay_gpio_04b.dtbo
apply_static_overlay_2 := overlay.dtbo
DTC_FLAGS_static_base_1 += -Wno-interrupts_property \
-Wno-node_name_vs_property_name \
-Wno-interrupt_map
static_test_1-dtbs := static_base_1.dtb $(apply_static_overlay_1)
static_test_2-dtbs := static_base_2.dtb $(apply_static_overlay_2)
dtb-$(CONFIG_OF_OVERLAY) += static_test_1.dtb static_test_2.dtb
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
&electric_1 {
status = "okay";
hvac_2: hvac-large-1 {
compatible = "ot,hvac-large";
heat-range = <40 75>;
cool-range = <65 80>;
};
};
&rides_1 {
#address-cells = <1>;
#size-cells = <1>;
status = "okay";
ride@100 {
#address-cells = <1>;
#size-cells = <1>;
track@30 {
incline-up = <48 32 16>;
};
track@40 {
incline-up = <47 31 15>;
};
};
ride_200: ride@200 {
#address-cells = <1>;
#size-cells = <1>;
compatible = "ot,ferris-wheel";
reg = <0x00000200 0x100>;
hvac-provider = <&hvac_2>;
hvac-thermostat = <27 32> ;
hvac-zones = <12 5>;
hvac-zone-names = "operator", "snack-bar";
spin-controller = <&spin_ctrl_1 3>;
spin-rph = <30>;
gondolas = <16>;
gondola-capacity = <6>;
ride_200_left: track@10 {
reg = <0x00000010 0x10>;
};
ride_200_right: track@20 {
reg = <0x00000020 0x10>;
};
};
};
&lights_2 {
status = "okay";
color = "purple", "white", "red", "green";
rate = <3 256>;
};
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
/* overlay_0 - enable using absolute target path */
&{/testcase-data/overlay-node/test-bus/test-unittest0} {
status = "okay";
};
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
/* overlay_1 - disable using absolute target path */
&{/testcase-data/overlay-node/test-bus/test-unittest1} {
status = "disabled";
};
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
/* overlay_10 */
/* overlays 8, 9, 10, 11 application and removal in bad sequence */
&unittest_test_bus {
/* suppress DTC warning */
#address-cells = <1>;
#size-cells = <0>;
test-unittest10 {
compatible = "unittest";
status = "okay";
reg = <10>;
#address-cells = <1>;
#size-cells = <0>;
test-unittest101 {
compatible = "unittest";
status = "okay";
reg = <1>;
};
};
};
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
/* overlay_11 */
/* overlays 8, 9, 10, 11 application and removal in bad sequence */
&unittest_test_bus {
/* suppress DTC warning */
#address-cells = <1>;
#size-cells = <0>;
test-unittest11 {
compatible = "unittest";
status = "okay";
reg = <11>;
#address-cells = <1>;
#size-cells = <0>;
test-unittest111 {
compatible = "unittest";
status = "okay";
reg = <1>;
};
};
};
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
/* overlay_12 - enable using absolute target path (i2c) */
&{/testcase-data/overlay-node/test-bus/i2c-test-bus/test-unittest12} {
status = "okay";
};
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
/* overlay_13 - disable using absolute target path (i2c) */
&{/testcase-data/overlay-node/test-bus/i2c-test-bus/test-unittest13} {
status = "disabled";
};
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
/* overlay_15 - mux overlay */
&unittest_i2c_test_bus {
#address-cells = <1>;
#size-cells = <0>;
test-unittest15 {
reg = <11>;
compatible = "unittest-i2c-mux";
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
i2c@0 {
#address-cells = <1>;
#size-cells = <0>;
reg = <0>;
test-mux-dev@20 {
reg = <0x20>;
compatible = "unittest-i2c-dev";
status = "okay";
};
};
};
};
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
/* overlay_16 - notify test */
&unittest_test_bus {
#address-cells = <1>;
#size-cells = <0>;
test-unittest16 {
compatible = "unittest";
reg = <16>;
};
};
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
/* overlay_17 - notify test */
&unittest_test_bus {
#address-cells = <1>;
#size-cells = <0>;
test-unittest17 {
compatible = "unittest";
reg = <17>;
};
};
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
/* overlay_18 - notify test */
&unittest_test_bus {
#address-cells = <1>;
#size-cells = <0>;
test-unittest18 {
compatible = "unittest";
reg = <18>;
};
};
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
/* overlay_19 - notify test */
&unittest_test_bus {
#address-cells = <1>;
#size-cells = <0>;
test-unittest19 {
compatible = "unittest";
reg = <19>;
};
};
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
/* overlay_2 - enable using label */
&unittest2 {
status = "okay";
};
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
/* overlay_20 - notify test */
&unittest_test_bus {
#address-cells = <1>;
#size-cells = <0>;
test-unittest20 {
compatible = "unittest";
reg = <20>;
};
};
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
/* overlay_3 - disable using label */
&unittest3 {
status = "disabled";
};
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
/* overlay_4 - test insertion of a full node */
&unittest_test_bus {
/* suppress DTC warning */
#address-cells = <1>;
#size-cells = <0>;
test-unittest4 {
compatible = "unittest";
status = "okay";
reg = <4>;
};
};
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
/* overlay_5 - test overlay apply revert */
&unittest5 {
status = "okay";
};
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
/* overlay_6 */
/* overlays 6, 7 application and removal in sequence */
&unittest6 {
status = "okay";
};
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
/* overlay_7 */
/* overlays 6, 7 application and removal in sequence */
&unittest7 {
status = "okay";
};
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
/* overlay_8 */
/* overlays 8, 9, 10, 11 application and removal in bad sequence */
&unittest8 {
status = "okay";
};
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
/* overlay_9 */
/* overlays 8, 9, 10, 11 application and removal in bad sequence */
&unittest8 {
property-foo = "bar";
};
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
/*
* &electric_1/motor-1 and &spin_ctrl_1 are the same node:
* /testcase-data-2/substation@100/motor-1
*
* Thus the new node "controller" in each fragment will
* result in an attempt to add the same node twice.
* This will result in an error and the overlay apply
* will fail.
*/
&electric_1 {
motor-1 {
controller {
power_bus = <0x1 0x2>;
};
};
};
&spin_ctrl_1 {
controller {
power_bus_emergency = <0x101 0x102>;
};
};
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
/*
* &electric_1/motor-1/electric and &spin_ctrl_1/electric are the same node:
* /testcase-data-2/substation@100/motor-1/electric
*
* Thus the property "rpm_avail" in each fragment will
* result in an attempt to update the same property twice.
* This will result in an error and the overlay apply
* will fail.
*
* The previous version of this test did not include the extra
* level of node 'electric'. That resulted in the 'rpm_avail'
* property being located in the pre-existing node 'motor-1'.
* Modifying a property results in a WARNING that a memory leak
* will occur if the overlay is removed. Since the overlay apply
* fails, the memory leak does actually occur, and kmemleak will
* further report the memory leak if CONFIG_DEBUG_KMEMLEAK is
* enabled. Adding the overlay node 'electric' avoids the
* memory leak and thus people who use kmemleak will not
* have to debug this non-problem again.
*/
&electric_1 {
motor-1 {
electric {
rpm_avail = <100>;
};
};
};
&spin_ctrl_1 {
electric {
rpm_avail = <100 200>;
};
};
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
&electric_1 {
// This label should cause an error when the overlay
// is applied. There is already a phandle value
// in the base tree for motor-1.
spin_ctrl_1_conflict: motor-1 {
accelerate = <3>;
decelerate = <5>;
};
};
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
&electric_1 {
// This label should cause an error when the overlay
// is applied. There is already a symbol hvac_1
// in the base tree
hvac_1: hvac-medium-2 {
compatible = "ot,hvac-medium";
heat-range = <50 75>;
cool-range = <60 80>;
};
};
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
&this_label_does_not_exist {
status = "okay";
};
@@ -0,0 +1,5 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
#include "overlay_common.dtsi"
@@ -0,0 +1,89 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Base device tree that overlays will be applied against.
*
* Do not add any properties in node "/".
* Do not add any nodes other than "/testcase-data-2" in node "/".
* Do not add anything that would result in dtc creating node "/__fixups__".
* dtc will create nodes "/__symbols__" and "/__local_fixups__".
*/
/ {
testcase-data-2 {
#address-cells = <1>;
#size-cells = <1>;
electric_1: substation@100 {
compatible = "ot,big-volts-control";
reg = <0x00000100 0x100>;
status = "disabled";
hvac_1: hvac-medium-1 {
compatible = "ot,hvac-medium";
heat-range = <50 75>;
cool-range = <60 80>;
};
spin_ctrl_1: motor-1 {
compatible = "ot,ferris-wheel-motor";
spin = "clockwise";
rpm_avail = <50>;
};
spin_ctrl_2: motor-8 {
compatible = "ot,roller-coaster-motor";
};
};
rides_1: fairway-1 {
#address-cells = <1>;
#size-cells = <1>;
compatible = "ot,rides";
status = "disabled";
orientation = <127>;
ride@100 {
#address-cells = <1>;
#size-cells = <1>;
compatible = "ot,roller-coaster";
reg = <0x00000100 0x100>;
hvac-provider = <&hvac_1>;
hvac-thermostat = <29> ;
hvac-zones = <14>;
hvac-zone-names = "operator";
spin-controller = <&spin_ctrl_2 5 &spin_ctrl_2 7>;
spin-controller-names = "track_1", "track_2";
queues = <2>;
track@30 {
reg = <0x00000030 0x10>;
};
track@40 {
reg = <0x00000040 0x10>;
};
};
};
lights_1: lights@30000 {
compatible = "ot,work-lights";
reg = <0x00030000 0x1000>;
status = "disabled";
};
lights_2: lights@40000 {
compatible = "ot,show-lights";
reg = <0x00040000 0x1000>;
status = "disabled";
rate = <13 138>;
};
retail_1: vending@50000 {
reg = <0x00050000 0x1000>;
compatible = "ot,tickets";
status = "disabled";
};
};
};
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
&unittest_test_bus {
#address-cells = <1>;
#size-cells = <0>;
gpio@0 {
compatible = "unittest-gpio";
reg = <0>;
gpio-controller;
#gpio-cells = <2>;
ngpios = <2>;
gpio-line-names = "line-A", "line-B";
line-b {
gpio-hog;
gpios = <2 0>;
input;
line-name = "line-B-input";
};
};
};
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
&unittest_test_bus {
#address-cells = <1>;
#size-cells = <0>;
gpio@2 {
compatible = "unittest-gpio";
reg = <2>;
gpio-controller;
#gpio-cells = <2>;
ngpios = <2>;
gpio-line-names = "line-A", "line-B";
};
};
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
&unittest_test_bus {
#address-cells = <1>;
#size-cells = <0>;
gpio@2 {
line-a {
gpio-hog;
gpios = <1 0>;
input;
line-name = "line-A-input";
};
};
};
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
&unittest_test_bus {
#address-cells = <1>;
#size-cells = <0>;
gpio@3 {
compatible = "unittest-gpio";
reg = <3>;
gpio-controller;
#gpio-cells = <2>;
ngpios = <2>;
gpio-line-names = "line-A", "line-B", "line-C", "line-D";
line-d {
gpio-hog;
gpios = <4 0>;
input;
line-name = "line-D-input";
};
};
};
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
&unittest_test_bus {
#address-cells = <1>;
#size-cells = <0>;
gpio@4 {
compatible = "unittest-gpio";
reg = <4>;
gpio-controller;
#gpio-cells = <2>;
ngpios = <2>;
gpio-line-names = "line-A", "line-B", "line-C", "line-D";
};
};
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
&unittest_test_bus {
#address-cells = <1>;
#size-cells = <0>;
gpio@4 {
line-c {
gpio-hog;
gpios = <3 0>;
input;
line-name = "line-C-input";
};
};
};
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/ {
fragment@0 {
target-path="";
__overlay__ {
#address-cells = <3>;
#size-cells = <2>;
pci-ep-bus@0 {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
ranges = <0x0 0x0 0x0 0x0 0x1000>;
reg = <0 0 0 0 0>;
unittest-pci@100 {
compatible = "unittest-pci";
reg = <0x100 0x200>;
};
};
};
};
};
@@ -0,0 +1,4 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
#include "testcases_common.dtsi"
@@ -0,0 +1,4 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
#include "overlay_common.dtsi"
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
#include "testcases_common.dtsi"
/ {
/*
* testcase data that intentionally results in an error is located here
* instead of in testcases_common.dtsi so that the static overlay apply
* tests will not include the error.
*/
testcase-data {
testcase-device2 {
compatible = "testcase-device";
interrupt-parent = <&test_intc2>;
interrupts = <1>; /* invalid specifier - too short */
};
};
};
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: GPL-2.0
/ {
testcase-data {
changeset {
prop-update = "hello";
prop-remove = "world";
node-remove {
};
};
};
};
#include "tests-phandle.dtsi"
#include "tests-interrupts.dtsi"
#include "tests-match.dtsi"
#include "tests-address.dtsi"
#include "tests-platform.dtsi"
#include "tests-overlay.dtsi"
#include "tests-lifecycle.dtsi"
@@ -0,0 +1,158 @@
// SPDX-License-Identifier: GPL-2.0
/ {
#address-cells = <1>;
#size-cells = <1>;
testcase-data {
#address-cells = <1>;
#size-cells = <1>;
ranges;
address-tests {
#address-cells = <1>;
#size-cells = <1>;
/* ranges here is to make sure we don't use it for
* dma-ranges translation */
ranges = <0x70000000 0x70000000 0x50000000>,
<0x00000000 0xd0000000 0x20000000>;
dma-ranges = <0x0 0x20000000 0x40000000>;
device@70000000 {
reg = <0x70000000 0x1000>;
};
bus@80000000 {
#address-cells = <2>;
#size-cells = <2>;
ranges = <0x0 0x0 0x80000000 0x0 0x100000>;
dma-ranges = <0x1 0x0 0x0 0x20 0x0>;
device@1000 {
reg = <0x0 0x1000 0x0 0x1000>;
};
};
pci@90000000 {
device_type = "pci";
#address-cells = <3>;
#size-cells = <2>;
reg = <0x90000000 0x1000>;
ranges = <0x42000000 0x0 0x40000000 0x40000000 0x0 0x10000000>;
dma-ranges = <0x42000000 0x0 0x80000000 0x00000000 0x0 0x10000000>,
<0x42000000 0x0 0xc0000000 0x20000000 0x0 0x10000000>;
};
bus@a0000000 {
#address-cells = <3>;
#size-cells = <2>;
ranges = <0xf00baa 0x0 0x0 0xa0000000 0x0 0x100000>,
<0xf00bee 0x1 0x0 0xb0000000 0x0 0x200000>;
};
};
address-tests2 {
#address-cells = <2>;
#size-cells = <1>;
ranges = <0x10000000 0x01000000 0xa0000000 0x01000000>,
<0x10000000 0x02000000 0xb0000000 0x01000000>,
<0x20000000 0x01000000 0xc0000000 0x01000000>,
<0x20000000 0x02000000 0xd0000000 0x01000000>,
<0x00000000 0xd1000000 0xd1000000 0x01000000>,
<0x00000000 0xe8000000 0xe8000000 0x07f00000>,
<0x00000000 0xefff0000 0xefff0000 0x00010000>;
bus-2cell@10000000 {
#address-cells = <2>;
#size-cells = <1>;
ranges = <0x100000 0x10000 0x10000000 0x1a00000 0x10000>,
<0x100000 0x20000 0x10000000 0x1b00000 0x10000>,
<0x200000 0x10000 0x20000000 0x1c00000 0x10000>,
<0x200000 0x20000 0x20000000 0x2d00000 0x10000>;
device@100000 {
reg = <0x100000 0x11000 0x100>,
<0x100000 0x12000 0x100>,
<0x200000 0x11000 0x100>,
<0x200000 0x21000 0x100>;
};
};
bus-3cell@20000000 {
#address-cells = <3>;
#size-cells = <1>;
ranges = <0x1 0x100000 0x10000 0x10000000 0x1a00000 0x10000>,
<0x2 0x100000 0x10000 0x10000000 0x1b00000 0x10000>,
<0x3 0x200000 0x10000 0x20000000 0x1c00000 0x10000>,
<0x4 0x200000 0x20000 0x20000000 0x2d00000 0x10000>;
local-bus@100000 {
#address-cells = <1>;
#size-cells = <1>;
ranges = <0xf1000000 0x1 0x100000 0x10000 0x10000>,
<0xf2000000 0x2 0x100000 0x10000 0x10000>,
<0xf3000000 0x3 0x200000 0x10000 0x08000>,
<0xf3800000 0x3 0x200000 0x18000 0x08000>,
<0xf4000000 0x4 0x200000 0x20000 0x10000>;
device@f1001000 {
reg = <0xf1001000 0x100>,
<0xf2002000 0x100>,
<0xf3001000 0x100>,
<0xf3801000 0x100>,
<0xf4001000 0x100>;
};
};
};
pcie@d1070000 {
#address-cells = <0x03>;
#size-cells = <0x02>;
bus-range = <0x00 0xff>;
device_type = "pci";
ranges = <0x82000000 0 0xe8000000 0 0xe8000000 0 0x7f00000>,
<0x81000000 0 0x00000000 0 0xefff0000 0 0x0010000>;
dma-ranges = <0x43000000 0x10 0x00 0x00 0x00 0x00 0x10000000>;
reg = <0x00000000 0xd1070000 0x20000>;
pci@0,0 {
#address-cells = <0x03>;
#size-cells = <0x02>;
bus-range = <0x01 0x01>;
device_type = "pci";
ranges = <0x82000000 0 0xe8000000
0x82000000 0 0xe8000000
0 0x4400000>;
reg = <0x00 0x00 0x00 0x00 0x00>;
dev@0,0 {
#address-cells = <0x03>;
#size-cells = <0x02>;
ranges = <0 0 0 0x82010000 0 0xe8000000 0 0x2000000>,
<1 0 0 0x82010000 0 0xea000000 0 0x1000000>,
<2 0 0 0x82010000 0 0xeb000000 0 0x0800000>,
<3 0 0 0x82010000 0 0xeb800000 0 0x0800000>,
<4 0 0 0x82010000 0 0xec000000 0 0x0020000>,
<5 0 0 0x82010000 0 0xec020000 0 0x0002000>;
reg = <0x10000 0x00 0x00 0x00 0x00>;
local-bus@0 {
#address-cells = <0x01>;
#size-cells = <0x01>;
ranges = <0xa0000000 0 0 0 0x2000000>,
<0xb0000000 1 0 0 0x1000000>;
dma-ranges = <0xc0000000 0x43000000 0x10 0x00 0x10000000>;
dev@e0000000 {
reg = <0xa0001000 0x1000>,
<0xb0002000 0x2000>;
};
};
};
};
};
};
};
};
@@ -0,0 +1,113 @@
// SPDX-License-Identifier: GPL-2.0
/ {
testcase-data {
interrupts {
#address-cells = <1>;
#size-cells = <1>;
test_intc0: intc0 {
interrupt-controller;
#interrupt-cells = <1>;
};
test_intc1: intc1 {
interrupt-controller;
#interrupt-cells = <3>;
};
test_intc2: intc2 {
interrupt-controller;
#interrupt-cells = <2>;
};
test_intmap0: intmap0 {
#interrupt-cells = <1>;
#address-cells = <0>;
interrupt-map = <1 &test_intc0 9>,
<2 &test_intc1 10 11 12>,
<3 &test_intc2 13 14>,
<4 &test_intc2 15 16>;
};
test_intmap1: intmap1 {
#interrupt-cells = <2>;
/*
* #address-cells is required
*
* The property is not provided in this node to
* test that the code will properly handle
* this case for legacy .dts files.
*
* Not having #address-cells will result in a
* warning from dtc starting with
* version v1.6.1-19-g0a3a9d3449c8
* The warning is suppressed by adding
* -Wno-interrupt_map to the Makefile for all
* .dts files this include this .dtsi
#address-cells = <1>;
*/
interrupt-map = <0x5000 1 2 &test_intc0 15>;
};
intmap2 {
#interrupt-cells = <2>;
#address-cells = <0>;
interrupt-map = <1 11 &test_intc0 100>,
<2 22 &test_intc1 200 201 202>,
<3 33 &test_intc2 300 301>,
<4 44 &test_intc2 400 401>;
};
test_intc_intmap0: intc-intmap0 {
#interrupt-cells = <1>;
#address-cells = <1>;
interrupt-controller;
interrupt-map = <0x6000 1 &test_intc_intmap0 0x7000 2>;
};
interrupts0 {
interrupt-parent = <&test_intc0>;
interrupts = <1>, <2>, <3>, <4>;
};
interrupts1 {
interrupt-parent = <&test_intmap0>;
interrupts = <1>, <2>, <3>, <4>;
};
interrupts2 {
reg = <0x6000 0x100>;
interrupt-parent = <&test_intc_intmap0>;
interrupts = <1>;
};
interrupts-extended0 {
reg = <0x5000 0x100>;
/*
* Do not remove &test_intmap1 from this
* property - see comment in node intmap1
*/
interrupts-extended = <&test_intc0 1>,
<&test_intc1 2 3 4>,
<&test_intc2 5 6>,
<&test_intmap0 1>,
<&test_intmap0 2>,
<&test_intmap0 3>,
<&test_intmap1 1 2>;
};
};
testcase-device1 {
compatible = "testcase-device";
interrupt-parent = <&test_intc0>;
interrupts = <1>;
};
/*
* testcase data that intentionally results in an error is
* located in testcases.dts instead of in this file so that the
* static overlay apply tests will not include the error.
*/
};
};
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: GPL-2.0
/ {
testcase-data {
refcount-node {
};
};
};
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: GPL-2.0
/ {
testcase-data {
match-node {
name0 { };
name1 { device_type = "type1"; };
a { name2 { device_type = "type1"; }; };
b { name2 { }; };
c { name2 { device_type = "type2"; }; };
name3 { compatible = "compat3"; };
name4 { compatible = "compat2", "compat3"; };
name5 { compatible = "compat2", "compat3"; };
name6 { compatible = "compat1", "compat2", "compat3"; };
name7 { compatible = "compat2"; device_type = "type1"; };
name8 { compatible = "compat2"; device_type = "type1"; };
name9 { compatible = "compat2"; };
};
};
};
@@ -0,0 +1,116 @@
// SPDX-License-Identifier: GPL-2.0
/ {
testcase-data {
overlay-node {
/* test bus */
unittest_test_bus: test-bus {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <0>;
unittest100: test-unittest100 {
compatible = "unittest";
status = "okay";
reg = <100>;
};
unittest101: test-unittest101 {
compatible = "unittest";
status = "disabled";
reg = <101>;
};
unittest0: test-unittest0 {
compatible = "unittest";
status = "disabled";
reg = <0>;
};
unittest1: test-unittest1 {
compatible = "unittest";
status = "okay";
reg = <1>;
};
unittest2: test-unittest2 {
compatible = "unittest";
status = "disabled";
reg = <2>;
};
unittest3: test-unittest3 {
compatible = "unittest";
status = "okay";
reg = <3>;
};
unittest5: test-unittest5 {
compatible = "unittest";
status = "disabled";
reg = <5>;
};
unittest6: test-unittest6 {
compatible = "unittest";
status = "disabled";
reg = <6>;
};
unittest7: test-unittest7 {
compatible = "unittest";
status = "disabled";
reg = <7>;
};
unittest8: test-unittest8 {
compatible = "unittest";
status = "disabled";
reg = <8>;
};
unittest_i2c_test_bus: i2c-test-bus {
compatible = "unittest-i2c-bus";
status = "okay";
reg = <50>;
#address-cells = <1>;
#size-cells = <0>;
test-unittest12 {
reg = <8>;
compatible = "unittest-i2c-dev";
status = "disabled";
};
test-unittest13 {
reg = <9>;
compatible = "unittest-i2c-dev";
status = "okay";
};
test-unittest14 {
reg = <10>;
compatible = "unittest-i2c-mux";
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
i2c@0 {
#address-cells = <1>;
#size-cells = <0>;
reg = <0>;
test-mux-dev@20 {
reg = <0x20>;
compatible = "unittest-i2c-dev";
status = "okay";
};
};
};
};
};
};
};
};
@@ -0,0 +1,84 @@
// SPDX-License-Identifier: GPL-2.0
/ {
aliases {
testcase-alias = &testcase;
};
testcase: testcase-data {
security-password = "password";
duplicate-name = "duplicate";
duplicate-name { };
phandle-tests {
provider0: provider0 {
#phandle-cells = <0>;
};
provider1: provider1 {
#phandle-cells = <1>;
};
provider2: provider2 {
#phandle-cells = <2>;
};
provider3: provider3 {
#phandle-cells = <3>;
};
provider4: provider4 {
#phandle-cells = <2>;
phandle-map = <0 1 &provider1 3>,
<4 0 &provider0>,
<16 5 &provider3 3 5 0>,
<200 8 &provider2 23 6>,
<19 0 &provider2 15 0>,
<2 3 &provider3 2 5 3>;
phandle-map-mask = <0xff 0xf>;
phandle-map-pass-thru = <0x0 0xf0>;
};
provider5: provider5 {
#phandle-cells = <2>;
phandle-map = <2 7 &provider4 2 3>;
phandle-map-mask = <0xff 0xf>;
phandle-map-pass-thru = <0x0 0xf0>;
};
consumer-a {
phandle-list = <&provider1 1>,
<&provider2 2 0>,
<0>,
<&provider3 4 4 3>,
<&provider2 5 100>,
<&provider0>,
<&provider1 7>;
phandle-list-names = "first", "second", "third";
phandle-list-bad-phandle = <12345678 0 0>;
phandle-list-bad-args = <&provider2 1 0>,
<&provider3 0>;
empty-property;
string-property = "foobar";
unterminated-string = [40 41 42 43];
unterminated-string-list = "first", "second", [40 41 42 43];
};
consumer-b {
phandle-list = <&provider1 1>,
<&provider4 2 3>,
<0>,
<&provider4 4 0x100>,
<&provider4 0 0x61>,
<&provider0>,
<&provider4 19 0x20>,
<&provider5 2 7>;
phandle-list-bad-phandle = <12345678 0 0>;
phandle-list-bad-args = <&provider2 1 0>,
<&provider4 0>;
};
};
};
};
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: GPL-2.0
/ {
testcase-data {
platform-tests {
#address-cells = <1>;
#size-cells = <0>;
test-device@0 {
compatible = "test-device";
reg = <0x0>;
#address-cells = <1>;
#size-cells = <0>;
dev@100 {
compatible = "test-sub-device";
reg = <0x100>;
};
};
test-device@1 {
compatible = "test-device";
reg = <0x1>;
#address-cells = <1>;
#size-cells = <0>;
dev@100 {
compatible = "test-sub-device",
"test-compat2",
"test-compat3";
reg = <0x100>;
};
};
test-device@2 {
compatible = "test,rust-device";
reg = <0x2>;
test,u32-prop = <0xdeadbeef>;
test,i16-array = /bits/ 16 <1 2 (-3) (-4)>;
ref_child_0: child-0 {
test,ref-arg = <&ref_child_1 0x20 0x32>;
};
ref_child_1: child-1 {
test,ref-arg = <&ref_child_0 0x10 0x64>;
};
};
};
platform-tests-2 {
// No #address-cells or #size-cells
node {
#address-cells = <1>;
#size-cells = <1>;
test-device@100 {
compatible = "test-sub-device";
reg = <0x100 1>;
};
};
};
};
};
File diff suppressed because it is too large Load Diff