summaryrefslogtreecommitdiff
path: root/drivers/of
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/of')
-rw-r--r--drivers/of/.kunitconfig5
-rw-r--r--drivers/of/Kconfig59
-rw-r--r--drivers/of/Makefile17
-rw-r--r--drivers/of/address.c1023
-rw-r--r--drivers/of/base.c1321
-rw-r--r--drivers/of/cpu.c210
-rw-r--r--drivers/of/device.c303
-rw-r--r--drivers/of/dynamic.c482
-rw-r--r--drivers/of/empty_root.dts13
-rw-r--r--drivers/of/fdt.c831
-rw-r--r--drivers/of/fdt_address.c25
-rw-r--r--drivers/of/irq.c455
-rw-r--r--drivers/of/kexec.c516
-rw-r--r--drivers/of/kobj.c21
-rw-r--r--drivers/of/kunit_overlay_test.dtso9
-rw-r--r--drivers/of/module.c81
-rw-r--r--drivers/of/of_kunit_helpers.c93
-rw-r--r--drivers/of/of_mdio.c529
-rw-r--r--drivers/of/of_net.c83
-rw-r--r--drivers/of/of_numa.c15
-rw-r--r--drivers/of/of_private.h103
-rw-r--r--drivers/of/of_reserved_mem.c647
-rw-r--r--drivers/of/of_test.c178
-rw-r--r--drivers/of/overlay.c514
-rw-r--r--drivers/of/overlay_test.c120
-rw-r--r--drivers/of/pdt.c2
-rw-r--r--drivers/of/platform.c321
-rw-r--r--drivers/of/property.c798
-rw-r--r--drivers/of/resolver.c101
-rw-r--r--drivers/of/unittest-data/Makefile120
-rw-r--r--drivers/of/unittest-data/overlay.dtso (renamed from drivers/of/unittest-data/overlay.dts)32
-rw-r--r--drivers/of/unittest-data/overlay_0.dts14
-rw-r--r--drivers/of/unittest-data/overlay_0.dtso9
-rw-r--r--drivers/of/unittest-data/overlay_1.dts14
-rw-r--r--drivers/of/unittest-data/overlay_1.dtso9
-rw-r--r--drivers/of/unittest-data/overlay_10.dtso (renamed from drivers/of/unittest-data/overlay_10.dts)0
-rw-r--r--drivers/of/unittest-data/overlay_11.dtso (renamed from drivers/of/unittest-data/overlay_11.dts)1
-rw-r--r--drivers/of/unittest-data/overlay_12.dts14
-rw-r--r--drivers/of/unittest-data/overlay_12.dtso9
-rw-r--r--drivers/of/unittest-data/overlay_13.dts14
-rw-r--r--drivers/of/unittest-data/overlay_13.dtso9
-rw-r--r--drivers/of/unittest-data/overlay_15.dtso (renamed from drivers/of/unittest-data/overlay_15.dts)1
-rw-r--r--drivers/of/unittest-data/overlay_16.dtso15
-rw-r--r--drivers/of/unittest-data/overlay_17.dtso15
-rw-r--r--drivers/of/unittest-data/overlay_18.dtso15
-rw-r--r--drivers/of/unittest-data/overlay_19.dtso15
-rw-r--r--drivers/of/unittest-data/overlay_2.dtso (renamed from drivers/of/unittest-data/overlay_2.dts)0
-rw-r--r--drivers/of/unittest-data/overlay_20.dtso15
-rw-r--r--drivers/of/unittest-data/overlay_3.dtso (renamed from drivers/of/unittest-data/overlay_3.dts)0
-rw-r--r--drivers/of/unittest-data/overlay_4.dtso (renamed from drivers/of/unittest-data/overlay_4.dts)1
-rw-r--r--drivers/of/unittest-data/overlay_5.dtso (renamed from drivers/of/unittest-data/overlay_5.dts)0
-rw-r--r--drivers/of/unittest-data/overlay_6.dtso (renamed from drivers/of/unittest-data/overlay_6.dts)0
-rw-r--r--drivers/of/unittest-data/overlay_7.dtso (renamed from drivers/of/unittest-data/overlay_7.dts)0
-rw-r--r--drivers/of/unittest-data/overlay_8.dtso (renamed from drivers/of/unittest-data/overlay_8.dts)0
-rw-r--r--drivers/of/unittest-data/overlay_9.dtso (renamed from drivers/of/unittest-data/overlay_9.dts)0
-rw-r--r--drivers/of/unittest-data/overlay_bad_add_dup_node.dtso (renamed from drivers/of/unittest-data/overlay_bad_add_dup_node.dts)9
-rw-r--r--drivers/of/unittest-data/overlay_bad_add_dup_prop.dts24
-rw-r--r--drivers/of/unittest-data/overlay_bad_add_dup_prop.dtso38
-rw-r--r--drivers/of/unittest-data/overlay_bad_phandle.dtso (renamed from drivers/of/unittest-data/overlay_bad_phandle.dts)5
-rw-r--r--drivers/of/unittest-data/overlay_bad_symbol.dtso (renamed from drivers/of/unittest-data/overlay_bad_symbol.dts)5
-rw-r--r--drivers/of/unittest-data/overlay_bad_unresolved.dtso7
-rw-r--r--drivers/of/unittest-data/overlay_base.dtso5
-rw-r--r--drivers/of/unittest-data/overlay_common.dtsi (renamed from drivers/of/unittest-data/overlay_base.dts)38
-rw-r--r--drivers/of/unittest-data/overlay_gpio_01.dtso24
-rw-r--r--drivers/of/unittest-data/overlay_gpio_02a.dtso17
-rw-r--r--drivers/of/unittest-data/overlay_gpio_02b.dtso17
-rw-r--r--drivers/of/unittest-data/overlay_gpio_03.dtso24
-rw-r--r--drivers/of/unittest-data/overlay_gpio_04a.dtso17
-rw-r--r--drivers/of/unittest-data/overlay_gpio_04b.dtso17
-rw-r--r--drivers/of/unittest-data/overlay_pci_node.dtso22
-rw-r--r--drivers/of/unittest-data/static_base_1.dts4
-rw-r--r--drivers/of/unittest-data/static_base_2.dts4
-rw-r--r--drivers/of/unittest-data/testcases.dtso21
-rw-r--r--drivers/of/unittest-data/testcases_common.dtsi (renamed from drivers/of/unittest-data/testcases.dts)6
-rw-r--r--drivers/of/unittest-data/tests-address.dtsi158
-rw-r--r--drivers/of/unittest-data/tests-interrupts.dtsi44
-rw-r--r--drivers/of/unittest-data/tests-lifecycle.dtsi8
-rw-r--r--drivers/of/unittest-data/tests-overlay.dtsi1
-rw-r--r--drivers/of/unittest-data/tests-phandle.dtsi12
-rw-r--r--drivers/of/unittest-data/tests-platform.dtsi28
-rw-r--r--drivers/of/unittest.c2424
81 files changed, 8393 insertions, 3783 deletions
diff --git a/drivers/of/.kunitconfig b/drivers/of/.kunitconfig
new file mode 100644
index 000000000000..7d570cb922a1
--- /dev/null
+++ b/drivers/of/.kunitconfig
@@ -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
diff --git a/drivers/of/Kconfig b/drivers/of/Kconfig
index ad3fcad4d75b..50697cc3b07e 100644
--- a/drivers/of/Kconfig
+++ b/drivers/of/Kconfig
@@ -2,6 +2,12 @@
config DTC
bool
+config GENERIC_BUILTIN_DTB
+ bool
+
+config BUILTIN_DTB_ALL
+ bool
+
menuconfig OF
bool "Device Tree and Open Firmware support"
help
@@ -14,15 +20,35 @@ if OF
config OF_UNITTEST
bool "Device Tree runtime unit tests"
- depends on !SPARC
+ depends on OF_EARLY_FLATTREE
select IRQ_DOMAIN
- select OF_EARLY_FLATTREE
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
@@ -42,7 +68,8 @@ config OF_FLATTREE
select CRC32
config OF_EARLY_FLATTREE
- bool
+ def_bool OF && !(SPARC || ALPHA || HEXAGON || M68K || PARISC || S390)
+ select DMA_DECLARE_COHERENT if HAS_DMA && HAS_IOMEM
select OF_FLATTREE
config OF_PROMTREE
@@ -69,22 +96,8 @@ config OF_IRQ
def_bool y
depends on !SPARC && IRQ_DOMAIN
-config OF_NET
- depends on NETDEVICES
- def_bool y
-
-config OF_MDIO
- def_tristate PHYLIB
- depends on PHYLIB
- select FIXED_PHY
- help
- OpenFirmware MDIO bus (Ethernet PHY) accessors
-
config OF_RESERVED_MEM
- depends on OF_EARLY_FLATTREE
- bool
- help
- Helpers to allow for reservation of memory regions
+ def_bool OF_EARLY_FLATTREE
config OF_RESOLVE
bool
@@ -100,6 +113,16 @@ config OF_OVERLAY
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
diff --git a/drivers/of/Makefile b/drivers/of/Makefile
index 663a4af0cccd..379a0afcbdc0 100644
--- a/drivers/of/Makefile
+++ b/drivers/of/Makefile
@@ -1,18 +1,27 @@
# SPDX-License-Identifier: GPL-2.0
-obj-y = base.o device.o platform.o property.o
+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
+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_NET) += of_net.o
obj-$(CONFIG_OF_UNITTEST) += unittest.o
-obj-$(CONFIG_OF_MDIO) += of_mdio.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/
diff --git a/drivers/of/address.c b/drivers/of/address.c
index 2270373b30ab..4034d798c55a 100644
--- a/drivers/of/address.c
+++ b/drivers/of/address.c
@@ -8,34 +8,20 @@
#include <linux/logic_pio.h>
#include <linux/module.h>
#include <linux/of_address.h>
+#include <linux/overflow.h>
#include <linux/pci.h>
#include <linux/pci_regs.h>
#include <linux/sizes.h>
#include <linux/slab.h>
#include <linux/string.h>
+#include <linux/dma-direct.h> /* for bus_dma_region */
-/* 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)
+#include <kunit/visibility.h>
-static struct of_bus *of_match_bus(struct device_node *np);
-static int __of_address_to_resource(struct device_node *dev,
- const __be32 *addrp, u64 size, unsigned int flags,
- const char *name, struct resource *r);
+/* Uncomment me to enable of_dump_addr() debugging output */
+// #define DEBUG
-/* Debug utility */
-#ifdef DEBUG
-static void 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 of_dump_addr(const char *s, const __be32 *addr, int na) { }
-#endif
+#include "of_private.h"
/* Callbacks for bus specific translators */
struct of_bus {
@@ -45,8 +31,9 @@ struct of_bus {
void (*count_cells)(struct device_node *child,
int *addrc, int *sizec);
u64 (*map)(__be32 *addr, const __be32 *range,
- int na, int ns, int pna);
+ int na, int ns, int pna, int fna);
int (*translate)(__be32 *addr, u64 offset, int na);
+ int flag_cells;
unsigned int (*get_flags)(const __be32 *addr);
};
@@ -64,17 +51,15 @@ static void of_bus_default_count_cells(struct device_node *dev,
}
static u64 of_bus_default_map(__be32 *addr, const __be32 *range,
- int na, int ns, int pna)
+ int na, int ns, int pna, int fna)
{
u64 cp, s, da;
- cp = of_read_number(range, na);
+ cp = of_read_number(range + fna, na - fna);
s = of_read_number(range + na + pna, ns);
- da = of_read_number(addr, na);
+ da = of_read_number(addr + fna, na - fna);
- pr_debug("default map, cp=%llx, s=%llx, da=%llx\n",
- (unsigned long long)cp, (unsigned long long)s,
- (unsigned long long)da);
+ pr_debug("default map, cp=%llx, s=%llx, da=%llx\n", cp, s, da);
if (da < cp || da >= (cp + s))
return OF_BAD_ADDR;
@@ -93,222 +78,128 @@ static int of_bus_default_translate(__be32 *addr, u64 offset, int na)
return 0;
}
+static unsigned int of_bus_default_flags_get_flags(const __be32 *addr)
+{
+ return of_read_number(addr, 1);
+}
+
static unsigned int of_bus_default_get_flags(const __be32 *addr)
{
return IORESOURCE_MEM;
}
-#ifdef CONFIG_PCI
-/*
- * PCI bus specific translator
- */
-
-static int of_bus_pci_match(struct device_node *np)
+static u64 of_bus_default_flags_map(__be32 *addr, const __be32 *range, int na,
+ int ns, int pna, int fna)
{
- /*
- * "pciex" is PCI Express
- * "vci" is for the /chaos bridge on 1st-gen PCI powermacs
- * "ht" is hypertransport
- */
- return of_node_is_type(np, "pci") || of_node_is_type(np, "pciex") ||
- of_node_is_type(np, "vci") || of_node_is_type(np, "ht");
+ /* Check that flags match */
+ if (*addr != *range)
+ return OF_BAD_ADDR;
+
+ return of_bus_default_map(addr, range, na, ns, pna, fna);
}
-static void of_bus_pci_count_cells(struct device_node *np,
- int *addrc, int *sizec)
+static int of_bus_default_flags_translate(__be32 *addr, u64 offset, int na)
{
- if (addrc)
- *addrc = 3;
- if (sizec)
- *sizec = 2;
+ /* Keep "flags" part (high cell) in translated address */
+ return of_bus_default_translate(addr + 1, offset, na - 1);
}
+#ifdef CONFIG_PCI
static unsigned int of_bus_pci_get_flags(const __be32 *addr)
{
unsigned int flags = 0;
u32 w = be32_to_cpup(addr);
+ if (!IS_ENABLED(CONFIG_PCI))
+ return 0;
+
switch((w >> 24) & 0x03) {
case 0x01:
flags |= IORESOURCE_IO;
break;
case 0x02: /* 32 bits */
- case 0x03: /* 64 bits */
flags |= IORESOURCE_MEM;
break;
+
+ case 0x03: /* 64 bits */
+ flags |= IORESOURCE_MEM | IORESOURCE_MEM_64;
+ break;
}
if (w & 0x40000000)
flags |= IORESOURCE_PREFETCH;
return flags;
}
-static u64 of_bus_pci_map(__be32 *addr, const __be32 *range, int na, int ns,
- int pna)
-{
- u64 cp, s, da;
- unsigned int af, rf;
-
- af = of_bus_pci_get_flags(addr);
- rf = of_bus_pci_get_flags(range);
-
- /* Check address type match */
- if ((af ^ rf) & (IORESOURCE_MEM | IORESOURCE_IO))
- return OF_BAD_ADDR;
-
- /* Read address values, skipping high cell */
- cp = of_read_number(range + 1, na - 1);
- s = of_read_number(range + na + pna, ns);
- da = of_read_number(addr + 1, na - 1);
+/*
+ * PCI bus specific translator
+ */
- pr_debug("PCI map, cp=%llx, s=%llx, da=%llx\n",
- (unsigned long long)cp, (unsigned long long)s,
- (unsigned long long)da);
+static bool of_node_is_pcie(const struct device_node *np)
+{
+ bool is_pcie = of_node_name_eq(np, "pcie");
- if (da < cp || da >= (cp + s))
- return OF_BAD_ADDR;
- return da - cp;
-}
+ if (is_pcie)
+ pr_warn_once("%pOF: Missing device_type\n", np);
-static int of_bus_pci_translate(__be32 *addr, u64 offset, int na)
-{
- return of_bus_default_translate(addr + 1, offset, na - 1);
+ return is_pcie;
}
-const __be32 *of_get_pci_address(struct device_node *dev, int bar_no, u64 *size,
- unsigned int *flags)
+static int of_bus_pci_match(struct device_node *np)
{
- const __be32 *prop;
- unsigned int psize;
- struct device_node *parent;
- struct of_bus *bus;
- int onesize, i, na, ns;
-
- /* Get parent & match bus type */
- parent = of_get_parent(dev);
- if (parent == NULL)
- return NULL;
- bus = of_match_bus(parent);
- if (strcmp(bus->name, "pci")) {
- of_node_put(parent);
- return NULL;
- }
- bus->count_cells(dev, &na, &ns);
- of_node_put(parent);
- if (!OF_CHECK_ADDR_COUNT(na))
- return NULL;
-
- /* Get "reg" or "assigned-addresses" property */
- prop = of_get_property(dev, bus->addresses, &psize);
- if (prop == NULL)
- return NULL;
- psize /= 4;
-
- onesize = na + ns;
- for (i = 0; psize >= onesize; psize -= onesize, prop += onesize, i++) {
- u32 val = be32_to_cpu(prop[0]);
- if ((val & 0xff) == ((bar_no * 4) + PCI_BASE_ADDRESS_0)) {
- if (size)
- *size = of_read_number(prop + na, ns);
- if (flags)
- *flags = bus->get_flags(prop);
- return prop;
- }
- }
- return NULL;
+ /*
+ * "pciex" is PCI Express
+ * "vci" is for the /chaos bridge on 1st-gen PCI powermacs
+ * "ht" is hypertransport
+ *
+ * If none of the device_type match, and that the node name is
+ * "pcie", accept the device as PCI (with a warning).
+ */
+ return of_node_is_type(np, "pci") || of_node_is_type(np, "pciex") ||
+ of_node_is_type(np, "vci") || of_node_is_type(np, "ht") ||
+ of_node_is_pcie(np);
}
-EXPORT_SYMBOL(of_get_pci_address);
-int of_pci_address_to_resource(struct device_node *dev, int bar,
- struct resource *r)
+static void of_bus_pci_count_cells(struct device_node *np,
+ int *addrc, int *sizec)
{
- const __be32 *addrp;
- u64 size;
- unsigned int flags;
-
- addrp = of_get_pci_address(dev, bar, &size, &flags);
- if (addrp == NULL)
- return -EINVAL;
- return __of_address_to_resource(dev, addrp, size, flags, NULL, r);
+ if (addrc)
+ *addrc = 3;
+ if (sizec)
+ *sizec = 2;
}
-EXPORT_SYMBOL_GPL(of_pci_address_to_resource);
-static int parser_init(struct of_pci_range_parser *parser,
- struct device_node *node, const char *name)
+static u64 of_bus_pci_map(__be32 *addr, const __be32 *range, int na, int ns,
+ int pna, int fna)
{
- const int na = 3, ns = 2;
- int rlen;
-
- parser->node = node;
- parser->pna = of_n_addr_cells(node);
- parser->np = parser->pna + na + ns;
-
- parser->range = of_get_property(node, name, &rlen);
- if (parser->range == NULL)
- return -ENOENT;
+ unsigned int af, rf;
- parser->end = parser->range + rlen / sizeof(__be32);
+ af = of_bus_pci_get_flags(addr);
+ rf = of_bus_pci_get_flags(range);
- return 0;
-}
+ /* Check address type match */
+ if ((af ^ rf) & (IORESOURCE_MEM | IORESOURCE_IO))
+ return OF_BAD_ADDR;
-int of_pci_range_parser_init(struct of_pci_range_parser *parser,
- struct device_node *node)
-{
- return parser_init(parser, node, "ranges");
+ return of_bus_default_map(addr, range, na, ns, pna, fna);
}
-EXPORT_SYMBOL_GPL(of_pci_range_parser_init);
-int of_pci_dma_range_parser_init(struct of_pci_range_parser *parser,
- struct device_node *node)
-{
- return parser_init(parser, node, "dma-ranges");
-}
-EXPORT_SYMBOL_GPL(of_pci_dma_range_parser_init);
+#endif /* CONFIG_PCI */
-struct of_pci_range *of_pci_range_parser_one(struct of_pci_range_parser *parser,
- struct of_pci_range *range)
+VISIBLE_IF_KUNIT int __of_address_resource_bounds(struct resource *r, u64 start, u64 size)
{
- const int na = 3, ns = 2;
-
- if (!range)
- return NULL;
-
- if (!parser->range || parser->range + parser->np > parser->end)
- return NULL;
-
- range->pci_space = be32_to_cpup(parser->range);
- range->flags = of_bus_pci_get_flags(parser->range);
- range->pci_addr = of_read_number(parser->range + 1, ns);
- range->cpu_addr = of_translate_address(parser->node,
- parser->range + na);
- range->size = of_read_number(parser->range + parser->pna + na, ns);
+ if (overflows_type(start, r->start))
+ return -EOVERFLOW;
- parser->range += parser->np;
-
- /* Now consume following elements while they are contiguous */
- while (parser->range + parser->np <= parser->end) {
- u32 flags;
- u64 pci_addr, cpu_addr, size;
+ r->start = start;
- flags = of_bus_pci_get_flags(parser->range);
- pci_addr = of_read_number(parser->range + 1, ns);
- cpu_addr = of_translate_address(parser->node,
- parser->range + na);
- size = of_read_number(parser->range + parser->pna + na, ns);
+ if (!size)
+ r->end = wrapping_sub(typeof(r->end), r->start, 1);
+ else if (size && check_add_overflow(r->start, size - 1, &r->end))
+ return -EOVERFLOW;
- if (flags != range->flags)
- break;
- if (pci_addr != range->pci_addr + range->size ||
- cpu_addr != range->cpu_addr + range->size)
- break;
-
- range->size += size;
- parser->range += parser->np;
- }
-
- return range;
+ return 0;
}
-EXPORT_SYMBOL_GPL(of_pci_range_parser_one);
+EXPORT_SYMBOL_IF_KUNIT(__of_address_resource_bounds);
/*
* of_pci_range_to_resource - Create a resource from an of_pci_range
@@ -317,7 +208,7 @@ EXPORT_SYMBOL_GPL(of_pci_range_parser_one);
* @res: pointer to a valid resource that will be updated to
* reflect the values contained in the range.
*
- * Returns EINVAL if the range cannot be converted to resource.
+ * Returns -EINVAL if the range cannot be converted to resource.
*
* Note that if the range is an IO range, the resource will be converted
* using pci_address_to_pio() which can fail if it is called too early or
@@ -325,9 +216,10 @@ EXPORT_SYMBOL_GPL(of_pci_range_parser_one);
* To guard against that we try to register the IO range first.
* If that fails we know that pci_address_to_pio() will do too.
*/
-int of_pci_range_to_resource(struct of_pci_range *range,
- struct device_node *np, struct resource *res)
+int of_pci_range_to_resource(const struct of_pci_range *range,
+ const struct device_node *np, struct resource *res)
{
+ u64 start;
int err;
res->flags = range->flags;
res->parent = res->child = res->sibling = NULL;
@@ -344,18 +236,11 @@ int of_pci_range_to_resource(struct of_pci_range *range,
err = -EINVAL;
goto invalid_range;
}
- res->start = port;
+ start = port;
} else {
- if ((sizeof(resource_size_t) < 8) &&
- upper_32_bits(range->cpu_addr)) {
- err = -EINVAL;
- goto invalid_range;
- }
-
- res->start = range->cpu_addr;
+ start = range->cpu_addr;
}
- res->end = res->start + range->size - 1;
- return 0;
+ return __of_address_resource_bounds(res, start, range->size);
invalid_range:
res->start = (resource_size_t)OF_BAD_ADDR;
@@ -363,7 +248,34 @@ invalid_range:
return err;
}
EXPORT_SYMBOL(of_pci_range_to_resource);
-#endif /* CONFIG_PCI */
+
+/*
+ * of_range_to_resource - Create a resource from a ranges entry
+ * @np: device node where the range belongs to
+ * @index: the 'ranges' index to convert to a resource
+ * @res: pointer to a valid resource that will be updated to
+ * reflect the values contained in the range.
+ *
+ * Returns -ENOENT if the entry is not found or -EOVERFLOW if the range
+ * cannot be converted to resource.
+ */
+int of_range_to_resource(struct device_node *np, int index, struct resource *res)
+{
+ int ret, i = 0;
+ struct of_range_parser parser;
+ struct of_range range;
+
+ ret = of_range_parser_init(&parser, np);
+ if (ret)
+ return ret;
+
+ for_each_of_range(&parser, &range)
+ if (i++ == index)
+ return of_pci_range_to_resource(&range, np, res);
+
+ return -ENOENT;
+}
+EXPORT_SYMBOL(of_range_to_resource);
/*
* ISA bus specific translator
@@ -384,31 +296,13 @@ static void of_bus_isa_count_cells(struct device_node *child,
}
static u64 of_bus_isa_map(__be32 *addr, const __be32 *range, int na, int ns,
- int pna)
+ int pna, int fna)
{
- u64 cp, s, da;
-
/* Check address type match */
if ((addr[0] ^ range[0]) & cpu_to_be32(1))
return OF_BAD_ADDR;
- /* Read address values, skipping high cell */
- cp = of_read_number(range + 1, na - 1);
- s = of_read_number(range + na + pna, ns);
- da = of_read_number(addr + 1, na - 1);
-
- pr_debug("ISA map, cp=%llx, s=%llx, da=%llx\n",
- (unsigned long long)cp, (unsigned long long)s,
- (unsigned long long)da);
-
- if (da < cp || da >= (cp + s))
- return OF_BAD_ADDR;
- return da - cp;
-}
-
-static int of_bus_isa_translate(__be32 *addr, u64 offset, int na)
-{
- return of_bus_default_translate(addr + 1, offset, na - 1);
+ return of_bus_default_map(addr, range, na, ns, pna, fna);
}
static unsigned int of_bus_isa_get_flags(const __be32 *addr)
@@ -423,11 +317,25 @@ static unsigned int of_bus_isa_get_flags(const __be32 *addr)
return flags;
}
+static int of_bus_default_flags_match(struct device_node *np)
+{
+ /*
+ * Check for presence first since of_bus_n_addr_cells() will warn when
+ * walking parent nodes.
+ */
+ return of_property_present(np, "#address-cells") && (of_bus_n_addr_cells(np) == 3);
+}
+
+static int of_bus_default_match(struct device_node *np)
+{
+ return of_property_present(np, "#address-cells");
+}
+
/*
* Array of bus specific translators
*/
-static struct of_bus of_busses[] = {
+static const struct of_bus of_busses[] = {
#ifdef CONFIG_PCI
/* PCI */
{
@@ -436,7 +344,8 @@ static struct of_bus of_busses[] = {
.match = of_bus_pci_match,
.count_cells = of_bus_pci_count_cells,
.map = of_bus_pci_map,
- .translate = of_bus_pci_translate,
+ .translate = of_bus_default_flags_translate,
+ .flag_cells = 1,
.get_flags = of_bus_pci_get_flags,
},
#endif /* CONFIG_PCI */
@@ -447,14 +356,26 @@ static struct of_bus of_busses[] = {
.match = of_bus_isa_match,
.count_cells = of_bus_isa_count_cells,
.map = of_bus_isa_map,
- .translate = of_bus_isa_translate,
+ .translate = of_bus_default_flags_translate,
+ .flag_cells = 1,
.get_flags = of_bus_isa_get_flags,
},
+ /* Default with flags cell */
+ {
+ .name = "default-flags",
+ .addresses = "reg",
+ .match = of_bus_default_flags_match,
+ .count_cells = of_bus_default_count_cells,
+ .map = of_bus_default_flags_map,
+ .translate = of_bus_default_flags_translate,
+ .flag_cells = 1,
+ .get_flags = of_bus_default_flags_get_flags,
+ },
/* Default */
{
.name = "default",
.addresses = "reg",
- .match = NULL,
+ .match = of_bus_default_match,
.count_cells = of_bus_default_count_cells,
.map = of_bus_default_map,
.translate = of_bus_default_translate,
@@ -462,18 +383,17 @@ static struct of_bus of_busses[] = {
},
};
-static struct of_bus *of_match_bus(struct device_node *np)
+static const struct of_bus *of_match_bus(struct device_node *np)
{
int i;
for (i = 0; i < ARRAY_SIZE(of_busses); i++)
if (!of_busses[i].match || of_busses[i].match(np))
return &of_busses[i];
- BUG();
return NULL;
}
-static int of_empty_ranges_quirk(struct device_node *np)
+static int of_empty_ranges_quirk(const struct device_node *np)
{
if (IS_ENABLED(CONFIG_PPC)) {
/* To save cycles, we cache the result for global "Mac" setting */
@@ -493,8 +413,8 @@ static int of_empty_ranges_quirk(struct device_node *np)
return false;
}
-static int of_translate_one(struct device_node *parent, struct of_bus *bus,
- struct of_bus *pbus, __be32 *addr,
+static int of_translate_one(const struct device_node *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;
@@ -517,15 +437,20 @@ static int of_translate_one(struct device_node *parent, struct of_bus *bus,
*
* As far as we know, this damage only exists on Apple machines, so
* This code is only enabled on powerpc. --gcl
+ *
+ * This quirk also applies for 'dma-ranges' which frequently exist in
+ * child nodes without 'dma-ranges' in the parent nodes. --RobH
*/
ranges = of_get_property(parent, rprop, &rlen);
- if (ranges == NULL && !of_empty_ranges_quirk(parent)) {
+ if (ranges == NULL && !of_empty_ranges_quirk(parent) &&
+ strcmp(rprop, "dma-ranges")) {
pr_debug("no ranges; cannot translate\n");
return 1;
}
if (ranges == NULL || rlen == 0) {
offset = of_read_number(addr, na);
- memset(addr, 0, pna * 4);
+ /* set address to zero, pass flags through */
+ memset(addr + pbus->flag_cells, 0, (pna - pbus->flag_cells) * 4);
pr_debug("empty ranges; 1:1 translation\n");
goto finish;
}
@@ -536,7 +461,7 @@ static int of_translate_one(struct device_node *parent, struct of_bus *bus,
rlen /= 4;
rone = na + pna + ns;
for (; rlen >= rone; rlen -= rone, ranges += rone) {
- offset = bus->map(addr, ranges, na, ns, pna);
+ offset = bus->map(addr, ranges, na, ns, pna, bus->flag_cells);
if (offset != OF_BAD_ADDR)
break;
}
@@ -548,7 +473,7 @@ static int of_translate_one(struct device_node *parent, struct of_bus *bus,
finish:
of_dump_addr("parent translation for:", addr, pna);
- pr_debug("with offset: %llx\n", (unsigned long long)offset);
+ pr_debug("with offset: %llx\n", offset);
/* Translate it into parent bus space */
return pbus->translate(addr, offset, pna);
@@ -568,33 +493,32 @@ static int of_translate_one(struct device_node *parent, struct of_bus *bus,
* device that had registered logical PIO mapping, and the return code is
* relative to that node.
*/
-static u64 __of_translate_address(struct device_node *dev,
+static u64 __of_translate_address(struct device_node *node,
+ struct device_node *(*get_parent)(const struct device_node *),
const __be32 *in_addr, const char *rprop,
struct device_node **host)
{
- struct device_node *parent = NULL;
- struct of_bus *bus, *pbus;
+ struct device_node *dev __free(device_node) = of_node_get(node);
+ struct device_node *parent __free(device_node) = get_parent(dev);
+ const struct of_bus *bus, *pbus;
__be32 addr[OF_MAX_ADDR_CELLS];
int na, ns, pna, pns;
- u64 result = OF_BAD_ADDR;
pr_debug("** translation for device %pOF **\n", dev);
- /* Increase refcount at current level */
- of_node_get(dev);
-
*host = NULL;
- /* Get parent & match bus type */
- parent = of_get_parent(dev);
+
if (parent == NULL)
- goto bail;
+ return OF_BAD_ADDR;
bus = of_match_bus(parent);
+ if (!bus)
+ return OF_BAD_ADDR;
/* Count address cells & copy address locally */
bus->count_cells(dev, &na, &ns);
if (!OF_CHECK_COUNTS(na, ns)) {
pr_debug("Bad cell count for %pOF\n", dev);
- goto bail;
+ return OF_BAD_ADDR;
}
memcpy(addr, in_addr, na * 4);
@@ -609,13 +533,12 @@ static u64 __of_translate_address(struct device_node *dev,
/* Switch to parent bus */
of_node_put(dev);
dev = parent;
- parent = of_get_parent(dev);
+ parent = get_parent(dev);
/* If root, we have finished */
if (parent == NULL) {
pr_debug("reached root node\n");
- result = of_read_number(addr, na);
- break;
+ return of_read_number(addr, na);
}
/*
@@ -624,19 +547,21 @@ static u64 __of_translate_address(struct device_node *dev,
*/
iorange = find_io_range_by_fwnode(&dev->fwnode);
if (iorange && (iorange->flags != LOGIC_PIO_CPU_MMIO)) {
- result = of_read_number(addr + 1, na - 1);
+ u64 result = of_read_number(addr + 1, na - 1);
pr_debug("indirectIO matched(%pOF) 0x%llx\n",
dev, result);
- *host = of_node_get(dev);
- break;
+ *host = no_free_ptr(dev);
+ return result;
}
/* Get new parent bus and counts */
pbus = of_match_bus(parent);
+ if (!pbus)
+ return OF_BAD_ADDR;
pbus->count_cells(dev, &pna, &pns);
if (!OF_CHECK_COUNTS(pna, pns)) {
pr_err("Bad cell count for %pOF\n", dev);
- break;
+ return OF_BAD_ADDR;
}
pr_debug("parent bus is %s (na=%d, ns=%d) on %pOF\n",
@@ -644,7 +569,7 @@ static u64 __of_translate_address(struct device_node *dev,
/* Apply bus translation */
if (of_translate_one(dev, bus, pbus, addr, na, ns, pna, rprop))
- break;
+ return OF_BAD_ADDR;
/* Complete the move up one level */
na = pna;
@@ -653,11 +578,8 @@ static u64 __of_translate_address(struct device_node *dev,
of_dump_addr("one level translation:", addr, na);
}
- bail:
- of_node_put(parent);
- of_node_put(dev);
- return result;
+ unreachable();
}
u64 of_translate_address(struct device_node *dev, const __be32 *in_addr)
@@ -665,7 +587,8 @@ u64 of_translate_address(struct device_node *dev, const __be32 *in_addr)
struct device_node *host;
u64 ret;
- ret = __of_translate_address(dev, in_addr, "ranges", &host);
+ ret = __of_translate_address(dev, of_get_parent,
+ in_addr, "ranges", &host);
if (host) {
of_node_put(host);
return OF_BAD_ADDR;
@@ -675,12 +598,43 @@ u64 of_translate_address(struct device_node *dev, const __be32 *in_addr)
}
EXPORT_SYMBOL(of_translate_address);
+#ifdef CONFIG_HAS_DMA
+struct device_node *__of_get_dma_parent(const struct device_node *np)
+{
+ struct of_phandle_args args;
+ int ret, index;
+
+ index = of_property_match_string(np, "interconnect-names", "dma-mem");
+ if (index < 0)
+ return of_get_parent(np);
+
+ ret = of_parse_phandle_with_args(np, "interconnects",
+ "#interconnect-cells",
+ index, &args);
+ if (ret < 0)
+ return of_get_parent(np);
+
+ return args.np;
+}
+#endif
+
+static struct device_node *of_get_next_dma_parent(struct device_node *np)
+{
+ struct device_node *parent;
+
+ parent = __of_get_dma_parent(np);
+ of_node_put(np);
+
+ return parent;
+}
+
u64 of_translate_dma_address(struct device_node *dev, const __be32 *in_addr)
{
struct device_node *host;
u64 ret;
- ret = __of_translate_address(dev, in_addr, "dma-ranges", &host);
+ ret = __of_translate_address(dev, __of_get_dma_parent,
+ in_addr, "dma-ranges", &host);
if (host) {
of_node_put(host);
@@ -691,23 +645,59 @@ u64 of_translate_dma_address(struct device_node *dev, const __be32 *in_addr)
}
EXPORT_SYMBOL(of_translate_dma_address);
-const __be32 *of_get_address(struct device_node *dev, int index, u64 *size,
- unsigned int *flags)
+/**
+ * of_translate_dma_region - Translate device tree address and size tuple
+ * @dev: device tree node for which to translate
+ * @prop: pointer into array of cells
+ * @start: return value for the start of the DMA range
+ * @length: return value for the length of the DMA range
+ *
+ * Returns a pointer to the cell immediately following the translated DMA region.
+ */
+const __be32 *of_translate_dma_region(struct device_node *dev, const __be32 *prop,
+ phys_addr_t *start, size_t *length)
+{
+ struct device_node *parent __free(device_node) = __of_get_dma_parent(dev);
+ u64 address, size;
+ int na, ns;
+
+ if (!parent)
+ return NULL;
+
+ na = of_bus_n_addr_cells(parent);
+ ns = of_bus_n_size_cells(parent);
+
+ address = of_translate_dma_address(dev, prop);
+ if (address == OF_BAD_ADDR)
+ return NULL;
+
+ size = of_read_number(prop + na, ns);
+
+ if (start)
+ *start = address;
+
+ if (length)
+ *length = size;
+
+ return prop + na + ns;
+}
+EXPORT_SYMBOL(of_translate_dma_region);
+
+const __be32 *__of_get_address(struct device_node *dev, int index, int bar_no,
+ u64 *size, unsigned int *flags)
{
const __be32 *prop;
unsigned int psize;
- struct device_node *parent;
- struct of_bus *bus;
+ struct device_node *parent __free(device_node) = of_get_parent(dev);
+ const struct of_bus *bus;
int onesize, i, na, ns;
- /* Get parent & match bus type */
- parent = of_get_parent(dev);
if (parent == NULL)
return NULL;
+
+ /* match the parent's bus type */
bus = of_match_bus(parent);
- bus->count_cells(dev, &na, &ns);
- of_node_put(parent);
- if (!OF_CHECK_ADDR_COUNT(na))
+ if (!bus || (strcmp(bus->name, "pci") && (bar_no >= 0)))
return NULL;
/* Get "reg" or "assigned-addresses" property */
@@ -716,18 +706,144 @@ const __be32 *of_get_address(struct device_node *dev, int index, u64 *size,
return NULL;
psize /= 4;
+ bus->count_cells(dev, &na, &ns);
+ if (!OF_CHECK_ADDR_COUNT(na))
+ return NULL;
+
onesize = na + ns;
- for (i = 0; psize >= onesize; psize -= onesize, prop += onesize, i++)
- if (i == index) {
+ for (i = 0; psize >= onesize; psize -= onesize, prop += onesize, i++) {
+ u32 val = be32_to_cpu(prop[0]);
+ /* PCI bus matches on BAR number instead of index */
+ if (((bar_no >= 0) && ((val & 0xff) == ((bar_no * 4) + PCI_BASE_ADDRESS_0))) ||
+ ((index >= 0) && (i == index))) {
if (size)
*size = of_read_number(prop + na, ns);
if (flags)
*flags = bus->get_flags(prop);
return prop;
}
+ }
return NULL;
}
-EXPORT_SYMBOL(of_get_address);
+EXPORT_SYMBOL(__of_get_address);
+
+/**
+ * of_property_read_reg - Retrieve the specified "reg" entry index without translating
+ * @np: device tree node for which to retrieve "reg" from
+ * @idx: "reg" entry index to read
+ * @addr: return value for the untranslated address
+ * @size: return value for the entry size
+ *
+ * Returns -EINVAL if "reg" is not found. Returns 0 on success with addr and
+ * size values filled in.
+ */
+int of_property_read_reg(struct device_node *np, int idx, u64 *addr, u64 *size)
+{
+ const __be32 *prop = of_get_address(np, idx, size, NULL);
+
+ if (!prop)
+ return -EINVAL;
+
+ *addr = of_read_number(prop, of_n_addr_cells(np));
+
+ return 0;
+}
+EXPORT_SYMBOL(of_property_read_reg);
+
+static int parser_init(struct of_pci_range_parser *parser,
+ struct device_node *node, const char *name)
+{
+ int rlen;
+
+ parser->node = node;
+ parser->pna = of_n_addr_cells(node);
+ parser->na = of_bus_n_addr_cells(node);
+ parser->ns = of_bus_n_size_cells(node);
+ parser->dma = !strcmp(name, "dma-ranges");
+ parser->bus = of_match_bus(node);
+
+ parser->range = of_get_property(node, name, &rlen);
+ if (parser->range == NULL)
+ return -ENOENT;
+
+ parser->end = parser->range + rlen / sizeof(__be32);
+
+ return 0;
+}
+
+int of_pci_range_parser_init(struct of_pci_range_parser *parser,
+ struct device_node *node)
+{
+ return parser_init(parser, node, "ranges");
+}
+EXPORT_SYMBOL_GPL(of_pci_range_parser_init);
+
+int of_pci_dma_range_parser_init(struct of_pci_range_parser *parser,
+ struct device_node *node)
+{
+ return parser_init(parser, node, "dma-ranges");
+}
+EXPORT_SYMBOL_GPL(of_pci_dma_range_parser_init);
+#define of_dma_range_parser_init of_pci_dma_range_parser_init
+
+struct of_pci_range *of_pci_range_parser_one(struct of_pci_range_parser *parser,
+ struct of_pci_range *range)
+{
+ int na = parser->na;
+ int ns = parser->ns;
+ int np = parser->pna + na + ns;
+ int busflag_na = parser->bus->flag_cells;
+
+ if (!range)
+ return NULL;
+
+ if (!parser->range || parser->range + np > parser->end)
+ return NULL;
+
+ range->flags = parser->bus->get_flags(parser->range);
+
+ range->bus_addr = of_read_number(parser->range + busflag_na, na - busflag_na);
+
+ if (parser->dma)
+ range->cpu_addr = of_translate_dma_address(parser->node,
+ parser->range + na);
+ else
+ range->cpu_addr = of_translate_address(parser->node,
+ parser->range + na);
+
+ range->parent_bus_addr = of_read_number(parser->range + na, parser->pna);
+ range->size = of_read_number(parser->range + parser->pna + na, ns);
+
+ parser->range += np;
+
+ /* Now consume following elements while they are contiguous */
+ while (parser->range + np <= parser->end) {
+ u32 flags = 0;
+ u64 bus_addr, cpu_addr, size;
+
+ flags = parser->bus->get_flags(parser->range);
+ bus_addr = of_read_number(parser->range + busflag_na, na - busflag_na);
+ if (parser->dma)
+ cpu_addr = of_translate_dma_address(parser->node,
+ parser->range + na);
+ else
+ cpu_addr = of_translate_address(parser->node,
+ parser->range + na);
+ size = of_read_number(parser->range + parser->pna + na, ns);
+
+ if (flags != range->flags)
+ break;
+ if (bus_addr != range->bus_addr + range->size ||
+ cpu_addr != range->cpu_addr + range->size)
+ break;
+
+ range->size += size;
+ parser->range += np;
+ }
+
+ return range;
+}
+EXPORT_SYMBOL_GPL(of_pci_range_parser_one);
static u64 of_translate_ioport(struct device_node *dev, const __be32 *in_addr,
u64 size)
@@ -736,7 +852,8 @@ static u64 of_translate_ioport(struct device_node *dev, const __be32 *in_addr,
unsigned long port;
struct device_node *host;
- taddr = __of_translate_address(dev, in_addr, "ranges", &host);
+ taddr = __of_translate_address(dev, of_get_parent,
+ in_addr, "ranges", &host);
if (host) {
/* host-specific port access */
port = logic_pio_trans_hwaddr(&host->fwnode, taddr, size);
@@ -752,11 +869,191 @@ static u64 of_translate_ioport(struct device_node *dev, const __be32 *in_addr,
return port;
}
-static int __of_address_to_resource(struct device_node *dev,
- const __be32 *addrp, u64 size, unsigned int flags,
- const char *name, struct resource *r)
+#ifdef CONFIG_HAS_DMA
+/**
+ * of_dma_get_range - Get DMA range info and put it into a map array
+ * @np: device node to get DMA range info
+ * @map: dma range structure to return
+ *
+ * Look in bottom up direction for the first "dma-ranges" property
+ * and parse it. Put the information into a DMA offset map array.
+ *
+ * dma-ranges format:
+ * DMA addr (dma_addr) : naddr cells
+ * CPU addr (phys_addr_t) : pna cells
+ * size : nsize cells
+ *
+ * It returns -ENODEV if "dma-ranges" property was not found for this
+ * device in the DT.
+ */
+int of_dma_get_range(struct device_node *np, const struct bus_dma_region **map)
+{
+ struct device_node *node __free(device_node) = of_node_get(np);
+ const __be32 *ranges = NULL;
+ bool found_dma_ranges = false;
+ struct of_range_parser parser;
+ struct of_range range;
+ struct bus_dma_region *r;
+ int len, num_ranges = 0;
+
+ while (node) {
+ ranges = of_get_property(node, "dma-ranges", &len);
+
+ /* Ignore empty ranges, they imply no translation required */
+ if (ranges && len > 0)
+ break;
+
+ /* Once we find 'dma-ranges', then a missing one is an error */
+ if (found_dma_ranges && !ranges)
+ return -ENODEV;
+
+ found_dma_ranges = true;
+
+ node = of_get_next_dma_parent(node);
+ }
+
+ if (!node || !ranges) {
+ pr_debug("no dma-ranges found for node(%pOF)\n", np);
+ return -ENODEV;
+ }
+ of_dma_range_parser_init(&parser, node);
+ for_each_of_range(&parser, &range) {
+ if (range.cpu_addr == OF_BAD_ADDR) {
+ pr_err("translation of DMA address(%llx) to CPU address failed node(%pOF)\n",
+ range.bus_addr, node);
+ continue;
+ }
+ num_ranges++;
+ }
+
+ if (!num_ranges)
+ return -EINVAL;
+
+ r = kcalloc(num_ranges + 1, sizeof(*r), GFP_KERNEL);
+ if (!r)
+ return -ENOMEM;
+
+ /*
+ * Record all info in the generic DMA ranges array for struct device,
+ * returning an error if we don't find any parsable ranges.
+ */
+ *map = r;
+ of_dma_range_parser_init(&parser, node);
+ for_each_of_range(&parser, &range) {
+ pr_debug("dma_addr(%llx) cpu_addr(%llx) size(%llx)\n",
+ range.bus_addr, range.cpu_addr, range.size);
+ if (range.cpu_addr == OF_BAD_ADDR)
+ continue;
+ r->cpu_start = range.cpu_addr;
+ r->dma_start = range.bus_addr;
+ r->size = range.size;
+ r++;
+ }
+ return 0;
+}
+#endif /* CONFIG_HAS_DMA */
+
+/**
+ * of_dma_get_max_cpu_address - Gets highest CPU address suitable for DMA
+ * @np: The node to start searching from or NULL to start from the root
+ *
+ * Gets the highest CPU physical address that is addressable by all DMA masters
+ * in the sub-tree pointed by np, or the whole tree if NULL is passed. If no
+ * DMA constrained device is found, it returns PHYS_ADDR_MAX.
+ */
+phys_addr_t __init of_dma_get_max_cpu_address(struct device_node *np)
+{
+ phys_addr_t max_cpu_addr = PHYS_ADDR_MAX;
+ struct of_range_parser parser;
+ phys_addr_t subtree_max_addr;
+ struct device_node *child;
+ struct of_range range;
+ const __be32 *ranges;
+ u64 cpu_end = 0;
+ int len;
+
+ if (!np)
+ np = of_root;
+
+ ranges = of_get_property(np, "dma-ranges", &len);
+ if (ranges && len) {
+ of_dma_range_parser_init(&parser, np);
+ for_each_of_range(&parser, &range)
+ if (range.cpu_addr + range.size > cpu_end)
+ cpu_end = range.cpu_addr + range.size - 1;
+
+ if (max_cpu_addr > cpu_end)
+ max_cpu_addr = cpu_end;
+ }
+
+ for_each_available_child_of_node(np, child) {
+ subtree_max_addr = of_dma_get_max_cpu_address(child);
+ if (max_cpu_addr > subtree_max_addr)
+ max_cpu_addr = subtree_max_addr;
+ }
+
+ return max_cpu_addr;
+}
+
+/**
+ * of_dma_is_coherent - Check if device is coherent
+ * @np: device node
+ *
+ * It returns true if "dma-coherent" property was found
+ * for this device in the DT, or if DMA is coherent by
+ * default for OF devices on the current platform and no
+ * "dma-noncoherent" property was found for this device.
+ */
+bool of_dma_is_coherent(struct device_node *np)
+{
+ struct device_node *node __free(device_node) = of_node_get(np);
+
+ while (node) {
+ if (of_property_read_bool(node, "dma-coherent"))
+ return true;
+
+ if (of_property_read_bool(node, "dma-noncoherent"))
+ return false;
+
+ node = of_get_next_dma_parent(node);
+ }
+ return dma_default_coherent;
+}
+EXPORT_SYMBOL_GPL(of_dma_is_coherent);
+
+/**
+ * of_mmio_is_nonposted - Check if device uses non-posted MMIO
+ * @np: device node
+ *
+ * Returns true if the "nonposted-mmio" property was found for
+ * the device's bus.
+ */
+static bool of_mmio_is_nonposted(const struct device_node *np)
+{
+ struct device_node *parent __free(device_node) = of_get_parent(np);
+
+ if (of_property_read_bool(np, "nonposted-mmio"))
+ return true;
+
+ return parent && of_property_read_bool(parent, "nonposted-mmio");
+}
+
+static int __of_address_to_resource(struct device_node *dev, int index, int bar_no,
+ struct resource *r)
{
u64 taddr;
+ const __be32 *addrp;
+ u64 size;
+ unsigned int flags;
+ const char *name = NULL;
+
+ addrp = __of_get_address(dev, index, bar_no, &size, &flags);
+ if (addrp == NULL)
+ return -EINVAL;
+
+ /* Get optional "reg-names" property to add a name to a resource */
+ if (index >= 0)
+ of_property_read_string_index(dev, "reg-names", index, &name);
if (flags & IORESOURCE_MEM)
taddr = of_translate_address(dev, addrp);
@@ -769,16 +1066,22 @@ static int __of_address_to_resource(struct device_node *dev,
return -EINVAL;
memset(r, 0, sizeof(struct resource));
- r->start = taddr;
- r->end = taddr + size - 1;
+ if (of_mmio_is_nonposted(dev))
+ flags |= IORESOURCE_MEM_NONPOSTED;
+
r->flags = flags;
r->name = name ? name : dev->full_name;
- return 0;
+ return __of_address_resource_bounds(r, taddr, size);
}
/**
* of_address_to_resource - Translate device tree address and return as resource
+ * @dev: Caller's Device Node
+ * @index: Index into the array
+ * @r: Pointer to resource array
+ *
+ * Returns -EINVAL if the range cannot be converted to resource.
*
* Note that if your address is a PIO address, the conversion will fail if
* the physical address can't be internally converted to an IO token with
@@ -788,44 +1091,24 @@ static int __of_address_to_resource(struct device_node *dev,
int of_address_to_resource(struct device_node *dev, int index,
struct resource *r)
{
- const __be32 *addrp;
- u64 size;
- unsigned int flags;
- const char *name = NULL;
-
- addrp = of_get_address(dev, index, &size, &flags);
- if (addrp == NULL)
- return -EINVAL;
-
- /* Get optional "reg-names" property to add a name to a resource */
- of_property_read_string_index(dev, "reg-names", index, &name);
-
- return __of_address_to_resource(dev, addrp, size, flags, name, r);
+ return __of_address_to_resource(dev, index, -1, r);
}
EXPORT_SYMBOL_GPL(of_address_to_resource);
-struct device_node *of_find_matching_node_by_address(struct device_node *from,
- const struct of_device_id *matches,
- u64 base_address)
+int of_pci_address_to_resource(struct device_node *dev, int bar,
+ struct resource *r)
{
- struct device_node *dn = of_find_matching_node(from, matches);
- struct resource res;
- while (dn) {
- if (!of_address_to_resource(dn, 0, &res) &&
- res.start == base_address)
- return dn;
-
- dn = of_find_matching_node(dn, matches);
- }
+ if (!IS_ENABLED(CONFIG_PCI))
+ return -ENOSYS;
- return NULL;
+ return __of_address_to_resource(dev, -1, bar, r);
}
-
+EXPORT_SYMBOL_GPL(of_pci_address_to_resource);
/**
* of_iomap - Maps the memory mapped IO for a given device_node
- * @device: the device whose io range will be mapped
+ * @np: the device whose io range will be mapped
* @index: index of the io range
*
* Returns a pointer to the mapped memory
@@ -837,7 +1120,10 @@ void __iomem *of_iomap(struct device_node *np, int index)
if (of_address_to_resource(np, index, &res))
return NULL;
- return ioremap(res.start, resource_size(&res));
+ if (res.flags & IORESOURCE_MEM_NONPOSTED)
+ return ioremap_np(res.start, resource_size(&res));
+ else
+ return ioremap(res.start, resource_size(&res));
}
EXPORT_SYMBOL(of_iomap);
@@ -869,7 +1155,11 @@ void __iomem *of_io_request_and_map(struct device_node *np, int index,
if (!request_mem_region(res.start, resource_size(&res), name))
return IOMEM_ERR_PTR(-EBUSY);
- mem = ioremap(res.start, resource_size(&res));
+ if (res.flags & IORESOURCE_MEM_NONPOSTED)
+ mem = ioremap_np(res.start, resource_size(&res));
+ else
+ mem = ioremap(res.start, resource_size(&res));
+
if (!mem) {
release_mem_region(res.start, resource_size(&res));
return IOMEM_ERR_PTR(-ENOMEM);
@@ -878,112 +1168,3 @@ void __iomem *of_io_request_and_map(struct device_node *np, int index,
return mem;
}
EXPORT_SYMBOL(of_io_request_and_map);
-
-/**
- * of_dma_get_range - Get DMA range info
- * @np: device node to get DMA range info
- * @dma_addr: pointer to store initial DMA address of DMA range
- * @paddr: pointer to store initial CPU address of DMA range
- * @size: pointer to store size of DMA range
- *
- * Look in bottom up direction for the first "dma-ranges" property
- * and parse it.
- * dma-ranges format:
- * DMA addr (dma_addr) : naddr cells
- * CPU addr (phys_addr_t) : pna cells
- * size : nsize cells
- *
- * It returns -ENODEV if "dma-ranges" property was not found
- * for this device in DT.
- */
-int of_dma_get_range(struct device_node *np, u64 *dma_addr, u64 *paddr, u64 *size)
-{
- struct device_node *node = of_node_get(np);
- const __be32 *ranges = NULL;
- int len, naddr, nsize, pna;
- int ret = 0;
- u64 dmaaddr;
-
- if (!node)
- return -EINVAL;
-
- while (1) {
- naddr = of_n_addr_cells(node);
- nsize = of_n_size_cells(node);
- node = of_get_next_parent(node);
- if (!node)
- break;
-
- ranges = of_get_property(node, "dma-ranges", &len);
-
- /* Ignore empty ranges, they imply no translation required */
- if (ranges && len > 0)
- break;
-
- /*
- * At least empty ranges has to be defined for parent node if
- * DMA is supported
- */
- if (!ranges)
- break;
- }
-
- if (!ranges) {
- pr_debug("no dma-ranges found for node(%pOF)\n", np);
- ret = -ENODEV;
- goto out;
- }
-
- len /= sizeof(u32);
-
- pna = of_n_addr_cells(node);
-
- /* dma-ranges format:
- * DMA addr : naddr cells
- * CPU addr : pna cells
- * size : nsize cells
- */
- dmaaddr = of_read_number(ranges, naddr);
- *paddr = of_translate_dma_address(np, ranges);
- if (*paddr == OF_BAD_ADDR) {
- pr_err("translation of DMA address(%pad) to CPU address failed node(%pOF)\n",
- dma_addr, np);
- ret = -EINVAL;
- goto out;
- }
- *dma_addr = dmaaddr;
-
- *size = of_read_number(ranges + naddr + pna, nsize);
-
- pr_debug("dma_addr(%llx) cpu_addr(%llx) size(%llx)\n",
- *dma_addr, *paddr, *size);
-
-out:
- of_node_put(node);
-
- return ret;
-}
-EXPORT_SYMBOL_GPL(of_dma_get_range);
-
-/**
- * of_dma_is_coherent - Check if device is coherent
- * @np: device node
- *
- * It returns true if "dma-coherent" property was found
- * for this device in DT.
- */
-bool of_dma_is_coherent(struct device_node *np)
-{
- struct device_node *node = of_node_get(np);
-
- while (node) {
- if (of_property_read_bool(node, "dma-coherent")) {
- of_node_put(node);
- return true;
- }
- node = of_get_next_parent(node);
- }
- of_node_put(node);
- return false;
-}
-EXPORT_SYMBOL_GPL(of_dma_is_coherent);
diff --git a/drivers/of/base.c b/drivers/of/base.c
index 5226e898476e..0b65039ece53 100644
--- a/drivers/of/base.c
+++ b/drivers/of/base.c
@@ -16,7 +16,7 @@
#define pr_fmt(fmt) "OF: " fmt
-#include <linux/bitmap.h>
+#include <linux/cleanup.h>
#include <linux/console.h>
#include <linux/ctype.h>
#include <linux/cpu.h>
@@ -36,6 +36,7 @@ LIST_HEAD(aliases_lookup);
struct device_node *of_root;
EXPORT_SYMBOL(of_root);
struct device_node *of_chosen;
+EXPORT_SYMBOL(of_chosen);
struct device_node *of_aliases;
struct device_node *of_stdout;
static const char *of_stdout_options;
@@ -86,34 +87,63 @@ static bool __of_node_is_type(const struct device_node *np, const char *type)
return np && match && type && !strcmp(match, type);
}
-int of_n_addr_cells(struct device_node *np)
+#define EXCLUDED_DEFAULT_CELLS_PLATFORMS ( \
+ IS_ENABLED(CONFIG_SPARC) || \
+ of_find_compatible_node(NULL, NULL, "coreboot") \
+)
+
+int of_bus_n_addr_cells(struct device_node *np)
{
u32 cells;
- do {
- if (np->parent)
- np = np->parent;
+ for (; np; np = np->parent) {
if (!of_property_read_u32(np, "#address-cells", &cells))
return cells;
- } while (np->parent);
- /* No #address-cells property for the root node */
+ /*
+ * Default root value and walking parent nodes for "#address-cells"
+ * is deprecated. Any platforms which hit this warning should
+ * be added to the excluded list.
+ */
+ WARN_ONCE(!EXCLUDED_DEFAULT_CELLS_PLATFORMS,
+ "Missing '#address-cells' in %pOF\n", np);
+ }
return OF_ROOT_NODE_ADDR_CELLS_DEFAULT;
}
+
+int of_n_addr_cells(struct device_node *np)
+{
+ if (np->parent)
+ np = np->parent;
+
+ return of_bus_n_addr_cells(np);
+}
EXPORT_SYMBOL(of_n_addr_cells);
-int of_n_size_cells(struct device_node *np)
+int of_bus_n_size_cells(struct device_node *np)
{
u32 cells;
- do {
- if (np->parent)
- np = np->parent;
+ for (; np; np = np->parent) {
if (!of_property_read_u32(np, "#size-cells", &cells))
return cells;
- } while (np->parent);
- /* No #size-cells property for the root node */
+ /*
+ * Default root value and walking parent nodes for "#size-cells"
+ * is deprecated. Any platforms which hit this warning should
+ * be added to the excluded list.
+ */
+ WARN_ONCE(!EXCLUDED_DEFAULT_CELLS_PLATFORMS,
+ "Missing '#size-cells' in %pOF\n", np);
+ }
return OF_ROOT_NODE_SIZE_CELLS_DEFAULT;
}
+
+int of_n_size_cells(struct device_node *np)
+{
+ if (np->parent)
+ np = np->parent;
+
+ return of_bus_n_size_cells(np);
+}
EXPORT_SYMBOL(of_n_size_cells);
#ifdef CONFIG_NUMA
@@ -123,115 +153,39 @@ int __weak of_node_to_nid(struct device_node *np)
}
#endif
-/*
- * Assumptions behind phandle_cache implementation:
- * - phandle property values are in a contiguous range of 1..n
- *
- * If the assumptions do not hold, then
- * - the phandle lookup overhead reduction provided by the cache
- * will likely be less
- */
+#define OF_PHANDLE_CACHE_BITS 7
+#define OF_PHANDLE_CACHE_SZ BIT(OF_PHANDLE_CACHE_BITS)
-static struct device_node **phandle_cache;
-static u32 phandle_cache_mask;
+static struct device_node *phandle_cache[OF_PHANDLE_CACHE_SZ];
-/*
- * Caller must hold devtree_lock.
- */
-static void __of_free_phandle_cache(void)
+static u32 of_phandle_cache_hash(phandle handle)
{
- u32 cache_entries = phandle_cache_mask + 1;
- u32 k;
-
- if (!phandle_cache)
- return;
-
- for (k = 0; k < cache_entries; k++)
- of_node_put(phandle_cache[k]);
-
- kfree(phandle_cache);
- phandle_cache = NULL;
+ return hash_32(handle, OF_PHANDLE_CACHE_BITS);
}
-int of_free_phandle_cache(void)
-{
- unsigned long flags;
-
- raw_spin_lock_irqsave(&devtree_lock, flags);
-
- __of_free_phandle_cache();
-
- raw_spin_unlock_irqrestore(&devtree_lock, flags);
-
- return 0;
-}
-#if !defined(CONFIG_MODULES)
-late_initcall_sync(of_free_phandle_cache);
-#endif
-
/*
* Caller must hold devtree_lock.
*/
-void __of_free_phandle_cache_entry(phandle handle)
+void __of_phandle_cache_inv_entry(phandle handle)
{
- phandle masked_handle;
+ u32 handle_hash;
struct device_node *np;
if (!handle)
return;
- masked_handle = handle & phandle_cache_mask;
-
- if (phandle_cache) {
- np = phandle_cache[masked_handle];
- if (np && handle == np->phandle) {
- of_node_put(np);
- phandle_cache[masked_handle] = NULL;
- }
- }
-}
-
-void of_populate_phandle_cache(void)
-{
- unsigned long flags;
- u32 cache_entries;
- struct device_node *np;
- u32 phandles = 0;
-
- raw_spin_lock_irqsave(&devtree_lock, flags);
-
- __of_free_phandle_cache();
-
- for_each_of_allnodes(np)
- if (np->phandle && np->phandle != OF_PHANDLE_ILLEGAL)
- phandles++;
+ handle_hash = of_phandle_cache_hash(handle);
- if (!phandles)
- goto out;
-
- cache_entries = roundup_pow_of_two(phandles);
- phandle_cache_mask = cache_entries - 1;
-
- phandle_cache = kcalloc(cache_entries, sizeof(*phandle_cache),
- GFP_ATOMIC);
- if (!phandle_cache)
- goto out;
-
- for_each_of_allnodes(np)
- if (np->phandle && np->phandle != OF_PHANDLE_ILLEGAL) {
- of_node_get(np);
- phandle_cache[np->phandle & phandle_cache_mask] = np;
- }
-
-out:
- raw_spin_unlock_irqrestore(&devtree_lock, flags);
+ np = phandle_cache[handle_hash];
+ if (np && handle == np->phandle)
+ phandle_cache[handle_hash] = NULL;
}
void __init of_core_init(void)
{
struct device_node *np;
- of_populate_phandle_cache();
+ of_platform_register_reconfig_notifier();
/* Create the kset, and register existing nodes */
mutex_lock(&of_mutex);
@@ -241,8 +195,11 @@ void __init of_core_init(void)
pr_err("failed to register existing nodes\n");
return;
}
- for_each_of_allnodes(np)
+ for_each_of_allnodes(np) {
__of_attach_node_sysfs(np);
+ if (np->phandle && !phandle_cache[of_phandle_cache_hash(np->phandle)])
+ phandle_cache[of_phandle_cache_hash(np->phandle)] = np;
+ }
mutex_unlock(&of_mutex);
/* Symlink in /proc as required by userspace ABI */
@@ -306,7 +263,7 @@ struct device_node *__of_find_all_nodes(struct device_node *prev)
* @prev: Previous node or NULL to start iteration
* of_node_put() will be called on it
*
- * Returns a node pointer with refcount incremented, use
+ * Return: A node pointer with refcount incremented, use
* of_node_put() on it when done.
*/
struct device_node *of_find_all_nodes(struct device_node *prev)
@@ -330,7 +287,7 @@ EXPORT_SYMBOL(of_find_all_nodes);
const void *__of_get_property(const struct device_node *np,
const char *name, int *lenp)
{
- struct property *pp = __of_find_property(np, name, lenp);
+ const struct property *pp = __of_find_property(np, name, lenp);
return pp ? pp->value : NULL;
}
@@ -342,141 +299,12 @@ const void *__of_get_property(const struct device_node *np,
const void *of_get_property(const struct device_node *np, const char *name,
int *lenp)
{
- struct property *pp = of_find_property(np, name, lenp);
+ const struct property *pp = of_find_property(np, name, lenp);
return pp ? pp->value : NULL;
}
EXPORT_SYMBOL(of_get_property);
-/*
- * 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)
- 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.
- *
- * Returns 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_node_to_id: Get the logical CPU number for a given device_node
- *
- * @cpu_node: Pointer to the device_node for CPU.
- *
- * Returns the logical CPU number of the given CPU device_node.
- * Returns -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_device_is_compatible() - Check if the node matches given constraints
* @device: pointer to node
@@ -510,7 +338,7 @@ EXPORT_SYMBOL(of_cpu_node_to_id);
static int __of_device_is_compatible(const struct device_node *device,
const char *compat, const char *type, const char *name)
{
- struct property *prop;
+ const struct property *prop;
const char *cp;
int index = 0, score = 0;
@@ -565,7 +393,7 @@ EXPORT_SYMBOL(of_device_is_compatible);
* a NULL terminated array of strings. Returns the best match
* score or 0.
*/
-int of_device_compatible_match(struct device_node *device,
+int of_device_compatible_match(const struct device_node *device,
const char *const *compat)
{
unsigned int tmp, score = 0;
@@ -582,37 +410,79 @@ int of_device_compatible_match(struct device_node *device,
return score;
}
+EXPORT_SYMBOL_GPL(of_device_compatible_match);
/**
- * of_machine_is_compatible - Test root of device tree for a given compatible value
- * @compat: compatible string to look for in root node's compatible property.
+ * of_machine_compatible_match - Test root of device tree against a compatible array
+ * @compats: NULL terminated array of compatible strings to look for in root node's compatible property.
*
- * Returns a positive integer if the root node has the given value in its
+ * Returns true if the root node has any of the given compatible values in its
* compatible property.
*/
-int of_machine_is_compatible(const char *compat)
+bool of_machine_compatible_match(const char *const *compats)
{
struct device_node *root;
int rc = 0;
root = of_find_node_by_path("/");
if (root) {
- rc = of_device_is_compatible(root, compat);
+ rc = of_device_compatible_match(root, compats);
of_node_put(root);
}
- return rc;
+
+ return rc != 0;
}
-EXPORT_SYMBOL(of_machine_is_compatible);
+EXPORT_SYMBOL(of_machine_compatible_match);
/**
- * __of_device_is_available - check if a device is available for use
+ * of_machine_device_match - Test root of device tree against a of_device_id array
+ * @matches: NULL terminated array of of_device_id match structures to search in
*
- * @device: Node to check for availability, with locks already held
+ * Returns true if the root node has any of the given compatible values in its
+ * compatible property.
+ */
+bool of_machine_device_match(const struct of_device_id *matches)
+{
+ struct device_node *root;
+ const struct of_device_id *match = NULL;
+
+ root = of_find_node_by_path("/");
+ if (root) {
+ match = of_match_node(matches, root);
+ of_node_put(root);
+ }
+
+ return match != NULL;
+}
+EXPORT_SYMBOL(of_machine_device_match);
+
+/**
+ * of_machine_get_match_data - Tell if root of device tree has a matching of_match structure
+ * @matches: NULL terminated array of of_device_id match structures to search in
*
- * Returns true if the status property is absent or set to "okay" or "ok",
- * false otherwise
+ * Returns data associated with matched entry or NULL
*/
-static bool __of_device_is_available(const struct device_node *device)
+const void *of_machine_get_match_data(const struct of_device_id *matches)
+{
+ const struct of_device_id *match;
+ struct device_node *root;
+
+ root = of_find_node_by_path("/");
+ if (!root)
+ return NULL;
+
+ match = of_match_node(matches, root);
+ of_node_put(root);
+
+ if (!match)
+ return NULL;
+
+ return match->data;
+}
+EXPORT_SYMBOL(of_machine_get_match_data);
+
+static bool __of_device_is_status(const struct device_node *device,
+ const char * const*strings)
{
const char *status;
int statlen;
@@ -622,22 +492,65 @@ static bool __of_device_is_available(const struct device_node *device)
status = __of_get_property(device, "status", &statlen);
if (status == NULL)
- return true;
+ return false;
if (statlen > 0) {
- if (!strcmp(status, "okay") || !strcmp(status, "ok"))
- return true;
+ while (*strings) {
+ unsigned int len = strlen(*strings);
+
+ if ((*strings)[len - 1] == '-') {
+ if (!strncmp(status, *strings, len))
+ return true;
+ } else {
+ if (!strcmp(status, *strings))
+ return true;
+ }
+ strings++;
+ }
}
return false;
}
/**
+ * __of_device_is_available - check if a device is available for use
+ *
+ * @device: Node to check for availability, with locks already held
+ *
+ * Return: True if the status property is absent or set to "okay" or "ok",
+ * false otherwise
+ */
+static bool __of_device_is_available(const struct device_node *device)
+{
+ static const char * const ok[] = {"okay", "ok", NULL};
+
+ if (!device)
+ return false;
+
+ return !__of_get_property(device, "status", NULL) ||
+ __of_device_is_status(device, ok);
+}
+
+/**
+ * __of_device_is_reserved - check if a device is reserved
+ *
+ * @device: Node to check for availability, with locks already held
+ *
+ * Return: True if the status property is set to "reserved", false otherwise
+ */
+static bool __of_device_is_reserved(const struct device_node *device)
+{
+ static const char * const reserved[] = {"reserved", NULL};
+
+ return __of_device_is_status(device, reserved);
+}
+
+/**
* of_device_is_available - check if a device is available for use
*
* @device: Node to check for availability
*
- * Returns true if the status property is absent or set to "okay" or "ok",
+ * Return: True if the status property is absent or set to "okay" or "ok",
* false otherwise
*/
bool of_device_is_available(const struct device_node *device)
@@ -654,11 +567,26 @@ bool of_device_is_available(const struct device_node *device)
EXPORT_SYMBOL(of_device_is_available);
/**
+ * __of_device_is_fail - check if a device has status "fail" or "fail-..."
+ *
+ * @device: Node to check status for, with locks already held
+ *
+ * Return: True if the status property is set to "fail" or "fail-..." (for any
+ * error code suffix), false otherwise
+ */
+static bool __of_device_is_fail(const struct device_node *device)
+{
+ static const char * const fail[] = {"fail", "fail-", NULL};
+
+ return __of_device_is_status(device, fail);
+}
+
+/**
* of_device_is_big_endian - check if a device has BE registers
*
* @device: Node to check for endianness
*
- * Returns true if the device has a "big-endian" property, or if the kernel
+ * Return: True if the device has a "big-endian" property, or if the kernel
* was compiled for BE *and* the device has a "native-endian" property.
* Returns false otherwise.
*
@@ -677,11 +605,11 @@ bool of_device_is_big_endian(const struct device_node *device)
EXPORT_SYMBOL(of_device_is_big_endian);
/**
- * of_get_parent - Get a node's parent if any
- * @node: Node to get parent
+ * of_get_parent - Get a node's parent if any
+ * @node: Node to get parent
*
- * Returns a node pointer with refcount incremented, use
- * of_node_put() on it when done.
+ * Return: A node pointer with refcount incremented, use
+ * of_node_put() on it when done.
*/
struct device_node *of_get_parent(const struct device_node *node)
{
@@ -699,15 +627,15 @@ struct device_node *of_get_parent(const struct device_node *node)
EXPORT_SYMBOL(of_get_parent);
/**
- * of_get_next_parent - Iterate to a node's parent
- * @node: Node to get parent of
+ * of_get_next_parent - Iterate to a node's parent
+ * @node: Node to get parent of
*
- * This is like of_get_parent() except that it drops the
- * refcount on the passed node, making it suitable for iterating
- * through a node's parents.
+ * This is like of_get_parent() except that it drops the
+ * refcount on the passed node, making it suitable for iterating
+ * through a node's parents.
*
- * Returns a node pointer with refcount incremented, use
- * of_node_put() on it when done.
+ * Return: A node pointer with refcount incremented, use
+ * of_node_put() on it when done.
*/
struct device_node *of_get_next_parent(struct device_node *node)
{
@@ -734,9 +662,7 @@ static struct device_node *__of_get_next_child(const struct device_node *node,
return NULL;
next = prev ? prev->sibling : node->child;
- for (; next; next = next->sibling)
- if (of_node_get(next))
- break;
+ of_node_get(next);
of_node_put(prev);
return next;
}
@@ -745,13 +671,13 @@ static struct device_node *__of_get_next_child(const struct device_node *node,
child = __of_get_next_child(parent, child))
/**
- * of_get_next_child - Iterate a node childs
- * @node: parent node
- * @prev: previous child of the parent node, or NULL to get first
+ * of_get_next_child - Iterate a node childs
+ * @node: parent node
+ * @prev: previous child of the parent node, or NULL to get first
*
- * Returns a node pointer with refcount incremented, use of_node_put() on
- * it when done. Returns NULL when prev is the last child. Decrements the
- * refcount of prev.
+ * Return: A node pointer with refcount incremented, use of_node_put() on
+ * it when done. Returns NULL when prev is the last child. Decrements the
+ * refcount of prev.
*/
struct device_node *of_get_next_child(const struct device_node *node,
struct device_node *prev)
@@ -767,15 +693,44 @@ struct device_node *of_get_next_child(const struct device_node *node,
EXPORT_SYMBOL(of_get_next_child);
/**
- * of_get_next_available_child - Find the next available child node
- * @node: parent node
- * @prev: previous child of the parent node, or NULL to get first
+ * of_get_next_child_with_prefix - Find the next child node with prefix
+ * @node: parent node
+ * @prev: previous child of the parent node, or NULL to get first
+ * @prefix: prefix that the node name should have
*
- * This function is like of_get_next_child(), except that it
- * automatically skips any disabled nodes (i.e. status = "disabled").
+ * This function is like of_get_next_child(), except that it automatically
+ * skips any nodes whose name doesn't have the given prefix.
+ *
+ * Return: A node pointer with refcount incremented, use
+ * of_node_put() on it when done.
*/
-struct device_node *of_get_next_available_child(const struct device_node *node,
- struct device_node *prev)
+struct device_node *of_get_next_child_with_prefix(const struct device_node *node,
+ struct device_node *prev,
+ const char *prefix)
+{
+ struct device_node *next;
+ unsigned long flags;
+
+ if (!node)
+ return NULL;
+
+ raw_spin_lock_irqsave(&devtree_lock, flags);
+ next = prev ? prev->sibling : node->child;
+ for (; next; next = next->sibling) {
+ if (!of_node_name_prefix(next, prefix))
+ continue;
+ if (of_node_get(next))
+ break;
+ }
+ of_node_put(prev);
+ raw_spin_unlock_irqrestore(&devtree_lock, flags);
+ return next;
+}
+EXPORT_SYMBOL(of_get_next_child_with_prefix);
+
+static struct device_node *of_get_next_status_child(const struct device_node *node,
+ struct device_node *prev,
+ bool (*checker)(const struct device_node *))
{
struct device_node *next;
unsigned long flags;
@@ -786,7 +741,7 @@ struct device_node *of_get_next_available_child(const struct device_node *node,
raw_spin_lock_irqsave(&devtree_lock, flags);
next = prev ? prev->sibling : node->child;
for (; next; next = next->sibling) {
- if (!__of_device_is_available(next))
+ if (!checker(next))
continue;
if (of_node_get(next))
break;
@@ -795,15 +750,47 @@ struct device_node *of_get_next_available_child(const struct device_node *node,
raw_spin_unlock_irqrestore(&devtree_lock, flags);
return next;
}
+
+/**
+ * of_get_next_available_child - Find the next available child node
+ * @node: parent node
+ * @prev: previous child of the parent node, or NULL to get first
+ *
+ * This function is like of_get_next_child(), except that it
+ * automatically skips any disabled nodes (i.e. status = "disabled").
+ */
+struct device_node *of_get_next_available_child(const struct device_node *node,
+ struct device_node *prev)
+{
+ return of_get_next_status_child(node, prev, __of_device_is_available);
+}
EXPORT_SYMBOL(of_get_next_available_child);
/**
- * of_get_next_cpu_node - Iterate on cpu nodes
- * @prev: previous child of the /cpus node, or NULL to get first
+ * of_get_next_reserved_child - Find the next reserved child node
+ * @node: parent node
+ * @prev: previous child of the parent node, or NULL to get first
*
- * Returns a cpu node pointer with refcount incremented, use of_node_put()
- * on it when done. Returns NULL when prev is the last child. Decrements
- * the refcount of prev.
+ * This function is like of_get_next_child(), except that it
+ * automatically skips any disabled nodes (i.e. status = "disabled").
+ */
+struct device_node *of_get_next_reserved_child(const struct device_node *node,
+ struct device_node *prev)
+{
+ return of_get_next_status_child(node, prev, __of_device_is_reserved);
+}
+EXPORT_SYMBOL(of_get_next_reserved_child);
+
+/**
+ * of_get_next_cpu_node - Iterate on cpu nodes
+ * @prev: previous child of the /cpus node, or NULL to get first
+ *
+ * Unusable CPUs (those with the status property set to "fail" or "fail-...")
+ * will be skipped.
+ *
+ * Return: A cpu node pointer with refcount incremented, use of_node_put()
+ * on it when done. Returns NULL when prev is the last child. Decrements
+ * the refcount of prev.
*/
struct device_node *of_get_next_cpu_node(struct device_node *prev)
{
@@ -822,6 +809,8 @@ struct device_node *of_get_next_cpu_node(struct device_node *prev)
of_node_put(node);
}
for (; next; next = next->sibling) {
+ if (__of_device_is_fail(next))
+ continue;
if (!(of_node_name_eq(next, "cpu") ||
__of_node_is_type(next, "cpu")))
continue;
@@ -842,7 +831,7 @@ EXPORT_SYMBOL(of_get_next_cpu_node);
* Lookup child node whose compatible property contains the given compatible
* string.
*
- * Returns a node pointer with refcount incremented, use of_node_put() on it
+ * Return: a node pointer with refcount incremented, use of_node_put() on it
* when done; or NULL if not found.
*/
struct device_node *of_get_compatible_child(const struct device_node *parent,
@@ -860,15 +849,15 @@ struct device_node *of_get_compatible_child(const struct device_node *parent,
EXPORT_SYMBOL(of_get_compatible_child);
/**
- * of_get_child_by_name - Find the child node by name for a given parent
- * @node: parent node
- * @name: child name to look for.
+ * of_get_child_by_name - Find the child node by name for a given parent
+ * @node: parent node
+ * @name: child name to look for.
*
- * This function looks for child node for given matching name
+ * This function looks for child node for given matching name
*
- * Returns a node pointer if found, with refcount incremented, use
- * of_node_put() on it when done.
- * Returns NULL if node is not found.
+ * Return: A node pointer if found, with refcount incremented, use
+ * of_node_put() on it when done.
+ * Returns NULL if node is not found.
*/
struct device_node *of_get_child_by_name(const struct device_node *node,
const char *name)
@@ -882,7 +871,34 @@ struct device_node *of_get_child_by_name(const struct device_node *node,
}
EXPORT_SYMBOL(of_get_child_by_name);
-struct device_node *__of_find_node_by_path(struct device_node *parent,
+/**
+ * of_get_available_child_by_name - Find the available child node by name for a given parent
+ * @node: parent node
+ * @name: child name to look for.
+ *
+ * This function looks for child node for given matching name and checks the
+ * device's availability for use.
+ *
+ * Return: A node pointer if found, with refcount incremented, use
+ * of_node_put() on it when done.
+ * Returns NULL if node is not found.
+ */
+struct device_node *of_get_available_child_by_name(const struct device_node *node,
+ const char *name)
+{
+ struct device_node *child;
+
+ child = of_get_child_by_name(node, name);
+ if (child && !of_device_is_available(child)) {
+ of_node_put(child);
+ return NULL;
+ }
+
+ return child;
+}
+EXPORT_SYMBOL(of_get_available_child_by_name);
+
+struct device_node *__of_find_node_by_path(const struct device_node *parent,
const char *path)
{
struct device_node *child;
@@ -919,27 +935,27 @@ struct device_node *__of_find_node_by_full_path(struct device_node *node,
}
/**
- * of_find_node_opts_by_path - Find a node matching a full OF path
- * @path: Either the full path to match, or if the path does not
- * start with '/', the name of a property of the /aliases
- * node (an alias). In the case of an alias, the node
- * matching the alias' value will be returned.
- * @opts: Address of a pointer into which to store the start of
- * an options string appended to the end of the path with
- * a ':' separator.
- *
- * Valid paths:
- * /foo/bar Full path
- * foo Valid alias
- * foo/bar Valid alias + relative path
- *
- * Returns a node pointer with refcount incremented, use
- * of_node_put() on it when done.
+ * of_find_node_opts_by_path - Find a node matching a full OF path
+ * @path: Either the full path to match, or if the path does not
+ * start with '/', the name of a property of the /aliases
+ * node (an alias). In the case of an alias, the node
+ * matching the alias' value will be returned.
+ * @opts: Address of a pointer into which to store the start of
+ * an options string appended to the end of the path with
+ * a ':' separator.
+ *
+ * Valid paths:
+ * * /foo/bar Full path
+ * * foo Valid alias
+ * * foo/bar Valid alias + relative path
+ *
+ * Return: A node pointer with refcount incremented, use
+ * of_node_put() on it when done.
*/
struct device_node *of_find_node_opts_by_path(const char *path, const char **opts)
{
struct device_node *np = NULL;
- struct property *pp;
+ const struct property *pp;
unsigned long flags;
const char *separator = strchr(path, ':');
@@ -952,10 +968,10 @@ struct device_node *of_find_node_opts_by_path(const char *path, const char **opt
/* The path could begin with an alias */
if (*path != '/') {
int len;
- const char *p = separator;
+ const char *p = strchrnul(path, '/');
- if (!p)
- p = strchrnul(path, '/');
+ if (separator && separator < p)
+ p = separator;
len = p - path;
/* of_aliases must not be NULL */
@@ -984,15 +1000,15 @@ struct device_node *of_find_node_opts_by_path(const char *path, const char **opt
EXPORT_SYMBOL(of_find_node_opts_by_path);
/**
- * of_find_node_by_name - Find a node by its "name" property
- * @from: The node to start searching from or NULL; the node
+ * of_find_node_by_name - Find a node by its "name" property
+ * @from: The node to start searching from or NULL; the node
* you pass will not be searched, only the next one
* will. Typically, you pass what the previous call
* returned. of_node_put() will be called on @from.
- * @name: The name string to match against
+ * @name: The name string to match against
*
- * Returns a node pointer with refcount incremented, use
- * of_node_put() on it when done.
+ * Return: A node pointer with refcount incremented, use
+ * of_node_put() on it when done.
*/
struct device_node *of_find_node_by_name(struct device_node *from,
const char *name)
@@ -1011,16 +1027,16 @@ struct device_node *of_find_node_by_name(struct device_node *from,
EXPORT_SYMBOL(of_find_node_by_name);
/**
- * of_find_node_by_type - Find a node by its "device_type" property
- * @from: The node to start searching from, or NULL to start searching
+ * of_find_node_by_type - Find a node by its "device_type" property
+ * @from: The node to start searching from, or NULL to start searching
* the entire device tree. The node you pass will not be
* searched, only the next one will; typically, you pass
* what the previous call returned. of_node_put() will be
* called on from for you.
- * @type: The type string to match against
+ * @type: The type string to match against
*
- * Returns a node pointer with refcount incremented, use
- * of_node_put() on it when done.
+ * Return: A node pointer with refcount incremented, use
+ * of_node_put() on it when done.
*/
struct device_node *of_find_node_by_type(struct device_node *from,
const char *type)
@@ -1039,18 +1055,18 @@ struct device_node *of_find_node_by_type(struct device_node *from,
EXPORT_SYMBOL(of_find_node_by_type);
/**
- * of_find_compatible_node - Find a node based on type and one of the
+ * of_find_compatible_node - Find a node based on type and one of the
* tokens in its "compatible" property
- * @from: The node to start searching from or NULL, the node
- * you pass will not be searched, only the next one
- * will; typically, you pass what the previous call
- * returned. of_node_put() will be called on it
- * @type: The type string to match "device_type" or NULL to ignore
- * @compatible: The string to match to one of the tokens in the device
- * "compatible" list.
- *
- * Returns a node pointer with refcount incremented, use
- * of_node_put() on it when done.
+ * @from: The node to start searching from or NULL, the node
+ * you pass will not be searched, only the next one
+ * will; typically, you pass what the previous call
+ * returned. of_node_put() will be called on it
+ * @type: The type string to match "device_type" or NULL to ignore
+ * @compatible: The string to match to one of the tokens in the device
+ * "compatible" list.
+ *
+ * Return: A node pointer with refcount incremented, use
+ * of_node_put() on it when done.
*/
struct device_node *of_find_compatible_node(struct device_node *from,
const char *type, const char *compatible)
@@ -1070,34 +1086,30 @@ struct device_node *of_find_compatible_node(struct device_node *from,
EXPORT_SYMBOL(of_find_compatible_node);
/**
- * of_find_node_with_property - Find a node which has a property with
- * the given name.
- * @from: The node to start searching from or NULL, the node
- * you pass will not be searched, only the next one
- * will; typically, you pass what the previous call
- * returned. of_node_put() will be called on it
- * @prop_name: The name of the property to look for.
- *
- * Returns a node pointer with refcount incremented, use
- * of_node_put() on it when done.
+ * of_find_node_with_property - Find a node which has a property with
+ * the given name.
+ * @from: The node to start searching from or NULL, the node
+ * you pass will not be searched, only the next one
+ * will; typically, you pass what the previous call
+ * returned. of_node_put() will be called on it
+ * @prop_name: The name of the property to look for.
+ *
+ * Return: A node pointer with refcount incremented, use
+ * of_node_put() on it when done.
*/
struct device_node *of_find_node_with_property(struct device_node *from,
const char *prop_name)
{
struct device_node *np;
- struct property *pp;
unsigned long flags;
raw_spin_lock_irqsave(&devtree_lock, flags);
for_each_of_allnodes_from(from, np) {
- for (pp = np->properties; pp; pp = pp->next) {
- if (of_prop_cmp(pp->name, prop_name) == 0) {
- of_node_get(np);
- goto out;
- }
+ if (__of_find_property(np, prop_name, NULL)) {
+ of_node_get(np);
+ break;
}
}
-out:
of_node_put(from);
raw_spin_unlock_irqrestore(&devtree_lock, flags);
return np;
@@ -1128,10 +1140,10 @@ const struct of_device_id *__of_match_node(const struct of_device_id *matches,
/**
* of_match_node - Tell if a device_node has a matching of_match structure
- * @matches: array of of device match structures to search in
- * @node: the of device structure to match against
+ * @matches: array of of device match structures to search in
+ * @node: the of device structure to match against
*
- * Low level utility function used by device matching.
+ * Low level utility function used by device matching.
*/
const struct of_device_id *of_match_node(const struct of_device_id *matches,
const struct device_node *node)
@@ -1147,17 +1159,17 @@ const struct of_device_id *of_match_node(const struct of_device_id *matches,
EXPORT_SYMBOL(of_match_node);
/**
- * of_find_matching_node_and_match - Find a node based on an of_device_id
- * match table.
- * @from: The node to start searching from or NULL, the node
- * you pass will not be searched, only the next one
- * will; typically, you pass what the previous call
- * returned. of_node_put() will be called on it
- * @matches: array of of device match structures to search in
- * @match Updated to point at the matches entry which matched
- *
- * Returns a node pointer with refcount incremented, use
- * of_node_put() on it when done.
+ * of_find_matching_node_and_match - Find a node based on an of_device_id
+ * match table.
+ * @from: The node to start searching from or NULL, the node
+ * you pass will not be searched, only the next one
+ * will; typically, you pass what the previous call
+ * returned. of_node_put() will be called on it
+ * @matches: array of of device match structures to search in
+ * @match: Updated to point at the matches entry which matched
+ *
+ * Return: A node pointer with refcount incremented, use
+ * of_node_put() on it when done.
*/
struct device_node *of_find_matching_node_and_match(struct device_node *from,
const struct of_device_id *matches,
@@ -1186,19 +1198,23 @@ struct device_node *of_find_matching_node_and_match(struct device_node *from,
EXPORT_SYMBOL(of_find_matching_node_and_match);
/**
- * of_modalias_node - Lookup appropriate modalias for a device node
+ * of_alias_from_compatible - Lookup appropriate alias for a device node
+ * depending on compatible
* @node: pointer to a device tree node
- * @modalias: Pointer to buffer that modalias value will be copied into
- * @len: Length of modalias value
+ * @alias: Pointer to buffer that alias value will be copied into
+ * @len: Length of alias value
*
* Based on the value of the compatible property, this routine will attempt
- * to choose an appropriate modalias value for a particular device tree node.
+ * to choose an appropriate alias value for a particular device tree node.
* It does this by stripping the manufacturer prefix (as delimited by a ',')
* from the first entry in the compatible list property.
*
- * This routine returns 0 on success, <0 on failure.
+ * Note: The matching on just the "product" side of the compatible is a relic
+ * from I2C and SPI. Please do not add any new user.
+ *
+ * Return: This routine returns 0 on success, <0 on failure.
*/
-int of_modalias_node(struct device_node *node, char *modalias, int len)
+int of_alias_from_compatible(const struct device_node *node, char *alias, int len)
{
const char *compatible, *p;
int cplen;
@@ -1207,52 +1223,40 @@ int of_modalias_node(struct device_node *node, char *modalias, int len)
if (!compatible || strlen(compatible) > cplen)
return -ENODEV;
p = strchr(compatible, ',');
- strlcpy(modalias, p ? p + 1 : compatible, len);
+ strscpy(alias, p ? p + 1 : compatible, len);
return 0;
}
-EXPORT_SYMBOL_GPL(of_modalias_node);
+EXPORT_SYMBOL_GPL(of_alias_from_compatible);
/**
* of_find_node_by_phandle - Find a node given a phandle
* @handle: phandle of the node to find
*
- * Returns a node pointer with refcount incremented, use
+ * Return: A node pointer with refcount incremented, use
* of_node_put() on it when done.
*/
struct device_node *of_find_node_by_phandle(phandle handle)
{
struct device_node *np = NULL;
unsigned long flags;
- phandle masked_handle;
+ u32 handle_hash;
if (!handle)
return NULL;
+ handle_hash = of_phandle_cache_hash(handle);
+
raw_spin_lock_irqsave(&devtree_lock, flags);
- masked_handle = handle & phandle_cache_mask;
-
- if (phandle_cache) {
- if (phandle_cache[masked_handle] &&
- handle == phandle_cache[masked_handle]->phandle)
- np = phandle_cache[masked_handle];
- if (np && of_node_check_flag(np, OF_DETACHED)) {
- WARN_ON(1); /* did not uncache np on node removal */
- of_node_put(np);
- phandle_cache[masked_handle] = NULL;
- np = NULL;
- }
- }
+ if (phandle_cache[handle_hash] &&
+ handle == phandle_cache[handle_hash]->phandle)
+ np = phandle_cache[handle_hash];
if (!np) {
for_each_of_allnodes(np)
if (np->phandle == handle &&
!of_node_check_flag(np, OF_DETACHED)) {
- if (phandle_cache) {
- /* will put when removed from cache */
- of_node_get(np);
- phandle_cache[masked_handle] = np;
- }
+ phandle_cache[handle_hash] = np;
break;
}
}
@@ -1286,6 +1290,13 @@ int of_phandle_iterator_init(struct of_phandle_iterator *it,
memset(it, 0, sizeof(*it));
+ /*
+ * one of cell_count or cells_name must be provided to determine the
+ * argument length.
+ */
+ if (cell_count < 0 && !cells_name)
+ return -EINVAL;
+
list = of_get_property(np, list_name, &size);
if (!list)
return -ENOENT;
@@ -1328,18 +1339,27 @@ int of_phandle_iterator_next(struct of_phandle_iterator *it)
if (it->cells_name) {
if (!it->node) {
- pr_err("%pOF: could not find phandle\n",
- it->parent);
+ pr_err("%pOF: could not find phandle %d\n",
+ it->parent, it->phandle);
goto err;
}
if (of_property_read_u32(it->node, it->cells_name,
&count)) {
- pr_err("%pOF: could not get %s for %pOF\n",
- it->parent,
- it->cells_name,
- it->node);
- goto err;
+ /*
+ * If both cell_count and cells_name is given,
+ * fall back to cell_count in absence
+ * of the cells_name property
+ */
+ if (it->cell_count >= 0) {
+ count = it->cell_count;
+ } else {
+ pr_err("%pOF: could not get %s for %pOF\n",
+ it->parent,
+ it->cells_name,
+ it->node);
+ goto err;
+ }
}
} else {
count = it->cell_count;
@@ -1350,8 +1370,14 @@ int of_phandle_iterator_next(struct of_phandle_iterator *it)
* property data length
*/
if (it->cur + count > it->list_end) {
- pr_err("%pOF: arguments longer than property\n",
- it->parent);
+ if (it->cells_name)
+ pr_err("%pOF: %s = %d found %td\n",
+ it->parent, it->cells_name,
+ count, it->list_end - it->cur);
+ else
+ pr_err("%pOF: phandle %s needs %d, found %td\n",
+ it->parent, of_node_full_name(it->node),
+ count, it->list_end - it->cur);
goto err;
}
}
@@ -1388,15 +1414,18 @@ int of_phandle_iterator_args(struct of_phandle_iterator *it,
return count;
}
-static int __of_parse_phandle_with_args(const struct device_node *np,
- const char *list_name,
- const char *cells_name,
- int cell_count, int index,
- struct of_phandle_args *out_args)
+int __of_parse_phandle_with_args(const struct device_node *np,
+ const char *list_name,
+ const char *cells_name,
+ int cell_count, int index,
+ struct of_phandle_args *out_args)
{
struct of_phandle_iterator it;
int rc, cur_index = 0;
+ if (index < 0)
+ return -EINVAL;
+
/* Loop over the phandles until all the requested entry is found */
of_for_each_phandle(&it, rc, np, list_name, cells_name, cell_count) {
/*
@@ -1439,75 +1468,7 @@ static int __of_parse_phandle_with_args(const struct device_node *np,
of_node_put(it.node);
return rc;
}
-
-/**
- * of_parse_phandle - Resolve a phandle property to a device_node pointer
- * @np: Pointer to device node holding phandle property
- * @phandle_name: Name of property holding a phandle value
- * @index: For properties holding a table of phandles, this is the index into
- * the table
- *
- * Returns the device_node pointer with refcount incremented. Use
- * of_node_put() on it when done.
- */
-struct device_node *of_parse_phandle(const struct device_node *np,
- const char *phandle_name, int index)
-{
- struct of_phandle_args args;
-
- if (index < 0)
- return NULL;
-
- if (__of_parse_phandle_with_args(np, phandle_name, NULL, 0,
- index, &args))
- return NULL;
-
- return args.np;
-}
-EXPORT_SYMBOL(of_parse_phandle);
-
-/**
- * of_parse_phandle_with_args() - Find a node pointed by phandle in a list
- * @np: pointer to a device tree node containing a list
- * @list_name: property name that contains a list
- * @cells_name: property name that specifies phandles' arguments count
- * @index: index of a phandle to parse out
- * @out_args: optional pointer to output arguments structure (will be filled)
- *
- * This function is useful to parse lists of phandles and their arguments.
- * Returns 0 on success and fills out_args, on error returns appropriate
- * errno value.
- *
- * Caller is responsible to call of_node_put() on the returned out_args->np
- * pointer.
- *
- * Example:
- *
- * phandle1: node1 {
- * #list-cells = <2>;
- * }
- *
- * phandle2: node2 {
- * #list-cells = <1>;
- * }
- *
- * node3 {
- * list = <&phandle1 1 2 &phandle2 3>;
- * }
- *
- * To get a device_node of the `node2' node you may call this:
- * of_parse_phandle_with_args(node3, "list", "#list-cells", 1, &args);
- */
-int of_parse_phandle_with_args(const struct device_node *np, const char *list_name,
- const char *cells_name, int index,
- struct of_phandle_args *out_args)
-{
- if (index < 0)
- return -EINVAL;
- return __of_parse_phandle_with_args(np, list_name, cells_name, 0,
- index, out_args);
-}
-EXPORT_SYMBOL(of_parse_phandle_with_args);
+EXPORT_SYMBOL(__of_parse_phandle_with_args);
/**
* of_parse_phandle_with_args_map() - Find a node pointed by phandle in a list and remap it
@@ -1526,29 +1487,29 @@ EXPORT_SYMBOL(of_parse_phandle_with_args);
* Caller is responsible to call of_node_put() on the returned out_args->np
* pointer.
*
- * Example:
+ * Example::
*
- * phandle1: node1 {
- * #list-cells = <2>;
- * }
+ * phandle1: node1 {
+ * #list-cells = <2>;
+ * };
*
- * phandle2: node2 {
- * #list-cells = <1>;
- * }
+ * phandle2: node2 {
+ * #list-cells = <1>;
+ * };
*
- * phandle3: node3 {
- * #list-cells = <1>;
- * list-map = <0 &phandle2 3>,
- * <1 &phandle2 2>,
- * <2 &phandle1 5 1>;
- * list-map-mask = <0x3>;
- * };
+ * phandle3: node3 {
+ * #list-cells = <1>;
+ * list-map = <0 &phandle2 3>,
+ * <1 &phandle2 2>,
+ * <2 &phandle1 5 1>;
+ * list-map-mask = <0x3>;
+ * };
*
- * node4 {
- * list = <&phandle1 1 2 &phandle3 0>;
- * }
+ * node4 {
+ * list = <&phandle1 1 2 &phandle3 0>;
+ * };
*
- * To get a device_node of the `node2' node you may call this:
+ * To get a device_node of the ``node2`` node you may call this:
* of_parse_phandle_with_args(node4, "list", "list", 1, &args);
*/
int of_parse_phandle_with_args_map(const struct device_node *np,
@@ -1556,12 +1517,14 @@ int of_parse_phandle_with_args_map(const struct device_node *np,
const char *stem_name,
int index, struct of_phandle_args *out_args)
{
- char *cells_name, *map_name = NULL, *mask_name = NULL;
- char *pass_name = NULL;
+ char *cells_name __free(kfree) = kasprintf(GFP_KERNEL, "#%s-cells", stem_name);
+ char *map_name __free(kfree) = kasprintf(GFP_KERNEL, "%s-map", stem_name);
+ char *mask_name __free(kfree) = kasprintf(GFP_KERNEL, "%s-map-mask", stem_name);
+ char *pass_name __free(kfree) = kasprintf(GFP_KERNEL, "%s-map-pass-thru", stem_name);
struct device_node *cur, *new = NULL;
const __be32 *map, *mask, *pass;
- static const __be32 dummy_mask[] = { [0 ... MAX_PHANDLE_ARGS] = ~0 };
- static const __be32 dummy_pass[] = { [0 ... MAX_PHANDLE_ARGS] = 0 };
+ static const __be32 dummy_mask[] = { [0 ... (MAX_PHANDLE_ARGS - 1)] = cpu_to_be32(~0) };
+ static const __be32 dummy_pass[] = { [0 ... (MAX_PHANDLE_ARGS - 1)] = cpu_to_be32(0) };
__be32 initial_match_array[MAX_PHANDLE_ARGS];
const __be32 *match_array = initial_match_array;
int i, ret, map_len, match;
@@ -1570,27 +1533,13 @@ int of_parse_phandle_with_args_map(const struct device_node *np,
if (index < 0)
return -EINVAL;
- cells_name = kasprintf(GFP_KERNEL, "#%s-cells", stem_name);
- if (!cells_name)
+ if (!cells_name || !map_name || !mask_name || !pass_name)
return -ENOMEM;
- ret = -ENOMEM;
- map_name = kasprintf(GFP_KERNEL, "%s-map", stem_name);
- if (!map_name)
- goto free;
-
- mask_name = kasprintf(GFP_KERNEL, "%s-map-mask", stem_name);
- if (!mask_name)
- goto free;
-
- pass_name = kasprintf(GFP_KERNEL, "%s-map-pass-thru", stem_name);
- if (!pass_name)
- goto free;
-
- ret = __of_parse_phandle_with_args(np, list_name, cells_name, 0, index,
+ ret = __of_parse_phandle_with_args(np, list_name, cells_name, -1, index,
out_args);
if (ret)
- goto free;
+ return ret;
/* Get the #<list>-cells property */
cur = out_args->np;
@@ -1607,8 +1556,7 @@ int of_parse_phandle_with_args_map(const struct device_node *np,
/* Get the <list>-map property */
map = of_get_property(cur, map_name, &map_len);
if (!map) {
- ret = 0;
- goto free;
+ return 0;
}
map_len /= sizeof(u32);
@@ -1630,8 +1578,10 @@ int of_parse_phandle_with_args_map(const struct device_node *np,
map_len--;
/* Check if not found */
- if (!new)
+ if (!new) {
+ ret = -EINVAL;
goto put;
+ }
if (!of_device_is_available(new))
match = 0;
@@ -1641,17 +1591,20 @@ int of_parse_phandle_with_args_map(const struct device_node *np,
goto put;
/* Check for malformed properties */
- if (WARN_ON(new_size > MAX_PHANDLE_ARGS))
- goto put;
- if (map_len < new_size)
+ if (WARN_ON(new_size > MAX_PHANDLE_ARGS) ||
+ map_len < new_size) {
+ ret = -EINVAL;
goto put;
+ }
/* Move forward by new node's #<list>-cells amount */
map += new_size;
map_len -= new_size;
}
- if (!match)
+ if (!match) {
+ ret = -ENOENT;
goto put;
+ }
/* Get the <list>-map-pass-thru property (optional) */
pass = of_get_property(cur, pass_name, NULL);
@@ -1663,7 +1616,6 @@ int of_parse_phandle_with_args_map(const struct device_node *np,
* specifier into the out_args structure, keeping the
* bits specified in <list>-map-pass-thru.
*/
- match_array = map - new_size;
for (i = 0; i < new_size; i++) {
__be32 val = *(map - new_size + i);
@@ -1672,6 +1624,7 @@ int of_parse_phandle_with_args_map(const struct device_node *np,
val |= cpu_to_be32(out_args->args[i]) & pass[i];
}
+ initial_match_array[i] = val;
out_args->args[i] = be32_to_cpu(val);
}
out_args->args_count = list_size = new_size;
@@ -1679,68 +1632,22 @@ int of_parse_phandle_with_args_map(const struct device_node *np,
out_args->np = new;
of_node_put(cur);
cur = new;
+ new = NULL;
}
put:
of_node_put(cur);
of_node_put(new);
-free:
- kfree(mask_name);
- kfree(map_name);
- kfree(cells_name);
- kfree(pass_name);
-
return ret;
}
EXPORT_SYMBOL(of_parse_phandle_with_args_map);
/**
- * of_parse_phandle_with_fixed_args() - Find a node pointed by phandle in a list
- * @np: pointer to a device tree node containing a list
- * @list_name: property name that contains a list
- * @cell_count: number of argument cells following the phandle
- * @index: index of a phandle to parse out
- * @out_args: optional pointer to output arguments structure (will be filled)
- *
- * This function is useful to parse lists of phandles and their arguments.
- * Returns 0 on success and fills out_args, on error returns appropriate
- * errno value.
- *
- * Caller is responsible to call of_node_put() on the returned out_args->np
- * pointer.
- *
- * Example:
- *
- * phandle1: node1 {
- * }
- *
- * phandle2: node2 {
- * }
- *
- * node3 {
- * list = <&phandle1 0 2 &phandle2 2 3>;
- * }
- *
- * To get a device_node of the `node2' node you may call this:
- * of_parse_phandle_with_fixed_args(node3, "list", 2, 1, &args);
- */
-int of_parse_phandle_with_fixed_args(const struct device_node *np,
- const char *list_name, int cell_count,
- int index, struct of_phandle_args *out_args)
-{
- if (index < 0)
- return -EINVAL;
- return __of_parse_phandle_with_args(np, list_name, NULL, cell_count,
- index, out_args);
-}
-EXPORT_SYMBOL(of_parse_phandle_with_fixed_args);
-
-/**
* of_count_phandle_with_args() - Find the number of phandles references in a property
* @np: pointer to a device tree node containing a list
* @list_name: property name that contains a list
* @cells_name: property name that specifies phandles' arguments count
*
- * Returns the number of phandle + argument tuples within a property. It
+ * Return: The number of phandle + argument tuples within a property. It
* is a typical pattern to encode a list of phandle and variable
* arguments into a single property. The number of arguments is encoded
* by a property in the phandle-target node. For example, a gpios
@@ -1755,7 +1662,24 @@ int of_count_phandle_with_args(const struct device_node *np, const char *list_na
struct of_phandle_iterator it;
int rc, cur_index = 0;
- rc = of_phandle_iterator_init(&it, np, list_name, cells_name, 0);
+ /*
+ * If cells_name is NULL we assume a cell count of 0. This makes
+ * counting the phandles trivial as each 32bit word in the list is a
+ * phandle and no arguments are to consider. So we don't iterate through
+ * the list but just use the length to determine the phandle count.
+ */
+ if (!cells_name) {
+ const __be32 *list;
+ int size;
+
+ list = of_get_property(np, list_name, &size);
+ if (!list)
+ return -ENOENT;
+
+ return size / sizeof(*list);
+ }
+
+ rc = of_phandle_iterator_init(&it, np, list_name, cells_name, -1);
if (rc)
return rc;
@@ -1769,44 +1693,67 @@ int of_count_phandle_with_args(const struct device_node *np, const char *list_na
}
EXPORT_SYMBOL(of_count_phandle_with_args);
+static struct property *__of_remove_property_from_list(struct property **list, struct property *prop)
+{
+ struct property **next;
+
+ for (next = list; *next; next = &(*next)->next) {
+ if (*next == prop) {
+ *next = prop->next;
+ prop->next = NULL;
+ return prop;
+ }
+ }
+ return NULL;
+}
+
/**
* __of_add_property - Add a property to a node without lock operations
+ * @np: Caller's Device Node
+ * @prop: Property to add
*/
int __of_add_property(struct device_node *np, struct property *prop)
{
+ int rc = 0;
+ unsigned long flags;
struct property **next;
+ raw_spin_lock_irqsave(&devtree_lock, flags);
+
+ __of_remove_property_from_list(&np->deadprops, prop);
+
prop->next = NULL;
next = &np->properties;
while (*next) {
- if (strcmp(prop->name, (*next)->name) == 0)
+ if (of_prop_cmp(prop->name, (*next)->name) == 0) {
/* duplicate ! don't insert it */
- return -EEXIST;
-
+ rc = -EEXIST;
+ goto out_unlock;
+ }
next = &(*next)->next;
}
*next = prop;
+out_unlock:
+ raw_spin_unlock_irqrestore(&devtree_lock, flags);
+ if (rc)
+ return rc;
+
+ __of_add_property_sysfs(np, prop);
return 0;
}
/**
* of_add_property - Add a property to a node
+ * @np: Caller's Device Node
+ * @prop: Property to add
*/
int of_add_property(struct device_node *np, struct property *prop)
{
- unsigned long flags;
int rc;
mutex_lock(&of_mutex);
-
- raw_spin_lock_irqsave(&devtree_lock, flags);
rc = __of_add_property(np, prop);
- raw_spin_unlock_irqrestore(&devtree_lock, flags);
-
- if (!rc)
- __of_add_property_sysfs(np, prop);
-
mutex_unlock(&of_mutex);
if (!rc)
@@ -1814,28 +1761,34 @@ int of_add_property(struct device_node *np, struct property *prop)
return rc;
}
+EXPORT_SYMBOL_GPL(of_add_property);
int __of_remove_property(struct device_node *np, struct property *prop)
{
- struct property **next;
+ unsigned long flags;
+ int rc = -ENODEV;
- for (next = &np->properties; *next; next = &(*next)->next) {
- if (*next == prop)
- break;
+ raw_spin_lock_irqsave(&devtree_lock, flags);
+
+ if (__of_remove_property_from_list(&np->properties, prop)) {
+ /* Found the property, add it to deadprops list */
+ prop->next = np->deadprops;
+ np->deadprops = prop;
+ rc = 0;
}
- if (*next == NULL)
- return -ENODEV;
- /* found the node */
- *next = prop->next;
- prop->next = np->deadprops;
- np->deadprops = prop;
+ raw_spin_unlock_irqrestore(&devtree_lock, flags);
+ if (rc)
+ return rc;
+ __of_remove_property_sysfs(np, prop);
return 0;
}
/**
* of_remove_property - Remove a property from a node.
+ * @np: Caller's Device Node
+ * @prop: Property to remove
*
* Note that we don't actually remove it, since we have given out
* who-knows-how-many pointers to the data using get-property.
@@ -1844,21 +1797,13 @@ int __of_remove_property(struct device_node *np, struct property *prop)
*/
int of_remove_property(struct device_node *np, struct property *prop)
{
- unsigned long flags;
int rc;
if (!prop)
return -ENODEV;
mutex_lock(&of_mutex);
-
- raw_spin_lock_irqsave(&devtree_lock, flags);
rc = __of_remove_property(np, prop);
- raw_spin_unlock_irqrestore(&devtree_lock, flags);
-
- if (!rc)
- __of_remove_property_sysfs(np, prop);
-
mutex_unlock(&of_mutex);
if (!rc)
@@ -1866,11 +1811,17 @@ int of_remove_property(struct device_node *np, struct property *prop)
return rc;
}
+EXPORT_SYMBOL_GPL(of_remove_property);
int __of_update_property(struct device_node *np, struct property *newprop,
struct property **oldpropp)
{
struct property **next, *oldprop;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&devtree_lock, flags);
+
+ __of_remove_property_from_list(&np->deadprops, newprop);
for (next = &np->properties; *next; next = &(*next)->next) {
if (of_prop_cmp((*next)->name, newprop->name) == 0)
@@ -1890,6 +1841,10 @@ int __of_update_property(struct device_node *np, struct property *newprop,
*next = newprop;
}
+ raw_spin_unlock_irqrestore(&devtree_lock, flags);
+
+ __of_update_property_sysfs(np, newprop, oldprop);
+
return 0;
}
@@ -1905,21 +1860,13 @@ int __of_update_property(struct device_node *np, struct property *newprop,
int of_update_property(struct device_node *np, struct property *newprop)
{
struct property *oldprop;
- unsigned long flags;
int rc;
if (!newprop->name)
return -EINVAL;
mutex_lock(&of_mutex);
-
- raw_spin_lock_irqsave(&devtree_lock, flags);
rc = __of_update_property(np, newprop, &oldprop);
- raw_spin_unlock_irqrestore(&devtree_lock, flags);
-
- if (!rc)
- __of_update_property_sysfs(np, newprop, oldprop);
-
mutex_unlock(&of_mutex);
if (!rc)
@@ -1933,8 +1880,7 @@ static void of_alias_add(struct alias_prop *ap, struct device_node *np,
{
ap->np = np;
ap->id = id;
- strncpy(ap->stem, stem, stem_len);
- ap->stem[stem_len] = 0;
+ strscpy(ap->stem, stem, stem_len + 1);
list_add_tail(&ap->link, &aliases_lookup);
pr_debug("adding DT alias:%s: stem=%s id=%i node=%pOF\n",
ap->alias, ap->stem, ap->id, np);
@@ -1942,17 +1888,15 @@ static void of_alias_add(struct alias_prop *ap, struct device_node *np,
/**
* of_alias_scan - Scan all properties of the 'aliases' node
- *
- * The function scans all the properties of the 'aliases' node and populates
- * the global lookup table with the properties. It returns the
- * number of alias properties found, or an error code in case of failure.
- *
* @dt_alloc: An allocator that provides a virtual address to memory
* for storing the resulting tree
+ *
+ * The function scans all the properties of the 'aliases' node and populates
+ * the global lookup table with the properties.
*/
void of_alias_scan(void * (*dt_alloc)(u64 size, u64 align))
{
- struct property *pp;
+ const struct property *pp;
of_aliases = of_find_node_by_path("/aliases");
of_chosen = of_find_node_by_path("/chosen");
@@ -1970,6 +1914,8 @@ void of_alias_scan(void * (*dt_alloc)(u64 size, u64 align))
of_property_read_string(of_aliases, "stdout", &name);
if (name)
of_stdout = of_find_node_opts_by_path(name, &of_stdout_options);
+ if (of_stdout)
+ of_stdout->fwnode.flags |= FWNODE_FLAG_BEST_EFFORT;
}
if (!of_aliases)
@@ -1983,9 +1929,7 @@ void of_alias_scan(void * (*dt_alloc)(u64 size, u64 align))
int id, len;
/* Skip those we do not want to proceed */
- if (!strcmp(pp->name, "name") ||
- !strcmp(pp->name, "phandle") ||
- !strcmp(pp->name, "linux,phandle"))
+ if (is_pseudo_property(pp->name))
continue;
np = of_find_node_by_path(pp->value);
@@ -2017,9 +1961,11 @@ void of_alias_scan(void * (*dt_alloc)(u64 size, u64 align))
* @stem: Alias stem of the given device_node
*
* The function travels the lookup table to get the alias id for the given
- * device_node and alias stem. It returns the alias id if found.
+ * device_node and alias stem.
+ *
+ * Return: The alias id if found.
*/
-int of_alias_get_id(struct device_node *np, const char *stem)
+int of_alias_get_id(const struct device_node *np, const char *stem)
{
struct alias_prop *app;
int id = -ENODEV;
@@ -2041,59 +1987,6 @@ int of_alias_get_id(struct device_node *np, const char *stem)
EXPORT_SYMBOL_GPL(of_alias_get_id);
/**
- * of_alias_get_alias_list - Get alias list for the given device driver
- * @matches: Array of OF device match structures to search in
- * @stem: Alias stem of the given device_node
- * @bitmap: Bitmap field pointer
- * @nbits: Maximum number of alias IDs which can be recorded in bitmap
- *
- * The function travels the lookup table to record alias ids for the given
- * device match structures and alias stem.
- *
- * Return: 0 or -ENOSYS when !CONFIG_OF or
- * -EOVERFLOW if alias ID is greater then allocated nbits
- */
-int of_alias_get_alias_list(const struct of_device_id *matches,
- const char *stem, unsigned long *bitmap,
- unsigned int nbits)
-{
- struct alias_prop *app;
- int ret = 0;
-
- /* Zero bitmap field to make sure that all the time it is clean */
- bitmap_zero(bitmap, nbits);
-
- mutex_lock(&of_mutex);
- pr_debug("%s: Looking for stem: %s\n", __func__, stem);
- list_for_each_entry(app, &aliases_lookup, link) {
- pr_debug("%s: stem: %s, id: %d\n",
- __func__, app->stem, app->id);
-
- if (strcmp(app->stem, stem) != 0) {
- pr_debug("%s: stem comparison didn't pass %s\n",
- __func__, app->stem);
- continue;
- }
-
- if (of_match_node(matches, app->np)) {
- pr_debug("%s: Allocated ID %d\n", __func__, app->id);
-
- if (app->id >= nbits) {
- pr_warn("%s: ID %d >= than bitmap field %d\n",
- __func__, app->id, nbits);
- ret = -EOVERFLOW;
- } else {
- set_bit(app->id, bitmap);
- }
- }
- }
- mutex_unlock(&of_mutex);
-
- return ret;
-}
-EXPORT_SYMBOL_GPL(of_alias_get_alias_list);
-
-/**
* of_alias_get_highest_id - Get highest alias id for the given stem
* @stem: Alias stem to be examined
*
@@ -2121,15 +2014,16 @@ EXPORT_SYMBOL_GPL(of_alias_get_highest_id);
/**
* of_console_check() - Test and setup console for DT setup
- * @dn - Pointer to device node
- * @name - Name to use for preferred console without index. ex. "ttyS"
- * @index - Index to use for preferred console.
+ * @dn: Pointer to device node
+ * @name: Name to use for preferred console without index. ex. "ttyS"
+ * @index: Index to use for preferred console.
*
* Check if the given device node matches the stdout-path property in the
- * /chosen node. If it does then register it as the preferred console and return
- * TRUE. Otherwise return FALSE.
+ * /chosen node. If it does then register it as the preferred console.
+ *
+ * Return: TRUE if console successfully setup. Otherwise return FALSE.
*/
-bool of_console_check(struct device_node *dn, char *name, int index)
+bool of_console_check(const struct device_node *dn, char *name, int index)
{
if (!dn || dn != of_stdout || console_set_on_cmdline)
return false;
@@ -2143,12 +2037,12 @@ bool of_console_check(struct device_node *dn, char *name, int index)
EXPORT_SYMBOL_GPL(of_console_check);
/**
- * of_find_next_cache_node - Find a node's subsidiary cache
- * @np: node of type "cpu" or "cache"
+ * of_find_next_cache_node - Find a node's subsidiary cache
+ * @np: node of type "cpu" or "cache"
*
- * Returns a node pointer with refcount incremented, use
- * of_node_put() on it when done. Caller should hold a reference
- * to np.
+ * Return: A node pointer with refcount incremented, use
+ * of_node_put() on it when done. Caller should hold a reference
+ * to np.
*/
struct device_node *of_find_next_cache_node(const struct device_node *np)
{
@@ -2178,7 +2072,7 @@ struct device_node *of_find_next_cache_node(const struct device_node *np)
*
* @cpu: cpu number(logical index) for which the last cache level is needed
*
- * Returns the the level at which the last cache is present. It is exactly
+ * Return: The level at which the last cache is present. It is exactly
* same as the total number of cache levels for the given logical cpu.
*/
int of_find_last_cache_level(unsigned int cpu)
@@ -2187,26 +2081,27 @@ int of_find_last_cache_level(unsigned int cpu)
struct device_node *prev = NULL, *np = of_cpu_device_node_get(cpu);
while (np) {
+ of_node_put(prev);
prev = np;
- of_node_put(np);
np = of_find_next_cache_node(np);
}
of_property_read_u32(prev, "cache-level", &cache_level);
+ of_node_put(prev);
return cache_level;
}
/**
- * of_map_rid - Translate a requester ID through a downstream mapping.
+ * of_map_id - Translate an ID through a downstream mapping.
* @np: root complex device node.
- * @rid: device requester ID to map.
+ * @id: device ID to map.
* @map_name: property name of the map to use.
* @map_mask_name: optional property name of the mask to use.
* @target: optional pointer to a target device node.
* @id_out: optional pointer to receive the translated ID.
*
- * Given a device requester ID, look up the appropriate implementation-defined
+ * Given a device ID, look up the appropriate implementation-defined
* platform ID and/or the target device which receives transactions on that
* ID, as per the "iommu-map" and "msi-map" bindings. Either of @target or
* @id_out may be NULL if only the other is required. If @target points to
@@ -2216,11 +2111,11 @@ int of_find_last_cache_level(unsigned int cpu)
*
* Return: 0 on success or a standard error code on failure.
*/
-int of_map_rid(struct device_node *np, u32 rid,
+int of_map_id(const struct device_node *np, u32 id,
const char *map_name, const char *map_mask_name,
struct device_node **target, u32 *id_out)
{
- u32 map_mask, masked_rid;
+ u32 map_mask, masked_id;
int map_len;
const __be32 *map = NULL;
@@ -2232,7 +2127,7 @@ int of_map_rid(struct device_node *np, u32 rid,
if (target)
return -ENODEV;
/* Otherwise, no map implies no translation */
- *id_out = rid;
+ *id_out = id;
return 0;
}
@@ -2252,22 +2147,22 @@ int of_map_rid(struct device_node *np, u32 rid,
if (map_mask_name)
of_property_read_u32(np, map_mask_name, &map_mask);
- masked_rid = map_mask & rid;
+ masked_id = map_mask & id;
for ( ; map_len > 0; map_len -= 4 * sizeof(*map), map += 4) {
struct device_node *phandle_node;
- u32 rid_base = be32_to_cpup(map + 0);
+ u32 id_base = be32_to_cpup(map + 0);
u32 phandle = be32_to_cpup(map + 1);
u32 out_base = be32_to_cpup(map + 2);
- u32 rid_len = be32_to_cpup(map + 3);
+ u32 id_len = be32_to_cpup(map + 3);
- if (rid_base & ~map_mask) {
- pr_err("%pOF: Invalid %s translation - %s-mask (0x%x) ignores rid-base (0x%x)\n",
+ if (id_base & ~map_mask) {
+ pr_err("%pOF: Invalid %s translation - %s-mask (0x%x) ignores id-base (0x%x)\n",
np, map_name, map_name,
- map_mask, rid_base);
+ map_mask, id_base);
return -EFAULT;
}
- if (masked_rid < rid_base || masked_rid >= rid_base + rid_len)
+ if (masked_id < id_base || masked_id >= id_base + id_len)
continue;
phandle_node = of_find_node_by_phandle(phandle);
@@ -2285,16 +2180,20 @@ int of_map_rid(struct device_node *np, u32 rid,
}
if (id_out)
- *id_out = masked_rid - rid_base + out_base;
+ *id_out = masked_id - id_base + out_base;
- pr_debug("%pOF: %s, using mask %08x, rid-base: %08x, out-base: %08x, length: %08x, rid: %08x -> %08x\n",
- np, map_name, map_mask, rid_base, out_base,
- rid_len, rid, masked_rid - rid_base + out_base);
+ pr_debug("%pOF: %s, using mask %08x, id-base: %08x, out-base: %08x, length: %08x, id: %08x -> %08x\n",
+ np, map_name, map_mask, id_base, out_base,
+ id_len, id, masked_id - id_base + out_base);
return 0;
}
- pr_err("%pOF: Invalid %s translation - no match for rid 0x%x on %pOF\n",
- np, map_name, rid, target && *target ? *target : NULL);
- return -EFAULT;
+ pr_info("%pOF: no %s translation for id 0x%x on %pOF\n", np, map_name,
+ id, target && *target ? *target : NULL);
+
+ /* Bypasses translation */
+ if (id_out)
+ *id_out = id;
+ return 0;
}
-EXPORT_SYMBOL_GPL(of_map_rid);
+EXPORT_SYMBOL_GPL(of_map_id);
diff --git a/drivers/of/cpu.c b/drivers/of/cpu.c
new file mode 100644
index 000000000000..5214dc3d05ae
--- /dev/null
+++ b/drivers/of/cpu.c
@@ -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)
+ 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);
diff --git a/drivers/of/device.c b/drivers/of/device.c
index 3717f2a20d0d..f7e75e527667 100644
--- a/drivers/of/device.c
+++ b/drivers/of/device.c
@@ -1,13 +1,13 @@
// SPDX-License-Identifier: GPL-2.0
-#include <linux/string.h>
#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/dma-mapping.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/module.h>
#include <linux/mod_devicetable.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
@@ -17,8 +17,8 @@
/**
* of_match_device - Tell if a struct device matches an of_device_id list
- * @ids: array of of device match structures to search in
- * @dev: the of device structure to match against
+ * @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.
@@ -26,58 +26,53 @@
const struct of_device_id *of_match_device(const struct of_device_id *matches,
const struct device *dev)
{
- if ((!matches) || (!dev->of_node))
+ if (!matches || !dev->of_node || dev->of_node_reused)
return NULL;
return of_match_node(matches, dev->of_node);
}
EXPORT_SYMBOL(of_match_device);
-struct platform_device *of_dev_get(struct platform_device *dev)
+static void
+of_dma_set_restricted_buffer(struct device *dev, struct device_node *np)
{
- struct device *tmp;
+ struct device_node *of_node = dev->of_node;
+ struct of_phandle_iterator it;
+ int rc, i = 0;
- if (!dev)
- return NULL;
- tmp = get_device(&dev->dev);
- if (tmp)
- return to_platform_device(tmp);
- else
- return NULL;
-}
-EXPORT_SYMBOL(of_dev_get);
-
-void of_dev_put(struct platform_device *dev)
-{
- if (dev)
- put_device(&dev->dev);
-}
-EXPORT_SYMBOL(of_dev_put);
-
-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 (!IS_ENABLED(CONFIG_DMA_RESTRICTED_POOL))
+ return;
/*
- * 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.
+ * If dev->of_node doesn't exist or doesn't contain memory-region, try
+ * the OF node having DMA configuration.
*/
- set_dev_node(&ofdev->dev, of_node_to_nid(ofdev->dev.of_node));
+ 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++;
+ }
- return device_add(&ofdev->dev);
}
/**
- * of_dma_configure - Setup DMA configuration
+ * 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.
@@ -86,16 +81,27 @@ int of_device_add(struct platform_device *ofdev)
* can use a platform bus notifier and handle BUS_NOTIFY_ADD_DEVICE events
* to fix up DMA configuration.
*/
-int of_dma_configure(struct device *dev, struct device_node *np, bool force_dma)
+int of_dma_configure_id(struct device *dev, struct device_node *np,
+ bool force_dma, const u32 *id)
{
- u64 dma_addr, paddr, size = 0;
+ const struct bus_dma_region *map = NULL;
+ struct device_node *bus_np;
+ u64 mask, end = 0;
+ bool coherent, set_map = false;
int ret;
- bool coherent;
- unsigned long offset;
- const struct iommu_ops *iommu;
- u64 mask;
- ret = of_dma_get_range(np, &dma_addr, &paddr, &size);
+ 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
@@ -104,28 +110,12 @@ int of_dma_configure(struct device *dev, struct device_node *np, bool force_dma)
*/
if (!force_dma)
return ret == -ENODEV ? 0 : ret;
-
- dma_addr = offset = 0;
} else {
- offset = PFN_DOWN(paddr - dma_addr);
-
- /*
- * Add a work around to treat the size as mask + 1 in case
- * it is defined in DT as a mask.
- */
- if (size & 1) {
- dev_warn(dev, "Invalid size 0x%llx for dma-range\n",
- size);
- size = size + 1;
- }
-
- if (!size) {
- dev_err(dev, "Adjusted size 0x%llx invalid\n", size);
- return -EINVAL;
- }
- dev_dbg(dev, "dma_pfn_offset(%#08lx)\n", offset);
+ /* 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
@@ -137,53 +127,48 @@ int of_dma_configure(struct device *dev, struct device_node *np, bool force_dma)
dev->dma_mask = &dev->coherent_dma_mask;
}
- if (!size && dev->coherent_dma_mask)
- size = max(dev->coherent_dma_mask, dev->coherent_dma_mask + 1);
- else if (!size)
- size = 1ULL << 32;
-
- dev->dma_pfn_offset = offset;
+ 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(dma_addr + size - 1) + 1);
+ mask = DMA_BIT_MASK(ilog2(end) + 1);
dev->coherent_dma_mask &= mask;
*dev->dma_mask &= mask;
- /* ...but only set bus mask if we found valid dma-ranges earlier */
- if (!ret)
- dev->bus_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 ");
- iommu = of_iommu_configure(dev, np);
- if (IS_ERR(iommu) && PTR_ERR(iommu) == -EPROBE_DEFER)
+ 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",
- iommu ? " " : " not ");
-
- arch_setup_dma_ops(dev, dma_addr, size, iommu, coherent);
+ !ret ? " " : " not ");
- return 0;
-}
-EXPORT_SYMBOL_GPL(of_dma_configure);
+ arch_setup_dma_ops(dev, coherent);
-int of_device_register(struct platform_device *pdev)
-{
- device_initialize(&pdev->dev);
- return of_device_add(pdev);
-}
-EXPORT_SYMBOL(of_device_register);
+ if (ret)
+ of_dma_set_restricted_buffer(dev, np);
-void of_device_unregister(struct platform_device *ofdev)
-{
- device_unregister(&ofdev->dev);
+ return 0;
}
-EXPORT_SYMBOL(of_device_unregister);
+EXPORT_SYMBOL_GPL(of_dma_configure_id);
const void *of_device_get_match_data(const struct device *dev)
{
@@ -197,74 +182,20 @@ const void *of_device_get_match_data(const struct device *dev)
}
EXPORT_SYMBOL(of_device_get_match_data);
-static ssize_t of_device_get_modalias(struct device *dev, char *str, ssize_t len)
-{
- const char *compat;
- char *c;
- struct property *p;
- ssize_t csize;
- ssize_t tsize;
-
- if ((!dev) || (!dev->of_node))
- return -ENODEV;
-
- /* Name & Type */
- /* %p eats all alphanum characters, so %c must be used here */
- csize = snprintf(str, len, "of:N%pOFn%c%s", dev->of_node, 'T',
- of_node_get_device_type(dev->of_node));
- tsize = csize;
- len -= csize;
- if (str)
- str += csize;
-
- of_property_for_each_string(dev->of_node, "compatible", p, compat) {
- csize = strlen(compat) + 1;
- tsize += csize;
- if (csize > len)
- continue;
-
- csize = snprintf(str, len, "C%s", compat);
- for (c = str; c; ) {
- c = strchr(c, ' ');
- if (c)
- *c++ = '_';
- }
- len -= csize;
- str += csize;
- }
-
- return tsize;
-}
-
-int of_device_request_module(struct device *dev)
-{
- char *str;
- ssize_t size;
- int ret;
-
- size = of_device_get_modalias(dev, NULL, 0);
- if (size < 0)
- return size;
-
- str = kmalloc(size + 1, GFP_KERNEL);
- if (!str)
- return -ENOMEM;
-
- of_device_get_modalias(dev, str, size);
- str[size] = '\0';
- ret = request_module(str);
- kfree(str);
-
- return ret;
-}
-EXPORT_SYMBOL_GPL(of_device_request_module);
-
/**
* 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 = of_device_get_modalias(dev, str, len - 2);
+ ssize_t sl;
+
+ if (!dev || !dev->of_node || dev->of_node_reused)
+ return -ENODEV;
+
+ sl = of_modalias(dev->of_node, str, len - 2);
if (sl < 0)
return sl;
if (sl > len - 2)
@@ -278,8 +209,10 @@ 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(struct device *dev, struct kobj_uevent_env *env)
+void of_device_uevent(const struct device *dev, struct kobj_uevent_env *env)
{
const char *compat, *type;
struct alias_prop *app;
@@ -315,20 +248,23 @@ void of_device_uevent(struct device *dev, struct kobj_uevent_env *env)
}
mutex_unlock(&of_mutex);
}
+EXPORT_SYMBOL_GPL(of_device_uevent);
-int of_device_uevent_modalias(struct device *dev, struct kobj_uevent_env *env)
+int of_device_uevent_modalias(const struct device *dev, struct kobj_uevent_env *env)
{
int sl;
- if ((!dev) || (!dev->of_node))
+ if ((!dev) || (!dev->of_node) || dev->of_node_reused)
return -ENODEV;
/* Devicetree modalias is tricky, we add it in 2 steps */
if (add_uevent_var(env, "MODALIAS="))
return -ENOMEM;
- sl = of_device_get_modalias(dev, &env->buf[env->buflen-1],
- sizeof(env->buf) - env->buflen);
+ 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;
@@ -336,3 +272,44 @@ int of_device_uevent_modalias(struct device *dev, struct kobj_uevent_env *env)
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);
diff --git a/drivers/of/dynamic.c b/drivers/of/dynamic.c
index 49b16f76d78e..2eaaddcb0ec4 100644
--- a/drivers/of/dynamic.c
+++ b/drivers/of/dynamic.c
@@ -9,6 +9,8 @@
#define pr_fmt(fmt) "OF: " fmt
+#include <linux/cleanup.h>
+#include <linux/device.h>
#include <linux/of.h>
#include <linux/spinlock.h>
#include <linux/slab.h>
@@ -27,7 +29,7 @@ static struct device_node *kobj_to_device_node(struct kobject *kobj)
* @node: Node to inc refcount, NULL is supported to simplify writing of
* callers
*
- * Returns node.
+ * Return: The node with refcount incremented.
*/
struct device_node *of_node_get(struct device_node *node)
{
@@ -63,37 +65,30 @@ int of_reconfig_notifier_unregister(struct notifier_block *nb)
}
EXPORT_SYMBOL_GPL(of_reconfig_notifier_unregister);
-#ifdef DEBUG
-const char *action_names[] = {
+static const char *action_names[] = {
+ [0] = "INVALID",
[OF_RECONFIG_ATTACH_NODE] = "ATTACH_NODE",
[OF_RECONFIG_DETACH_NODE] = "DETACH_NODE",
[OF_RECONFIG_ADD_PROPERTY] = "ADD_PROPERTY",
[OF_RECONFIG_REMOVE_PROPERTY] = "REMOVE_PROPERTY",
[OF_RECONFIG_UPDATE_PROPERTY] = "UPDATE_PROPERTY",
};
-#endif
+
+#define _do_print(func, prefix, action, node, prop, ...) ({ \
+ func("changeset: " prefix "%-15s %pOF%s%s\n", \
+ ##__VA_ARGS__, action_names[action], node, \
+ prop ? ":" : "", prop ? prop->name : ""); \
+})
+#define of_changeset_action_err(...) _do_print(pr_err, __VA_ARGS__)
+#define of_changeset_action_debug(...) _do_print(pr_debug, __VA_ARGS__)
int of_reconfig_notify(unsigned long action, struct of_reconfig_data *p)
{
int rc;
-#ifdef DEBUG
struct of_reconfig_data *pr = p;
- switch (action) {
- case OF_RECONFIG_ATTACH_NODE:
- case OF_RECONFIG_DETACH_NODE:
- pr_debug("notify %-15s %pOF\n", action_names[action],
- pr->dn);
- break;
- case OF_RECONFIG_ADD_PROPERTY:
- case OF_RECONFIG_REMOVE_PROPERTY:
- case OF_RECONFIG_UPDATE_PROPERTY:
- pr_debug("notify %-15s %pOF:%s\n", action_names[action],
- pr->dn, pr->prop->name);
- break;
+ of_changeset_action_debug("notify: ", action, pr->dn, pr->prop);
- }
-#endif
rc = blocking_notifier_call_chain(&of_reconfig_chain, action, p);
return notifier_to_errno(rc);
}
@@ -104,8 +99,10 @@ int of_reconfig_notify(unsigned long action, struct of_reconfig_data *p)
* @arg - argument of the of notifier
*
* Returns the new state of a device based on the notifier used.
- * Returns 0 on device going from enabled to disabled, 1 on device
- * going from disabled to enabled and -1 on no change.
+ *
+ * Return: OF_RECONFIG_CHANGE_REMOVE on device going from enabled to
+ * disabled, OF_RECONFIG_CHANGE_ADD on device going from disabled to
+ * enabled and OF_RECONFIG_NO_CHANGE on no change.
*/
int of_reconfig_get_state_change(unsigned long action, struct of_reconfig_data *pr)
{
@@ -204,6 +201,9 @@ static void __of_attach_node(struct device_node *np)
{
const __be32 *phandle;
int sz;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&devtree_lock, flags);
if (!of_node_check_flag(np, OF_OVERLAY)) {
np->name = __of_get_property(np, "name", NULL);
@@ -225,25 +225,26 @@ static void __of_attach_node(struct device_node *np)
np->sibling = np->parent->child;
np->parent->child = np;
of_node_clear_flag(np, OF_DETACHED);
+ np->fwnode.flags |= FWNODE_FLAG_NOT_DEVICE;
+
+ raw_spin_unlock_irqrestore(&devtree_lock, flags);
+
+ __of_attach_node_sysfs(np);
}
/**
* of_attach_node() - Plug a device node into the tree and global list.
+ * @np: Pointer to the caller's Device Node
*/
int of_attach_node(struct device_node *np)
{
struct of_reconfig_data rd;
- unsigned long flags;
memset(&rd, 0, sizeof(rd));
rd.dn = np;
mutex_lock(&of_mutex);
- raw_spin_lock_irqsave(&devtree_lock, flags);
__of_attach_node(np);
- raw_spin_unlock_irqrestore(&devtree_lock, flags);
-
- __of_attach_node_sysfs(np);
mutex_unlock(&of_mutex);
of_reconfig_notify(OF_RECONFIG_ATTACH_NODE, &rd);
@@ -254,13 +255,15 @@ int of_attach_node(struct device_node *np)
void __of_detach_node(struct device_node *np)
{
struct device_node *parent;
+ unsigned long flags;
- if (WARN_ON(of_node_check_flag(np, OF_DETACHED)))
- return;
+ raw_spin_lock_irqsave(&devtree_lock, flags);
parent = np->parent;
- if (WARN_ON(!parent))
+ if (WARN_ON(of_node_check_flag(np, OF_DETACHED) || !parent)) {
+ raw_spin_unlock_irqrestore(&devtree_lock, flags);
return;
+ }
if (parent->child == np)
parent->child = np->sibling;
@@ -276,50 +279,54 @@ void __of_detach_node(struct device_node *np)
of_node_set_flag(np, OF_DETACHED);
/* race with of_find_node_by_phandle() prevented by devtree_lock */
- __of_free_phandle_cache_entry(np->phandle);
+ __of_phandle_cache_inv_entry(np->phandle);
+
+ raw_spin_unlock_irqrestore(&devtree_lock, flags);
+
+ __of_detach_node_sysfs(np);
}
/**
* of_detach_node() - "Unplug" a node from the device tree.
+ * @np: Pointer to the caller's Device Node
*/
int of_detach_node(struct device_node *np)
{
struct of_reconfig_data rd;
- unsigned long flags;
- int rc = 0;
memset(&rd, 0, sizeof(rd));
rd.dn = np;
mutex_lock(&of_mutex);
- raw_spin_lock_irqsave(&devtree_lock, flags);
__of_detach_node(np);
- raw_spin_unlock_irqrestore(&devtree_lock, flags);
-
- __of_detach_node_sysfs(np);
mutex_unlock(&of_mutex);
of_reconfig_notify(OF_RECONFIG_DETACH_NODE, &rd);
- return rc;
+ return 0;
}
EXPORT_SYMBOL_GPL(of_detach_node);
+void __of_prop_free(struct property *prop)
+{
+ kfree(prop->name);
+ kfree(prop->value);
+ kfree(prop);
+}
+
static void property_list_free(struct property *prop_list)
{
struct property *prop, *next;
for (prop = prop_list; prop != NULL; prop = next) {
next = prop->next;
- kfree(prop->name);
- kfree(prop->value);
- kfree(prop);
+ __of_prop_free(prop);
}
}
/**
* of_node_release() - release a dynamically allocated node
- * @kref: kref element of the node to be released
+ * @kobj: kernel object of the node to be released
*
* In of_node_put() this function is passed to kref_put() as the destructor.
*/
@@ -327,10 +334,30 @@ void of_node_release(struct kobject *kobj)
{
struct device_node *node = kobj_to_device_node(kobj);
+ /*
+ * can not use '"%pOF", node' in pr_err() calls from this function
+ * because an of_node_get(node) when refcount is already zero
+ * will result in an error and a stack dump
+ */
+
/* We should never be releasing nodes that haven't been detached. */
if (!of_node_check_flag(node, OF_DETACHED)) {
- pr_err("ERROR: Bad of_node_put() on %pOF\n", node);
- dump_stack();
+
+ pr_err("ERROR: %s() detected bad of_node_put() on %pOF/%s\n",
+ __func__, node->parent, node->full_name);
+
+ /*
+ * of unittests will test this path. Do not print the stack
+ * trace when the error is caused by unittest so that we do
+ * not display what a normal developer might reasonably
+ * consider a real bug.
+ */
+ if (!IS_ENABLED(CONFIG_OF_UNITTEST) ||
+ strcmp(node->parent->full_name, "testcase-data")) {
+ dump_stack();
+ pr_err("ERROR: next of_node_put() on this node will result in a kobject warning 'refcount_t: underflow; use-after-free.'\n");
+ }
+
return;
}
if (!of_node_check_flag(node, OF_DYNAMIC))
@@ -355,8 +382,13 @@ void of_node_release(struct kobject *kobj)
__func__, node);
}
+ if (node->child)
+ pr_err("ERROR: %s() unexpected children for %pOF/%s\n",
+ __func__, node->parent, node->full_name);
+
property_list_free(node->properties);
property_list_free(node->deadprops);
+ fwnode_links_purge(of_fwnode_handle(node));
kfree(node->full_name);
kfree(node->data);
@@ -372,7 +404,8 @@ void of_node_release(struct kobject *kobj)
* property structure and the property name & contents. The property's
* flags have the OF_DYNAMIC bit set so that we can differentiate between
* dynamically allocated properties and not.
- * Returns the newly allocated property or NULL on out of memory error.
+ *
+ * Return: The newly allocated property or NULL on out of memory error.
*/
struct property *__of_prop_dup(const struct property *prop, gfp_t allocflags)
{
@@ -400,9 +433,7 @@ struct property *__of_prop_dup(const struct property *prop, gfp_t allocflags)
return new;
err_free:
- kfree(new->name);
- kfree(new->value);
- kfree(new);
+ __of_prop_free(new);
return NULL;
}
@@ -415,7 +446,8 @@ struct property *__of_prop_dup(const struct property *prop, gfp_t allocflags)
* another node. The node data are dynamically allocated and all the node
* flags have the OF_DYNAMIC & OF_DETACHED bits set.
*
- * Returns the newly allocated node or NULL on out of memory error.
+ * Return: The newly allocated node or NULL on out of memory error. Use
+ * of_node_put() on it when done to free the memory allocated for it.
*/
struct device_node *__of_node_dup(const struct device_node *np,
const char *full_name)
@@ -443,9 +475,7 @@ struct device_node *__of_node_dup(const struct device_node *np,
if (!new_pp)
goto err_prop;
if (__of_add_property(node, new_pp)) {
- kfree(new_pp->name);
- kfree(new_pp->value);
- kfree(new_pp);
+ __of_prop_free(new_pp);
goto err_prop;
}
}
@@ -457,6 +487,38 @@ struct device_node *__of_node_dup(const struct device_node *np,
return NULL;
}
+/**
+ * of_changeset_create_node - Dynamically create a device node and attach to
+ * a given changeset.
+ *
+ * @ocs: Pointer to changeset
+ * @parent: Pointer to parent device node
+ * @full_name: Node full name
+ *
+ * Return: Pointer to the created device node or NULL in case of an error.
+ */
+struct device_node *of_changeset_create_node(struct of_changeset *ocs,
+ struct device_node *parent,
+ const char *full_name)
+{
+ struct device_node *np;
+ int ret;
+
+ np = __of_node_dup(NULL, full_name);
+ if (!np)
+ return NULL;
+ np->parent = parent;
+
+ ret = of_changeset_attach_node(ocs, np);
+ if (ret) {
+ of_node_put(np);
+ return NULL;
+ }
+
+ return np;
+}
+EXPORT_SYMBOL(of_changeset_create_node);
+
static void __of_changeset_entry_destroy(struct of_changeset_entry *ce)
{
if (ce->action == OF_RECONFIG_ATTACH_NODE &&
@@ -474,31 +536,7 @@ static void __of_changeset_entry_destroy(struct of_changeset_entry *ce)
kfree(ce);
}
-#ifdef DEBUG
-static void __of_changeset_entry_dump(struct of_changeset_entry *ce)
-{
- switch (ce->action) {
- case OF_RECONFIG_ADD_PROPERTY:
- case OF_RECONFIG_REMOVE_PROPERTY:
- case OF_RECONFIG_UPDATE_PROPERTY:
- pr_debug("cset<%p> %-15s %pOF/%s\n", ce, action_names[ce->action],
- ce->np, ce->prop->name);
- break;
- case OF_RECONFIG_ATTACH_NODE:
- case OF_RECONFIG_DETACH_NODE:
- pr_debug("cset<%p> %-15s %pOF\n", ce, action_names[ce->action],
- ce->np);
- break;
- }
-}
-#else
-static inline void __of_changeset_entry_dump(struct of_changeset_entry *ce)
-{
- /* empty */
-}
-#endif
-
-static void __of_changeset_entry_invert(struct of_changeset_entry *ce,
+static void __of_changeset_entry_invert(const struct of_changeset_entry *ce,
struct of_changeset_entry *rce)
{
memcpy(rce, ce, sizeof(*rce));
@@ -565,13 +603,10 @@ static int __of_changeset_entry_notify(struct of_changeset_entry *ce,
static int __of_changeset_entry_apply(struct of_changeset_entry *ce)
{
- struct property *old_prop, **propp;
- unsigned long flags;
int ret = 0;
- __of_changeset_entry_dump(ce);
+ of_changeset_action_debug("apply: ", ce->action, ce->np, ce->prop);
- raw_spin_lock_irqsave(&devtree_lock, flags);
switch (ce->action) {
case OF_RECONFIG_ATTACH_NODE:
__of_attach_node(ce->np);
@@ -580,82 +615,28 @@ static int __of_changeset_entry_apply(struct of_changeset_entry *ce)
__of_detach_node(ce->np);
break;
case OF_RECONFIG_ADD_PROPERTY:
- /* If the property is in deadprops then it must be removed */
- for (propp = &ce->np->deadprops; *propp; propp = &(*propp)->next) {
- if (*propp == ce->prop) {
- *propp = ce->prop->next;
- ce->prop->next = NULL;
- break;
- }
- }
-
ret = __of_add_property(ce->np, ce->prop);
- if (ret) {
- pr_err("changeset: add_property failed @%pOF/%s\n",
- ce->np,
- ce->prop->name);
- break;
- }
break;
case OF_RECONFIG_REMOVE_PROPERTY:
ret = __of_remove_property(ce->np, ce->prop);
- if (ret) {
- pr_err("changeset: remove_property failed @%pOF/%s\n",
- ce->np,
- ce->prop->name);
- break;
- }
break;
case OF_RECONFIG_UPDATE_PROPERTY:
- /* If the property is in deadprops then it must be removed */
- for (propp = &ce->np->deadprops; *propp; propp = &(*propp)->next) {
- if (*propp == ce->prop) {
- *propp = ce->prop->next;
- ce->prop->next = NULL;
- break;
- }
- }
-
- ret = __of_update_property(ce->np, ce->prop, &old_prop);
- if (ret) {
- pr_err("changeset: update_property failed @%pOF/%s\n",
- ce->np,
- ce->prop->name);
- break;
- }
+ ret = __of_update_property(ce->np, ce->prop, &ce->old_prop);
break;
default:
ret = -EINVAL;
}
- raw_spin_unlock_irqrestore(&devtree_lock, flags);
- if (ret)
+ if (ret) {
+ of_changeset_action_err("apply failed: ", ce->action, ce->np, ce->prop);
return ret;
-
- switch (ce->action) {
- case OF_RECONFIG_ATTACH_NODE:
- __of_attach_node_sysfs(ce->np);
- break;
- case OF_RECONFIG_DETACH_NODE:
- __of_detach_node_sysfs(ce->np);
- break;
- case OF_RECONFIG_ADD_PROPERTY:
- /* ignore duplicate names */
- __of_add_property_sysfs(ce->np, ce->prop);
- break;
- case OF_RECONFIG_REMOVE_PROPERTY:
- __of_remove_property_sysfs(ce->np, ce->prop);
- break;
- case OF_RECONFIG_UPDATE_PROPERTY:
- __of_update_property_sysfs(ce->np, ce->prop, ce->old_prop);
- break;
}
return 0;
}
-static inline int __of_changeset_entry_revert(struct of_changeset_entry *ce)
+static inline int __of_changeset_entry_revert(const struct of_changeset_entry *ce)
{
struct of_changeset_entry ce_inverted;
@@ -689,6 +670,17 @@ void of_changeset_destroy(struct of_changeset *ocs)
{
struct of_changeset_entry *ce, *cen;
+ /*
+ * When a device is deleted, the device links to/from it are also queued
+ * for deletion. Until these device links are freed, the devices
+ * themselves aren't freed. If the device being deleted is due to an
+ * overlay change, this device might be holding a reference to a device
+ * node that will be freed. So, wait until all already pending device
+ * links are deleted before freeing a device node. This ensures we don't
+ * free any device node that has a non-zero reference count.
+ */
+ device_link_wait_removal();
+
list_for_each_entry_safe_reverse(ce, cen, &ocs->entries, node)
__of_changeset_entry_destroy(ce);
}
@@ -781,7 +773,8 @@ static int __of_changeset_apply(struct of_changeset *ocs)
* Any side-effects of live tree state changes are applied here on
* success, like creation/destruction of devices and side-effects
* like creation of sysfs properties and directories.
- * Returns 0 on success, a negative error value in case of an error.
+ *
+ * Return: 0 on success, a negative error value in case of an error.
* On error the partially applied effects are reverted.
*/
int of_changeset_apply(struct of_changeset *ocs)
@@ -875,7 +868,8 @@ static int __of_changeset_revert(struct of_changeset *ocs)
* was before the application.
* Any side-effects like creation/destruction of devices and
* removal of sysfs properties and directories are applied.
- * Returns 0 on success, a negative error value in case of an error.
+ *
+ * Return: 0 on success, a negative error value in case of an error.
*/
int of_changeset_revert(struct of_changeset *ocs)
{
@@ -903,13 +897,17 @@ EXPORT_SYMBOL_GPL(of_changeset_revert);
* + OF_RECONFIG_ADD_PROPERTY
* + OF_RECONFIG_REMOVE_PROPERTY,
* + OF_RECONFIG_UPDATE_PROPERTY
- * Returns 0 on success, a negative error value in case of an error.
+ *
+ * Return: 0 on success, a negative error value in case of an error.
*/
int of_changeset_action(struct of_changeset *ocs, unsigned long action,
struct device_node *np, struct property *prop)
{
struct of_changeset_entry *ce;
+ if (WARN_ON(action >= ARRAY_SIZE(action_names)))
+ return -EINVAL;
+
ce = kzalloc(sizeof(*ce), GFP_KERNEL);
if (!ce)
return -ENOMEM;
@@ -919,11 +917,207 @@ int of_changeset_action(struct of_changeset *ocs, unsigned long action,
ce->np = of_node_get(np);
ce->prop = prop;
- if (action == OF_RECONFIG_UPDATE_PROPERTY && prop)
- ce->old_prop = of_find_property(np, prop->name, NULL);
-
/* add it to the list */
list_add_tail(&ce->node, &ocs->entries);
return 0;
}
EXPORT_SYMBOL_GPL(of_changeset_action);
+
+static int of_changeset_add_prop_helper(struct of_changeset *ocs,
+ struct device_node *np,
+ const struct property *pp)
+{
+ struct property *new_pp;
+ int ret;
+
+ new_pp = __of_prop_dup(pp, GFP_KERNEL);
+ if (!new_pp)
+ return -ENOMEM;
+
+ ret = of_changeset_add_property(ocs, np, new_pp);
+ if (ret) {
+ __of_prop_free(new_pp);
+ return ret;
+ }
+
+ new_pp->next = np->deadprops;
+ np->deadprops = new_pp;
+
+ return 0;
+}
+
+/**
+ * of_changeset_add_prop_string - Add a string property to a changeset
+ *
+ * @ocs: changeset pointer
+ * @np: device node pointer
+ * @prop_name: name of the property to be added
+ * @str: pointer to null terminated string
+ *
+ * Create a string property and add it to a changeset.
+ *
+ * Return: 0 on success, a negative error value in case of an error.
+ */
+int of_changeset_add_prop_string(struct of_changeset *ocs,
+ struct device_node *np,
+ const char *prop_name, const char *str)
+{
+ struct property prop;
+
+ prop.name = (char *)prop_name;
+ prop.length = strlen(str) + 1;
+ prop.value = (void *)str;
+
+ return of_changeset_add_prop_helper(ocs, np, &prop);
+}
+EXPORT_SYMBOL_GPL(of_changeset_add_prop_string);
+
+/**
+ * of_changeset_add_prop_string_array - Add a string list property to
+ * a changeset
+ *
+ * @ocs: changeset pointer
+ * @np: device node pointer
+ * @prop_name: name of the property to be added
+ * @str_array: pointer to an array of null terminated strings
+ * @sz: number of string array elements
+ *
+ * Create a string list property and add it to a changeset.
+ *
+ * Return: 0 on success, a negative error value in case of an error.
+ */
+int of_changeset_add_prop_string_array(struct of_changeset *ocs,
+ struct device_node *np,
+ const char *prop_name,
+ const char * const *str_array, size_t sz)
+{
+ struct property prop;
+ int i, ret;
+ char *vp;
+
+ prop.name = (char *)prop_name;
+
+ prop.length = 0;
+ for (i = 0; i < sz; i++)
+ prop.length += strlen(str_array[i]) + 1;
+
+ prop.value = kmalloc(prop.length, GFP_KERNEL);
+ if (!prop.value)
+ return -ENOMEM;
+
+ vp = prop.value;
+ for (i = 0; i < sz; i++) {
+ vp += snprintf(vp, (char *)prop.value + prop.length - vp, "%s",
+ str_array[i]) + 1;
+ }
+ ret = of_changeset_add_prop_helper(ocs, np, &prop);
+ kfree(prop.value);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(of_changeset_add_prop_string_array);
+
+/**
+ * of_changeset_add_prop_u32_array - Add a property of 32 bit integers
+ * property to a changeset
+ *
+ * @ocs: changeset pointer
+ * @np: device node pointer
+ * @prop_name: name of the property to be added
+ * @array: pointer to an array of 32 bit integers
+ * @sz: number of array elements
+ *
+ * Create a property of 32 bit integers and add it to a changeset.
+ *
+ * Return: 0 on success, a negative error value in case of an error.
+ */
+int of_changeset_add_prop_u32_array(struct of_changeset *ocs,
+ struct device_node *np,
+ const char *prop_name,
+ const u32 *array, size_t sz)
+{
+ struct property prop;
+ __be32 *val __free(kfree) = kcalloc(sz, sizeof(__be32), GFP_KERNEL);
+ int i;
+
+ if (!val)
+ return -ENOMEM;
+
+ for (i = 0; i < sz; i++)
+ val[i] = cpu_to_be32(array[i]);
+ prop.name = (char *)prop_name;
+ prop.length = sizeof(u32) * sz;
+ prop.value = (void *)val;
+
+ return of_changeset_add_prop_helper(ocs, np, &prop);
+}
+EXPORT_SYMBOL_GPL(of_changeset_add_prop_u32_array);
+
+/**
+ * of_changeset_add_prop_bool - Add a boolean property (i.e. a property without
+ * any values) to a changeset.
+ *
+ * @ocs: changeset pointer
+ * @np: device node pointer
+ * @prop_name: name of the property to be added
+ *
+ * Create a boolean property and add it to a changeset.
+ *
+ * Return: 0 on success, a negative error value in case of an error.
+ */
+int of_changeset_add_prop_bool(struct of_changeset *ocs, struct device_node *np,
+ const char *prop_name)
+{
+ struct property prop;
+
+ prop.name = (char *)prop_name;
+ prop.length = 0;
+ prop.value = NULL;
+
+ return of_changeset_add_prop_helper(ocs, np, &prop);
+}
+EXPORT_SYMBOL_GPL(of_changeset_add_prop_bool);
+
+static int of_changeset_update_prop_helper(struct of_changeset *ocs,
+ struct device_node *np,
+ const struct property *pp)
+{
+ struct property *new_pp;
+ int ret;
+
+ new_pp = __of_prop_dup(pp, GFP_KERNEL);
+ if (!new_pp)
+ return -ENOMEM;
+
+ ret = of_changeset_update_property(ocs, np, new_pp);
+ if (ret)
+ __of_prop_free(new_pp);
+
+ return ret;
+}
+
+/**
+ * of_changeset_update_prop_string - Add a string property update to a changeset
+ *
+ * @ocs: changeset pointer
+ * @np: device node pointer
+ * @prop_name: name of the property to be updated
+ * @str: pointer to null terminated string
+ *
+ * Create a string property to be updated and add it to a changeset.
+ *
+ * Return: 0 on success, a negative error value in case of an error.
+ */
+int of_changeset_update_prop_string(struct of_changeset *ocs,
+ struct device_node *np,
+ const char *prop_name, const char *str)
+{
+ struct property prop = {
+ .name = (char *)prop_name,
+ .length = strlen(str) + 1,
+ .value = (void *)str,
+ };
+
+ return of_changeset_update_prop_helper(ocs, np, &prop);
+}
+EXPORT_SYMBOL_GPL(of_changeset_update_prop_string);
diff --git a/drivers/of/empty_root.dts b/drivers/of/empty_root.dts
new file mode 100644
index 000000000000..cbe169ba3db5
--- /dev/null
+++ b/drivers/of/empty_root.dts
@@ -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>;
+};
diff --git a/drivers/of/fdt.c b/drivers/of/fdt.c
index 9cc1461aac7d..d378d4b4109f 100644
--- a/drivers/of/fdt.c
+++ b/drivers/of/fdt.c
@@ -8,6 +8,7 @@
#define pr_fmt(fmt) "OF: fdt: " fmt
+#include <linux/crash_dump.h>
#include <linux/crc32.h>
#include <linux/kernel.h>
#include <linux/initrd.h>
@@ -15,7 +16,6 @@
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_fdt.h>
-#include <linux/of_reserved_mem.h>
#include <linux/sizes.h>
#include <linux/string.h>
#include <linux/errno.h>
@@ -24,6 +24,8 @@
#include <linux/debugfs.h>
#include <linux/serial_core.h>
#include <linux/sysfs.h>
+#include <linux/random.h>
+#include <linux/kexec_handover.h>
#include <asm/setup.h> /* for COMMAND_LINE_SIZE */
#include <asm/page.h>
@@ -31,6 +33,13 @@
#include "of_private.h"
/*
+ * __dtb_empty_root_begin[] and __dtb_empty_root_end[] magically created by
+ * cmd_wrap_S_dtb in scripts/Makefile.dtbs
+ */
+extern uint8_t __dtb_empty_root_begin[];
+extern uint8_t __dtb_empty_root_end[];
+
+/*
* of_fdt_limit_memory - limit the number of regions in the /memory node
* @limit: maximum entries
*
@@ -38,33 +47,12 @@
* memory entries in the /memory node. This function may be called
* any time after initial_boot_param is set.
*/
-void of_fdt_limit_memory(int limit)
+void __init of_fdt_limit_memory(int limit)
{
int memory;
int len;
const void *val;
- int nr_address_cells = OF_ROOT_NODE_ADDR_CELLS_DEFAULT;
- int nr_size_cells = OF_ROOT_NODE_SIZE_CELLS_DEFAULT;
- const __be32 *addr_prop;
- const __be32 *size_prop;
- int root_offset;
- int cell_size;
-
- root_offset = fdt_path_offset(initial_boot_params, "/");
- if (root_offset < 0)
- return;
-
- addr_prop = fdt_getprop(initial_boot_params, root_offset,
- "#address-cells", NULL);
- if (addr_prop)
- nr_address_cells = fdt32_to_cpu(*addr_prop);
-
- size_prop = fdt_getprop(initial_boot_params, root_offset,
- "#size-cells", NULL);
- if (size_prop)
- nr_size_cells = fdt32_to_cpu(*size_prop);
-
- cell_size = sizeof(uint32_t)*(nr_address_cells + nr_size_cells);
+ int cell_size = sizeof(uint32_t)*(dt_root_addr_cells + dt_root_size_cells);
memory = fdt_path_offset(initial_boot_params, "/memory");
if (memory > 0) {
@@ -78,58 +66,7 @@ void of_fdt_limit_memory(int limit)
}
}
-/**
- * of_fdt_is_compatible - Return true if given node from the given blob has
- * compat in its compatible list
- * @blob: A device tree blob
- * @node: node to test
- * @compat: compatible string to compare with compatible list.
- *
- * On match, returns a non-zero value with smaller values returned for more
- * specific compatible values.
- */
-static int of_fdt_is_compatible(const void *blob,
- unsigned long node, const char *compat)
-{
- const char *cp;
- int cplen;
- unsigned long l, score = 0;
-
- cp = fdt_getprop(blob, node, "compatible", &cplen);
- if (cp == NULL)
- return 0;
- while (cplen > 0) {
- score++;
- if (of_compat_cmp(cp, compat, strlen(compat)) == 0)
- return score;
- l = strlen(cp) + 1;
- cp += l;
- cplen -= l;
- }
-
- return 0;
-}
-
-/**
- * of_fdt_is_big_endian - Return true if given node needs BE MMIO accesses
- * @blob: A device tree blob
- * @node: node to test
- *
- * Returns true if the node has a "big-endian" property, or if the kernel
- * was compiled for BE *and* the node has a "native-endian" property.
- * Returns false otherwise.
- */
-bool of_fdt_is_big_endian(const void *blob, unsigned long node)
-{
- if (fdt_getprop(blob, node, "big-endian", NULL))
- return true;
- if (IS_ENABLED(CONFIG_CPU_BIG_ENDIAN) &&
- fdt_getprop(blob, node, "native-endian", NULL))
- return true;
- return false;
-}
-
-static bool of_fdt_device_is_available(const void *blob, unsigned long node)
+bool of_fdt_device_is_available(const void *blob, unsigned long node)
{
const char *status = fdt_getprop(blob, node, "status", NULL);
@@ -142,27 +79,6 @@ static bool of_fdt_device_is_available(const void *blob, unsigned long node)
return false;
}
-/**
- * of_fdt_match - Return true if node matches a list of compatible values
- */
-int of_fdt_match(const void *blob, unsigned long node,
- const char *const *compat)
-{
- unsigned int tmp, score = 0;
-
- if (!compat)
- return 0;
-
- while (*compat) {
- tmp = of_fdt_is_compatible(blob, node, *compat);
- if (tmp && (score == 0 || (tmp < score)))
- score = tmp;
- compat++;
- }
-
- return score;
-}
-
static void *unflatten_dt_alloc(void **mem, unsigned long size,
unsigned long align)
{
@@ -264,19 +180,15 @@ static void populate_properties(const void *blob,
pp->length = len;
pp->value = pp + 1;
*pprev = pp;
- pprev = &pp->next;
memcpy(pp->value, ps, len - 1);
((char *)pp->value)[len - 1] = 0;
pr_debug("fixed up name for %s -> %s\n",
nodename, (char *)pp->value);
}
}
-
- if (!dryrun)
- *pprev = NULL;
}
-static bool populate_node(const void *blob,
+static int populate_node(const void *blob,
int offset,
void **mem,
struct device_node *dad,
@@ -285,24 +197,24 @@ static bool populate_node(const void *blob,
{
struct device_node *np;
const char *pathp;
- unsigned int l, allocl;
+ int len;
- pathp = fdt_get_name(blob, offset, &l);
+ pathp = fdt_get_name(blob, offset, &len);
if (!pathp) {
*pnp = NULL;
- return false;
+ return len;
}
- allocl = ++l;
+ len++;
- np = unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl,
+ np = unflatten_dt_alloc(mem, sizeof(struct device_node) + len,
__alignof__(struct device_node));
if (!dryrun) {
char *fn;
of_node_init(np);
np->full_name = fn = ((char *)np) + sizeof(*np);
- memcpy(fn, pathp, l);
+ memcpy(fn, pathp, len);
if (dad != NULL) {
np->parent = dad;
@@ -319,7 +231,7 @@ static bool populate_node(const void *blob,
}
*pnp = np;
- return true;
+ return 0;
}
static void reverse_nodes(struct device_node *parent)
@@ -353,7 +265,7 @@ static void reverse_nodes(struct device_node *parent)
* @dad: Parent struct device_node
* @nodepp: The device_node tree created by the call
*
- * It returns the size of unflattened device tree or error code
+ * Return: The size of unflattened device tree or error code
*/
static int unflatten_dt_nodes(const void *blob,
void *mem,
@@ -366,6 +278,7 @@ static int unflatten_dt_nodes(const void *blob,
struct device_node *nps[FDT_MAX_DEPTH];
void *base = mem;
bool dryrun = !base;
+ int ret;
if (nodepp)
*nodepp = NULL;
@@ -386,16 +299,17 @@ static int unflatten_dt_nodes(const void *blob,
for (offset = 0;
offset >= 0 && depth >= initial_depth;
offset = fdt_next_node(blob, offset, &depth)) {
- if (WARN_ON_ONCE(depth >= FDT_MAX_DEPTH))
+ if (WARN_ON_ONCE(depth >= FDT_MAX_DEPTH - 1))
continue;
if (!IS_ENABLED(CONFIG_OF_KOBJ) &&
!of_fdt_device_is_available(blob, offset))
continue;
- if (!populate_node(blob, offset, &mem, nps[depth],
- &nps[depth+1], dryrun))
- return mem - base;
+ ret = populate_node(blob, offset, &mem, nps[depth],
+ &nps[depth+1], dryrun);
+ if (ret < 0)
+ return ret;
if (!dryrun && nodepp && !*nodepp)
*nodepp = nps[depth+1];
@@ -420,11 +334,6 @@ static int unflatten_dt_nodes(const void *blob,
/**
* __unflatten_device_tree - create tree of device_nodes from flat blob
- *
- * unflattens a device-tree, creating the
- * tree of struct device_node. It also fills the "name" and "type"
- * pointers of the nodes so the normal device-tree walking functions
- * can be used.
* @blob: The blob to expand
* @dad: Parent device node
* @mynodes: The device_node tree created by the call
@@ -432,7 +341,11 @@ static int unflatten_dt_nodes(const void *blob,
* for the resulting tree
* @detached: if true set OF_DETACHED on @mynodes
*
- * Returns NULL on failure or the memory chunk containing the unflattened
+ * unflattens a device-tree, creating the tree of struct device_node. It also
+ * fills the "name" and "type" pointers of the nodes so the normal device-tree
+ * walking functions can be used.
+ *
+ * Return: NULL on failure or the memory chunk containing the unflattened
* device tree on success.
*/
void *__unflatten_device_tree(const void *blob,
@@ -443,6 +356,10 @@ void *__unflatten_device_tree(const void *blob,
{
int size;
void *mem;
+ int ret;
+
+ if (mynodes)
+ *mynodes = NULL;
pr_debug(" -> unflatten_device_tree()\n");
@@ -463,7 +380,7 @@ void *__unflatten_device_tree(const void *blob,
/* First pass, scan for size */
size = unflatten_dt_nodes(blob, NULL, dad, NULL);
- if (size < 0)
+ if (size <= 0)
return NULL;
size = ALIGN(size, 4);
@@ -481,12 +398,16 @@ void *__unflatten_device_tree(const void *blob,
pr_debug(" unflattening %p...\n", mem);
/* Second pass, do actual unflattening */
- unflatten_dt_nodes(blob, mem, dad, mynodes);
+ ret = unflatten_dt_nodes(blob, mem, dad, mynodes);
+
if (be32_to_cpup(mem + size) != 0xdeadbeef)
- pr_warning("End of tree marker overwritten: %08x\n",
- be32_to_cpup(mem + size));
+ pr_warn("End of tree marker overwritten: %08x\n",
+ be32_to_cpup(mem + size));
+
+ if (ret <= 0)
+ return NULL;
- if (detached && mynodes) {
+ if (detached && mynodes && *mynodes) {
of_node_set_flag(*mynodes, OF_DETACHED);
pr_debug("unflattened tree is detached\n");
}
@@ -513,7 +434,7 @@ static DEFINE_MUTEX(of_fdt_unflatten_mutex);
* pointers of the nodes so the normal device-tree walking functions
* can be used.
*
- * Returns NULL on failure or the memory chunk containing the unflattened
+ * Return: NULL on failure or the memory chunk containing the unflattened
* device tree on success.
*/
void *of_fdt_unflatten_tree(const unsigned long *blob,
@@ -535,115 +456,35 @@ EXPORT_SYMBOL_GPL(of_fdt_unflatten_tree);
int __initdata dt_root_addr_cells;
int __initdata dt_root_size_cells;
-void *initial_boot_params;
+void *initial_boot_params __ro_after_init;
+phys_addr_t initial_boot_params_pa __ro_after_init;
#ifdef CONFIG_OF_EARLY_FLATTREE
static u32 of_fdt_crc32;
-/**
- * res_mem_reserve_reg() - reserve all memory described in 'reg' property
- */
-static int __init __reserved_mem_reserve_reg(unsigned long node,
- const char *uname)
-{
- int t_len = (dt_root_addr_cells + dt_root_size_cells) * sizeof(__be32);
- phys_addr_t base, size;
- int len;
- const __be32 *prop;
- int nomap, first = 1;
-
- prop = of_get_flat_dt_prop(node, "reg", &len);
- if (!prop)
- return -ENOENT;
-
- if (len && len % t_len != 0) {
- pr_err("Reserved memory: invalid reg property in '%s', skipping node.\n",
- uname);
- return -EINVAL;
- }
-
- nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL;
-
- while (len >= t_len) {
- base = dt_mem_next_cell(dt_root_addr_cells, &prop);
- size = dt_mem_next_cell(dt_root_size_cells, &prop);
-
- if (size &&
- early_init_dt_reserve_memory_arch(base, size, nomap) == 0)
- pr_debug("Reserved memory: reserved region for node '%s': base %pa, size %ld MiB\n",
- uname, &base, (unsigned long)size / SZ_1M);
- else
- pr_info("Reserved memory: failed to reserve memory for node '%s': base %pa, size %ld MiB\n",
- uname, &base, (unsigned long)size / SZ_1M);
-
- len -= t_len;
- if (first) {
- fdt_reserved_mem_save_node(node, uname, base, size);
- first = 0;
- }
- }
- 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
+/*
+ * fdt_reserve_elfcorehdr() - reserves memory for elf core header
+ *
+ * This function reserves the memory occupied by an elf core header
+ * described in the device tree. This region contains all the
+ * information about primary kernel's core image and is used by a dump
+ * capture kernel to access the system memory on primary kernel.
*/
-static int __init __reserved_mem_check_root(unsigned long node)
+static void __init fdt_reserve_elfcorehdr(void)
{
- 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;
-}
+ if (!IS_ENABLED(CONFIG_CRASH_DUMP) || !elfcorehdr_size)
+ return;
-/**
- * fdt_scan_reserved_mem() - scan a single FDT node for reserved memory
- */
-static int __init __fdt_scan_reserved_mem(unsigned long node, const char *uname,
- int depth, void *data)
-{
- static int found;
- int err;
-
- if (!found && depth == 1 && strcmp(uname, "reserved-memory") == 0) {
- if (__reserved_mem_check_root(node) != 0) {
- pr_err("Reserved memory: unsupported node format, ignoring\n");
- /* break scan */
- return 1;
- }
- found = 1;
- /* scan next node */
- return 0;
- } else if (!found) {
- /* scan next node */
- return 0;
- } else if (found && depth < 2) {
- /* scanning of /reserved-memory has been finished */
- return 1;
+ if (memblock_is_region_reserved(elfcorehdr_addr, elfcorehdr_size)) {
+ pr_warn("elfcorehdr is overlapped\n");
+ return;
}
- if (!of_fdt_device_is_available(initial_boot_params, node))
- return 0;
+ memblock_reserve(elfcorehdr_addr, elfcorehdr_size);
- err = __reserved_mem_reserve_reg(node, uname);
- if (err == -ENOENT && of_get_flat_dt_prop(node, "size", NULL))
- fdt_reserved_mem_save_node(node, uname, 0, 0);
-
- /* scan next node */
- return 0;
+ pr_info("Reserving %llu KiB of memory at 0x%llx for elfcorehdr\n",
+ elfcorehdr_size >> 10, elfcorehdr_addr);
}
/**
@@ -656,21 +497,26 @@ static int __init __fdt_scan_reserved_mem(unsigned long node, const char *uname,
void __init early_init_fdt_scan_reserved_mem(void)
{
int n;
+ int res;
u64 base, size;
if (!initial_boot_params)
return;
+ fdt_scan_reserved_mem();
+ fdt_reserve_elfcorehdr();
+
/* Process header /memreserve/ fields */
for (n = 0; ; n++) {
- fdt_get_mem_rsv(initial_boot_params, n, &base, &size);
+ res = fdt_get_mem_rsv(initial_boot_params, n, &base, &size);
+ if (res) {
+ pr_err("Invalid memory reservation block index %d\n", n);
+ break;
+ }
if (!size)
break;
- early_init_dt_reserve_memory_arch(base, size, 0);
+ memblock_reserve(base, size);
}
-
- of_scan_flat_dt(__fdt_scan_reserved_mem, NULL);
- fdt_init_reserved_mem();
}
/**
@@ -682,9 +528,8 @@ void __init early_init_fdt_reserve_self(void)
return;
/* Reserve the dtb region */
- early_init_dt_reserve_memory_arch(__pa(initial_boot_params),
- fdt_totalsize(initial_boot_params),
- 0);
+ memblock_reserve(__pa(initial_boot_params),
+ fdt_totalsize(initial_boot_params));
}
/**
@@ -713,8 +558,6 @@ int __init of_scan_flat_dt(int (*it)(unsigned long node,
offset = fdt_next_node(blob, offset, &depth)) {
pathp = fdt_get_name(blob, offset, NULL);
- if (*pathp == '/')
- pathp = kbasename(pathp);
rc = it(offset, pathp, depth, data);
}
return rc;
@@ -722,6 +565,7 @@ int __init of_scan_flat_dt(int (*it)(unsigned long node,
/**
* of_scan_flat_dt_subnodes - scan sub-nodes of a node call callback on each.
+ * @parent: parent node
* @it: callback function
* @data: context data pointer
*
@@ -741,8 +585,6 @@ int __init of_scan_flat_dt_subnodes(unsigned long parent,
int rc;
pathp = fdt_get_name(blob, node, NULL);
- if (*pathp == '/')
- pathp = kbasename(pathp);
rc = it(node, pathp, data);
if (rc)
return rc;
@@ -758,12 +600,12 @@ int __init of_scan_flat_dt_subnodes(unsigned long parent,
* @return offset of the subnode, or -FDT_ERR_NOTFOUND if there is none
*/
-int of_get_flat_dt_subnode_by_name(unsigned long node, const char *uname)
+int __init of_get_flat_dt_subnode_by_name(unsigned long node, const char *uname)
{
return fdt_subnode_offset(initial_boot_params, node, uname);
}
-/**
+/*
* of_get_flat_dt_root - find the root node in the flat blob
*/
unsigned long __init of_get_flat_dt_root(void)
@@ -771,15 +613,7 @@ unsigned long __init of_get_flat_dt_root(void)
return 0;
}
-/**
- * of_get_flat_dt_size - Return the total size of the FDT
- */
-int __init of_get_flat_dt_size(void)
-{
- return fdt_totalsize(initial_boot_params);
-}
-
-/**
+/*
* of_get_flat_dt_prop - Given a node in the flat blob, return the property ptr
*
* This function can be used within scan_flattened_dt callback to get
@@ -791,6 +625,79 @@ const void *__init of_get_flat_dt_prop(unsigned long node, const char *name,
return fdt_getprop(initial_boot_params, node, name, size);
}
+const __be32 *__init of_flat_dt_get_addr_size_prop(unsigned long node,
+ const char *name,
+ int *entries)
+{
+ const __be32 *prop;
+ int len, elen = (dt_root_addr_cells + dt_root_size_cells) * sizeof(__be32);
+
+ prop = of_get_flat_dt_prop(node, name, &len);
+ if (!prop || len % elen) {
+ *entries = 0;
+ return NULL;
+ }
+
+ *entries = len / elen;
+ return prop;
+}
+
+bool __init of_flat_dt_get_addr_size(unsigned long node, const char *name,
+ u64 *addr, u64 *size)
+{
+ const __be32 *prop;
+ int entries;
+
+ prop = of_flat_dt_get_addr_size_prop(node, name, &entries);
+ if (!prop || entries != 1)
+ return false;
+
+ of_flat_dt_read_addr_size(prop, 0, addr, size);
+ return true;
+}
+
+void __init of_flat_dt_read_addr_size(const __be32 *prop, int entry_index,
+ u64 *addr, u64 *size)
+{
+ int entry_cells = dt_root_addr_cells + dt_root_size_cells;
+ prop += entry_cells * entry_index;
+
+ *addr = dt_mem_next_cell(dt_root_addr_cells, &prop);
+ *size = dt_mem_next_cell(dt_root_size_cells, &prop);
+}
+
+/**
+ * of_fdt_is_compatible - Return true if given node from the given blob has
+ * compat in its compatible list
+ * @blob: A device tree blob
+ * @node: node to test
+ * @compat: compatible string to compare with compatible list.
+ *
+ * Return: a non-zero value on match with smaller values returned for more
+ * specific compatible values.
+ */
+static int of_fdt_is_compatible(const void *blob,
+ unsigned long node, const char *compat)
+{
+ const char *cp;
+ int cplen;
+ unsigned long l, score = 0;
+
+ cp = fdt_getprop(blob, node, "compatible", &cplen);
+ if (cp == NULL)
+ return 0;
+ while (cplen > 0) {
+ score++;
+ if (of_compat_cmp(cp, compat, strlen(compat)) == 0)
+ return score;
+ l = strlen(cp) + 1;
+ cp += l;
+ cplen -= l;
+ }
+
+ return 0;
+}
+
/**
* of_flat_dt_is_compatible - Return true if given node has compat in compatible list
* @node: node to test
@@ -801,31 +708,34 @@ int __init of_flat_dt_is_compatible(unsigned long node, const char *compat)
return of_fdt_is_compatible(initial_boot_params, node, compat);
}
-/**
+/*
* of_flat_dt_match - Return true if node matches a list of compatible values
*/
-int __init of_flat_dt_match(unsigned long node, const char *const *compat)
+static int __init of_flat_dt_match(unsigned long node, const char *const *compat)
{
- return of_fdt_match(initial_boot_params, node, compat);
+ unsigned int tmp, score = 0;
+
+ if (!compat)
+ return 0;
+
+ while (*compat) {
+ tmp = of_fdt_is_compatible(initial_boot_params, node, *compat);
+ if (tmp && (score == 0 || (tmp < score)))
+ score = tmp;
+ compat++;
+ }
+
+ return score;
}
-/**
- * of_get_flat_dt_prop - Given a node in the flat blob, return the phandle
+/*
+ * of_get_flat_dt_phandle - Given a node in the flat blob, return the phandle
*/
uint32_t __init of_get_flat_dt_phandle(unsigned long node)
{
return fdt_get_phandle(initial_boot_params, node);
}
-struct fdt_scan_status {
- const char *name;
- int namelen;
- int depth;
- int found;
- int (*iterator)(unsigned long node, const char *uname, int depth, void *data);
- void *data;
-};
-
const char * __init of_flat_dt_get_machine_name(void)
{
const char *name;
@@ -886,16 +796,16 @@ const void * __init of_flat_dt_match_machine(const void *default_match,
return best_data;
}
-#ifdef CONFIG_BLK_DEV_INITRD
static void __early_init_dt_declare_initrd(unsigned long start,
unsigned long end)
{
- /* ARM64 would cause a BUG to occur here when CONFIG_DEBUG_VM is
- * enabled since __va() is called too early. ARM64 does make use
- * of phys_initrd_start/phys_initrd_size so we can skip this
- * conversion.
+ /*
+ * __va() is not yet available this early on some platforms. In that
+ * case, the platform uses phys_initrd_start/phys_initrd_size instead
+ * and does the VA conversion itself.
*/
- if (!IS_ENABLED(CONFIG_ARM64)) {
+ if (!IS_ENABLED(CONFIG_ARM64) &&
+ !(IS_ENABLED(CONFIG_RISCV) && IS_ENABLED(CONFIG_64BIT))) {
initrd_start = (unsigned long)__va(start);
initrd_end = (unsigned long)__va(end);
initrd_below_start_ok = 1;
@@ -912,6 +822,9 @@ static void __init early_init_dt_check_for_initrd(unsigned long node)
int len;
const __be32 *prop;
+ if (!IS_ENABLED(CONFIG_BLK_DEV_INITRD))
+ return;
+
pr_debug("Looking for initrd properties... ");
prop = of_get_flat_dt_prop(node, "linux,initrd-start", &len);
@@ -923,19 +836,106 @@ static void __init early_init_dt_check_for_initrd(unsigned long node)
if (!prop)
return;
end = of_read_number(prop, len/4);
+ if (start > end)
+ return;
__early_init_dt_declare_initrd(start, end);
phys_initrd_start = start;
phys_initrd_size = end - start;
- pr_debug("initrd_start=0x%llx initrd_end=0x%llx\n",
- (unsigned long long)start, (unsigned long long)end);
+ pr_debug("initrd_start=0x%llx initrd_end=0x%llx\n", start, end);
}
-#else
-static inline void early_init_dt_check_for_initrd(unsigned long node)
+
+/**
+ * early_init_dt_check_for_elfcorehdr - Decode elfcorehdr location from flat
+ * tree
+ * @node: reference to node containing elfcorehdr location ('chosen')
+ */
+static void __init early_init_dt_check_for_elfcorehdr(unsigned long node)
{
+ if (!IS_ENABLED(CONFIG_CRASH_DUMP))
+ return;
+
+ pr_debug("Looking for elfcorehdr property... ");
+
+ if (!of_flat_dt_get_addr_size(node, "linux,elfcorehdr",
+ &elfcorehdr_addr, &elfcorehdr_size))
+ return;
+
+ pr_debug("elfcorehdr_start=0x%llx elfcorehdr_size=0x%llx\n",
+ elfcorehdr_addr, elfcorehdr_size);
+}
+
+static unsigned long chosen_node_offset = -FDT_ERR_NOTFOUND;
+
+/*
+ * The main usage of linux,usable-memory-range is for crash dump kernel.
+ * Originally, the number of usable-memory regions is one. Now there may
+ * be two regions, low region and high region.
+ * To make compatibility with existing user-space and older kdump, the low
+ * region is always the last range of linux,usable-memory-range if exist.
+ */
+#define MAX_USABLE_RANGES 2
+
+/**
+ * early_init_dt_check_for_usable_mem_range - Decode usable memory range
+ * location from flat tree
+ */
+void __init early_init_dt_check_for_usable_mem_range(void)
+{
+ struct memblock_region rgn[MAX_USABLE_RANGES] = {0};
+ const __be32 *prop;
+ int len, i;
+ u64 base, size;
+ unsigned long node = chosen_node_offset;
+
+ if ((long)node < 0)
+ return;
+
+ pr_debug("Looking for usable-memory-range property... ");
+
+ prop = of_flat_dt_get_addr_size_prop(node, "linux,usable-memory-range",
+ &len);
+ if (!prop)
+ return;
+
+ len = min(len, MAX_USABLE_RANGES);
+
+ for (i = 0; i < len; i++) {
+ of_flat_dt_read_addr_size(prop, i, &base, &size);
+ rgn[i].base = base;
+ rgn[i].size = size;
+
+ pr_debug("cap_mem_regions[%d]: base=%pa, size=%pa\n",
+ i, &rgn[i].base, &rgn[i].size);
+ }
+
+ memblock_cap_memory_range(rgn[0].base, rgn[0].size);
+ for (i = 1; i < MAX_USABLE_RANGES && rgn[i].size; i++)
+ memblock_add(rgn[i].base, rgn[i].size);
+}
+
+/**
+ * early_init_dt_check_kho - Decode info required for kexec handover from DT
+ */
+static void __init early_init_dt_check_kho(void)
+{
+ unsigned long node = chosen_node_offset;
+ u64 fdt_start, fdt_size, scratch_start, scratch_size;
+
+ if (!IS_ENABLED(CONFIG_KEXEC_HANDOVER) || (long)node < 0)
+ return;
+
+ if (!of_flat_dt_get_addr_size(node, "linux,kho-fdt",
+ &fdt_start, &fdt_size))
+ return;
+
+ if (!of_flat_dt_get_addr_size(node, "linux,kho-scratch",
+ &scratch_start, &scratch_size))
+ return;
+
+ kho_populate(fdt_start, fdt_size, scratch_start, scratch_size);
}
-#endif /* CONFIG_BLK_DEV_INITRD */
#ifdef CONFIG_SERIAL_EARLYCON
@@ -944,8 +944,9 @@ int __init early_init_dt_scan_chosen_stdout(void)
int offset;
const char *p, *q, *options = NULL;
int l;
- const struct earlycon_id **p_match;
+ const struct earlycon_id *match;
const void *fdt = initial_boot_params;
+ int ret;
offset = fdt_path_offset(fdt, "/chosen");
if (offset < 0)
@@ -971,49 +972,47 @@ int __init early_init_dt_scan_chosen_stdout(void)
return 0;
}
- for (p_match = __earlycon_table; p_match < __earlycon_table_end;
- p_match++) {
- const struct earlycon_id *match = *p_match;
-
+ for (match = __earlycon_table; match < __earlycon_table_end; match++) {
if (!match->compatible[0])
continue;
if (fdt_node_check_compatible(fdt, offset, match->compatible))
continue;
- of_setup_earlycon(match, offset, options);
- return 0;
+ ret = of_setup_earlycon(match, offset, options);
+ if (!ret || ret == -EALREADY)
+ return 0;
}
return -ENODEV;
}
#endif
-/**
+/*
* early_init_dt_scan_root - fetch the top level address and size cells
*/
-int __init early_init_dt_scan_root(unsigned long node, const char *uname,
- int depth, void *data)
+int __init early_init_dt_scan_root(void)
{
const __be32 *prop;
+ const void *fdt = initial_boot_params;
+ int node = fdt_path_offset(fdt, "/");
- if (depth != 0)
- return 0;
+ if (node < 0)
+ return -ENODEV;
dt_root_size_cells = OF_ROOT_NODE_SIZE_CELLS_DEFAULT;
dt_root_addr_cells = OF_ROOT_NODE_ADDR_CELLS_DEFAULT;
prop = of_get_flat_dt_prop(node, "#size-cells", NULL);
- if (prop)
+ if (!WARN(!prop, "No '#size-cells' in root node\n"))
dt_root_size_cells = be32_to_cpup(prop);
pr_debug("dt_root_size_cells = %x\n", dt_root_size_cells);
prop = of_get_flat_dt_prop(node, "#address-cells", NULL);
- if (prop)
+ if (!WARN(!prop, "No '#address-cells' in root node\n"))
dt_root_addr_cells = be32_to_cpup(prop);
pr_debug("dt_root_addr_cells = %x\n", dt_root_addr_cells);
- /* break now */
- return 1;
+ return 0;
}
u64 __init dt_mem_next_cell(int s, const __be32 **cellp)
@@ -1024,75 +1023,99 @@ u64 __init dt_mem_next_cell(int s, const __be32 **cellp)
return of_read_number(p, s);
}
-/**
+/*
* early_init_dt_scan_memory - Look for and parse memory nodes
*/
-int __init early_init_dt_scan_memory(unsigned long node, const char *uname,
- int depth, void *data)
+int __init early_init_dt_scan_memory(void)
{
- const char *type = of_get_flat_dt_prop(node, "device_type", NULL);
- const __be32 *reg, *endp;
- int l;
- bool hotpluggable;
+ int node, found_memory = 0;
+ const void *fdt = initial_boot_params;
- /* We are scanning "memory" nodes only */
- if (type == NULL || strcmp(type, "memory") != 0)
- return 0;
+ fdt_for_each_subnode(node, fdt, 0) {
+ const char *type = of_get_flat_dt_prop(node, "device_type", NULL);
+ const __be32 *reg;
+ int i, l;
+ bool hotpluggable;
- reg = of_get_flat_dt_prop(node, "linux,usable-memory", &l);
- if (reg == NULL)
- reg = of_get_flat_dt_prop(node, "reg", &l);
- if (reg == NULL)
- return 0;
+ /* We are scanning "memory" nodes only */
+ if (type == NULL || strcmp(type, "memory") != 0)
+ continue;
- endp = reg + (l / sizeof(__be32));
- hotpluggable = of_get_flat_dt_prop(node, "hotpluggable", NULL);
+ if (!of_fdt_device_is_available(fdt, node))
+ continue;
- pr_debug("memory scan node %s, reg size %d,\n", uname, l);
+ reg = of_flat_dt_get_addr_size_prop(node, "linux,usable-memory", &l);
+ if (reg == NULL)
+ reg = of_flat_dt_get_addr_size_prop(node, "reg", &l);
+ if (reg == NULL)
+ continue;
- while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) {
- u64 base, size;
+ hotpluggable = of_get_flat_dt_prop(node, "hotpluggable", NULL);
- base = dt_mem_next_cell(dt_root_addr_cells, &reg);
- size = dt_mem_next_cell(dt_root_size_cells, &reg);
+ pr_debug("memory scan node %s, reg {addr,size} entries %d,\n",
+ fdt_get_name(fdt, node, NULL), l);
- if (size == 0)
- continue;
- pr_debug(" - %llx , %llx\n", (unsigned long long)base,
- (unsigned long long)size);
+ for (i = 0; i < l; i++) {
+ u64 base, size;
- early_init_dt_add_memory_arch(base, size);
+ of_flat_dt_read_addr_size(reg, i, &base, &size);
- if (!hotpluggable)
- continue;
+ if (size == 0)
+ continue;
+ pr_debug(" - %llx, %llx\n", base, size);
- if (early_init_dt_mark_hotplug_memory_arch(base, size))
- pr_warn("failed to mark hotplug range 0x%llx - 0x%llx\n",
- base, base + size);
- }
+ early_init_dt_add_memory_arch(base, size);
- return 0;
+ found_memory = 1;
+
+ if (!hotpluggable)
+ continue;
+
+ if (memblock_mark_hotplug(base, size))
+ pr_warn("failed to mark hotplug range 0x%llx - 0x%llx\n",
+ base, base + size);
+ }
+ }
+ return found_memory;
}
-int __init early_init_dt_scan_chosen(unsigned long node, const char *uname,
- int depth, void *data)
+int __init early_init_dt_scan_chosen(char *cmdline)
{
- int l;
+ int l, node;
const char *p;
+ const void *rng_seed;
+ const void *fdt = initial_boot_params;
- pr_debug("search \"chosen\", depth: %d, uname: %s\n", depth, uname);
+ node = fdt_path_offset(fdt, "/chosen");
+ if (node < 0)
+ node = fdt_path_offset(fdt, "/chosen@0");
+ if (node < 0)
+ /* Handle the cmdline config options even if no /chosen node */
+ goto handle_cmdline;
- if (depth != 1 || !data ||
- (strcmp(uname, "chosen") != 0 && strcmp(uname, "chosen@0") != 0))
- return 0;
+ chosen_node_offset = node;
early_init_dt_check_for_initrd(node);
+ early_init_dt_check_for_elfcorehdr(node);
+
+ rng_seed = of_get_flat_dt_prop(node, "rng-seed", &l);
+ if (rng_seed && l > 0) {
+ add_bootloader_randomness(rng_seed, l);
+
+ /* try to clear seed so it won't be found. */
+ fdt_nop_property(initial_boot_params, node, "rng-seed");
+
+ /* update CRC check value */
+ of_fdt_crc32 = crc32_be(~0, initial_boot_params,
+ fdt_totalsize(initial_boot_params));
+ }
/* Retrieve command line */
p = of_get_flat_dt_prop(node, "bootargs", &l);
if (p != NULL && l > 0)
- strlcpy(data, p, min((int)l, COMMAND_LINE_SIZE));
+ strscpy(cmdline, p, min(l, COMMAND_LINE_SIZE));
+handle_cmdline:
/*
* CONFIG_CMDLINE is meant to be a default in case nothing else
* managed to set the command line, unless CONFIG_CMDLINE_FORCE
@@ -1100,21 +1123,20 @@ int __init early_init_dt_scan_chosen(unsigned long node, const char *uname,
*/
#ifdef CONFIG_CMDLINE
#if defined(CONFIG_CMDLINE_EXTEND)
- strlcat(data, " ", COMMAND_LINE_SIZE);
- strlcat(data, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
+ strlcat(cmdline, " ", COMMAND_LINE_SIZE);
+ strlcat(cmdline, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
#elif defined(CONFIG_CMDLINE_FORCE)
- strlcpy(data, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
+ strscpy(cmdline, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
#else
/* No arguments from boot loader, use kernel's cmdl*/
- if (!((char *)data)[0])
- strlcpy(data, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
+ if (!((char *)cmdline)[0])
+ strscpy(cmdline, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
#endif
#endif /* CONFIG_CMDLINE */
- pr_debug("Command line is: %s\n", (char*)data);
+ pr_debug("Command line is: %s\n", (char *)cmdline);
- /* break now */
- return 1;
+ return 0;
}
#ifndef MIN_MEMBLOCK_ADDR
@@ -1141,87 +1163,82 @@ void __init __weak early_init_dt_add_memory_arch(u64 base, u64 size)
size &= PAGE_MASK;
if (base > MAX_MEMBLOCK_ADDR) {
- pr_warning("Ignoring memory block 0x%llx - 0x%llx\n",
- base, base + size);
+ pr_warn("Ignoring memory block 0x%llx - 0x%llx\n",
+ base, base + size);
return;
}
if (base + size - 1 > MAX_MEMBLOCK_ADDR) {
- pr_warning("Ignoring memory range 0x%llx - 0x%llx\n",
- ((u64)MAX_MEMBLOCK_ADDR) + 1, base + size);
+ pr_warn("Ignoring memory range 0x%llx - 0x%llx\n",
+ ((u64)MAX_MEMBLOCK_ADDR) + 1, base + size);
size = MAX_MEMBLOCK_ADDR - base + 1;
}
if (base + size < phys_offset) {
- pr_warning("Ignoring memory block 0x%llx - 0x%llx\n",
- base, base + size);
+ pr_warn("Ignoring memory block 0x%llx - 0x%llx\n",
+ base, base + size);
return;
}
if (base < phys_offset) {
- pr_warning("Ignoring memory range 0x%llx - 0x%llx\n",
- base, phys_offset);
+ pr_warn("Ignoring memory range 0x%llx - 0x%llx\n",
+ base, phys_offset);
size -= phys_offset - base;
base = phys_offset;
}
memblock_add(base, size);
}
-int __init __weak early_init_dt_mark_hotplug_memory_arch(u64 base, u64 size)
-{
- return memblock_mark_hotplug(base, size);
-}
-
-int __init __weak early_init_dt_reserve_memory_arch(phys_addr_t base,
- phys_addr_t size, bool nomap)
-{
- if (nomap)
- return memblock_remove(base, size);
- return memblock_reserve(base, size);
-}
-
static void * __init early_init_dt_alloc_memory_arch(u64 size, u64 align)
{
- return memblock_alloc(size, align);
+ return memblock_alloc_or_panic(size, align);
}
-bool __init early_init_dt_verify(void *params)
+bool __init early_init_dt_verify(void *dt_virt, phys_addr_t dt_phys)
{
- if (!params)
+ if (!dt_virt)
return false;
/* check device tree validity */
- if (fdt_check_header(params))
+ if (fdt_check_header(dt_virt))
return false;
/* Setup flat device-tree pointer */
- initial_boot_params = params;
+ initial_boot_params = dt_virt;
+ initial_boot_params_pa = dt_phys;
of_fdt_crc32 = crc32_be(~0, initial_boot_params,
fdt_totalsize(initial_boot_params));
+
+ /* Initialize {size,address}-cells info */
+ early_init_dt_scan_root();
+
return true;
}
void __init early_init_dt_scan_nodes(void)
{
- int rc = 0;
+ int rc;
/* Retrieve various information from the /chosen node */
- rc = of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
- if (!rc)
+ rc = early_init_dt_scan_chosen(boot_command_line);
+ if (rc)
pr_warn("No chosen node found, continuing without\n");
- /* Initialize {size,address}-cells info */
- of_scan_flat_dt(early_init_dt_scan_root, NULL);
-
/* Setup memory, calling early_init_dt_add_memory_arch */
- of_scan_flat_dt(early_init_dt_scan_memory, NULL);
+ early_init_dt_scan_memory();
+
+ /* Handle linux,usable-memory-range property */
+ early_init_dt_check_for_usable_mem_range();
+
+ /* Handle kexec handover */
+ early_init_dt_check_kho();
}
-bool __init early_init_dt_scan(void *params)
+bool __init early_init_dt_scan(void *dt_virt, phys_addr_t dt_phys)
{
bool status;
- status = early_init_dt_verify(params);
+ status = early_init_dt_verify(dt_virt, dt_phys);
if (!status)
return false;
@@ -1229,6 +1246,21 @@ bool __init early_init_dt_scan(void *params)
return true;
}
+static void *__init copy_device_tree(void *fdt)
+{
+ int size;
+ void *dt;
+
+ size = fdt_totalsize(fdt);
+ dt = early_init_dt_alloc_memory_arch(size,
+ roundup_pow_of_two(FDT_V17_SIZE));
+
+ if (dt)
+ memcpy(dt, fdt, size);
+
+ return dt;
+}
+
/**
* unflatten_device_tree - create tree of device_nodes from flat blob
*
@@ -1239,7 +1271,25 @@ bool __init early_init_dt_scan(void *params)
*/
void __init unflatten_device_tree(void)
{
- __unflatten_device_tree(initial_boot_params, NULL, &of_root,
+ void *fdt = initial_boot_params;
+
+ /* Save the statically-placed regions in the reserved_mem array */
+ fdt_scan_reserved_mem_reg_nodes();
+
+ /* Populate an empty root node when bootloader doesn't provide one */
+ if (!fdt) {
+ fdt = (void *) __dtb_empty_root_begin;
+ /* fdt_totalsize() will be used for copy size */
+ if (fdt_totalsize(fdt) >
+ __dtb_empty_root_end - __dtb_empty_root_begin) {
+ pr_err("invalid size in dtb_empty_root\n");
+ return;
+ }
+ of_fdt_crc32 = crc32_be(~0, fdt, fdt_totalsize(fdt));
+ fdt = copy_device_tree(fdt);
+ }
+
+ __unflatten_device_tree(fdt, NULL, &of_root,
early_init_dt_alloc_memory_arch, false);
/* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */
@@ -1261,38 +1311,16 @@ void __init unflatten_device_tree(void)
*/
void __init unflatten_and_copy_device_tree(void)
{
- int size;
- void *dt;
+ if (initial_boot_params)
+ initial_boot_params = copy_device_tree(initial_boot_params);
- if (!initial_boot_params) {
- pr_warn("No valid device tree found, continuing without\n");
- return;
- }
-
- size = fdt_totalsize(initial_boot_params);
- dt = early_init_dt_alloc_memory_arch(size,
- roundup_pow_of_two(FDT_V17_SIZE));
-
- if (dt) {
- memcpy(dt, initial_boot_params, size);
- initial_boot_params = dt;
- }
unflatten_device_tree();
}
#ifdef CONFIG_SYSFS
-static ssize_t of_fdt_raw_read(struct file *filp, struct kobject *kobj,
- struct bin_attribute *bin_attr,
- char *buf, loff_t off, size_t count)
-{
- memcpy(buf, initial_boot_params + off, count);
- return count;
-}
-
static int __init of_fdt_raw_init(void)
{
- static struct bin_attribute of_fdt_raw_attr =
- __BIN_ATTR(fdt, S_IRUSR, of_fdt_raw_read, NULL, 0);
+ static __ro_after_init BIN_ATTR_SIMPLE_ADMIN_RO(fdt);
if (!initial_boot_params)
return 0;
@@ -1302,8 +1330,9 @@ static int __init of_fdt_raw_init(void)
pr_warn("not creating '/sys/firmware/fdt': CRC check failed\n");
return 0;
}
- of_fdt_raw_attr.size = fdt_totalsize(initial_boot_params);
- return sysfs_create_bin_file(firmware_kobj, &of_fdt_raw_attr);
+ bin_attr_fdt.private = initial_boot_params;
+ bin_attr_fdt.size = fdt_totalsize(initial_boot_params);
+ return sysfs_create_bin_file(firmware_kobj, &bin_attr_fdt);
}
late_initcall(of_fdt_raw_init);
#endif
diff --git a/drivers/of/fdt_address.c b/drivers/of/fdt_address.c
index 1dc15ab78b10..f358d2c80754 100644
--- a/drivers/of/fdt_address.c
+++ b/drivers/of/fdt_address.c
@@ -17,23 +17,10 @@
#include <linux/of_fdt.h>
#include <linux/sizes.h>
-/* Max address size we deal with */
-#define OF_MAX_ADDR_CELLS 4
-#define OF_CHECK_COUNTS(na, ns) ((na) > 0 && (na) <= OF_MAX_ADDR_CELLS && \
- (ns) > 0)
-
-/* Debug utility */
-#ifdef DEBUG
-static void __init of_dump_addr(const char *s, const __be32 *addr, int na)
-{
- pr_debug("%s", s);
- while(na--)
- pr_cont(" %08x", *(addr++));
- pr_cont("\n");
-}
-#else
-static void __init of_dump_addr(const char *s, const __be32 *addr, int na) { }
-#endif
+/* Uncomment me to enable of_dump_addr() debugging output */
+// #define DEBUG
+
+#include "of_private.h"
/* Callbacks for bus specific translators */
struct of_bus {
@@ -55,7 +42,7 @@ static void __init fdt_bus_default_count_cells(const void *blob, int parentoffse
if (prop)
*addrc = be32_to_cpup(prop);
else
- *addrc = dt_root_addr_cells;
+ *addrc = -1;
}
if (sizec) {
@@ -63,7 +50,7 @@ static void __init fdt_bus_default_count_cells(const void *blob, int parentoffse
if (prop)
*sizec = be32_to_cpup(prop);
else
- *sizec = dt_root_size_cells;
+ *sizec = -1;
}
}
diff --git a/drivers/of/irq.c b/drivers/of/irq.c
index e1f6f392a4c0..e3816819dbfe 100644
--- a/drivers/of/irq.c
+++ b/drivers/of/irq.c
@@ -16,6 +16,7 @@
#define pr_fmt(fmt) "OF: " fmt
+#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/list.h>
@@ -25,6 +26,8 @@
#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
@@ -36,11 +39,15 @@
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;
- return irq_create_of_mapping(&oirq);
+ ret = irq_create_of_mapping(&oirq);
+ of_node_put(oirq.np);
+
+ return ret;
}
EXPORT_SYMBOL_GPL(irq_of_parse_and_map);
@@ -48,8 +55,8 @@ 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
*
- * Returns a pointer to the interrupt parent node, or NULL if the interrupt
- * parent could not be determined.
+ * 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)
{
@@ -76,28 +83,104 @@ struct device_node *of_irq_find_parent(struct device_node *child)
}
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;
+}
+
/**
* 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
*
- * Returns 0 on success and a negative number on error
- *
* This function is a low-level interrupt tree walking function. It
- * can be used to do a partial walk with synthetized reg and interrupts
+ * 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, *newpar = NULL;
+ struct device_node *ipar, *tnode, *old = NULL;
__be32 initial_match_array[MAX_PHANDLE_ARGS];
const __be32 *match_array = initial_match_array;
- const __be32 *tmp, *imap, *imask, dummy_imask[] = { [0 ... MAX_PHANDLE_ARGS] = cpu_to_be32(~0) };
- u32 intsize = 1, addrsize, newintsize = 0, newaddrsize = 0;
- int imaplen, match, i, rc = -EINVAL;
+ 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);
@@ -156,10 +239,21 @@ int of_irq_parse_raw(const __be32 *addr, struct of_phandle_args *out_irq)
/* Now start the actual "proper" walk of the interrupt tree */
while (ipar != NULL) {
- /* Now check if cursor is an interrupt-controller and if it is
- * then we are done
+ 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.
*/
- if (of_property_read_bool(ipar, "interrupt-controller")) {
+ 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;
}
@@ -173,8 +267,6 @@ int of_irq_parse_raw(const __be32 *addr, struct of_phandle_args *out_irq)
goto fail;
}
- /* Now look for an interrupt-map */
- imap = of_get_property(ipar, "interrupt-map", &imaplen);
/* No interrupt map, check for an interrupt parent */
if (imap == NULL) {
pr_debug(" -> no map, getting parent\n");
@@ -190,7 +282,7 @@ int of_irq_parse_raw(const __be32 *addr, struct of_phandle_args *out_irq)
/* Parse interrupt-map */
match = 0;
- while (imaplen > (addrsize + intsize + 1) && !match) {
+ while (imaplen > (addrsize + intsize + 1)) {
/* Compare specifiers */
match = 1;
for (i = 0; i < (addrsize + intsize); i++, imaplen--)
@@ -198,66 +290,45 @@ int of_irq_parse_raw(const __be32 *addr, struct of_phandle_args *out_irq)
pr_debug(" -> match=%d (imaplen=%d)\n", match, imaplen);
- /* Get the interrupt parent */
- if (of_irq_workarounds & OF_IMAP_NO_PHANDLE)
- newpar = of_node_get(of_irq_dflt_pic);
- else
- newpar = of_find_node_by_phandle(be32_to_cpup(imap));
- imap++;
- --imaplen;
-
- /* Check if not found */
- if (newpar == NULL) {
- pr_debug(" -> imap parent not found !\n");
- goto fail;
- }
-
- if (!of_device_is_available(newpar))
- match = 0;
-
- /* Get #interrupt-cells and #address-cells of new
- * parent
- */
- if (of_property_read_u32(newpar, "#interrupt-cells",
- &newintsize)) {
- pr_debug(" -> parent lacks #interrupt-cells!\n");
- goto fail;
- }
- if (of_property_read_u32(newpar, "#address-cells",
- &newaddrsize))
- newaddrsize = 0;
-
- pr_debug(" -> newintsize=%d, newaddrsize=%d\n",
- newintsize, newaddrsize);
-
- /* Check for malformed properties */
- if (WARN_ON(newaddrsize + newintsize > MAX_PHANDLE_ARGS)
- || (imaplen < (newaddrsize + newintsize))) {
- rc = -EFAULT;
+ oldimap = imap;
+ imap = of_irq_parse_imap_parent(oldimap, imaplen, out_irq);
+ if (!imap)
goto fail;
- }
- imap += newaddrsize + newintsize;
- imaplen -= newaddrsize + newintsize;
+ 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 interrrupt-map translation; copy new
+ * Successfully parsed an interrupt-map translation; copy new
* interrupt specifier into the out_irq structure
*/
- match_array = imap - newaddrsize - newintsize;
- for (i = 0; i < newintsize; i++)
- out_irq->args[i] = be32_to_cpup(imap - newintsize + i);
- out_irq->args_count = intsize = newintsize;
- addrsize = newaddrsize;
+ 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 */
- out_irq->np = newpar;
pr_debug(" -> new parent: %pOF\n", newpar);
of_node_put(ipar);
ipar = newpar;
@@ -267,7 +338,6 @@ int of_irq_parse_raw(const __be32 *addr, struct of_phandle_args *out_irq)
fail:
of_node_put(ipar);
- of_node_put(newpar);
return rc;
}
@@ -277,18 +347,21 @@ 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_irq filled by this function
+ * @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 *p;
+ struct device_node __free(device_node) *p = NULL;
const __be32 *addr;
u32 intsize;
- int i, res;
+ int i, res, addr_len;
+ __be32 addr_buf[3] = { 0 };
pr_debug("of_irq_parse_one: dev=%pOF, index=%d\n", device, index);
@@ -297,46 +370,45 @@ int of_irq_parse_one(struct device_node *device, int index, struct of_phandle_ar
return of_irq_parse_oldworld(device, index, out_irq);
/* Get the reg property (if any) */
- addr = of_get_property(device, "reg", NULL);
+ 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)
- return of_irq_parse_raw(addr, out_irq);
-
- /* Look for the interrupt parent. */
- p = of_irq_find_parent(device);
- if (p == NULL)
- return -EINVAL;
+ 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;
+ }
- /* Get size of interrupt specifier */
- if (of_property_read_u32(p, "#interrupt-cells", &intsize)) {
- res = -EINVAL;
- goto out;
+ pr_debug(" intspec=%d\n", *out_irq->args);
}
- 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)
- goto out;
- }
-
- pr_debug(" intspec=%d\n", *out_irq->args);
-
-
/* Check if there are any interrupt-map translations to process */
- res = of_irq_parse_raw(addr, out_irq);
- out:
- of_node_put(p);
- return res;
+ return of_irq_parse_raw(addr_buf, out_irq);
}
EXPORT_SYMBOL_GPL(of_irq_parse_one);
@@ -366,9 +438,8 @@ int of_irq_to_resource(struct device_node *dev, int index, struct resource *r)
of_property_read_string_index(dev, "interrupt-names", index,
&name);
- r->start = r->end = irq;
- r->flags = IORESOURCE_IRQ | irqd_get_trigger_type(irq_get_irq_data(irq));
- r->name = name ? name : of_node_full_name(dev);
+ *r = DEFINE_RES_IRQ_NAMED(irq, name ?: of_node_full_name(dev));
+ r->flags |= irq_get_trigger_type(irq);
}
return irq;
@@ -380,7 +451,7 @@ EXPORT_SYMBOL_GPL(of_irq_to_resource);
* @dev: pointer to device tree node
* @index: zero-based index of the IRQ
*
- * Returns Linux IRQ number on success, or 0 on the IRQ mapping failure, or
+ * 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.
*/
@@ -395,19 +466,45 @@ int of_irq_get(struct device_node *dev, int index)
return rc;
domain = irq_find_host(oirq.np);
- if (!domain)
- return -EPROBE_DEFER;
+ if (!domain) {
+ rc = -EPROBE_DEFER;
+ goto out;
+ }
+
+ rc = irq_create_of_mapping(&oirq);
+out:
+ of_node_put(oirq.np);
- return irq_create_of_mapping(&oirq);
+ 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
*
- * Returns Linux IRQ number on success, or 0 on the IRQ mapping failure, or
+ * 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.
*/
@@ -435,11 +532,14 @@ 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)
+ 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
@@ -447,7 +547,7 @@ int of_irq_count(struct device_node *dev)
* @res: array of resources to fill in
* @nr_irqs: the number of IRQs (and upper bound for num of @res elements)
*
- * Returns the size of the filled in table (up to @nr_irqs).
+ * 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)
@@ -500,16 +600,27 @@ void __init of_irq_init(const struct of_device_id *matches)
* pointer, interrupt-parent device_node etc.
*/
desc = kzalloc(sizeof(*desc), GFP_KERNEL);
- if (WARN_ON(!desc)) {
+ if (!desc) {
of_node_put(np);
goto err;
}
desc->irq_init_cb = match->data;
desc->dev = of_node_get(np);
- desc->interrupt_parent = of_irq_find_parent(np);
- if (desc->interrupt_parent == 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);
}
@@ -540,7 +651,12 @@ void __init of_irq_init(const struct of_device_id *matches)
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;
}
@@ -571,60 +687,94 @@ void __init of_irq_init(const struct of_device_id *matches)
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 u32 __of_msi_map_rid(struct device *dev, struct device_node **np,
- u32 rid_in)
+static int of_check_msi_parent(struct device_node *dev_node, struct device_node **msi_node)
{
- struct device *parent_dev;
- u32 rid_out = rid_in;
+ struct of_phandle_args msi_spec;
+ int ret;
/*
- * Walk up the device parent links looking for one with a
- * "msi-map" property.
+ * 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.
*/
- for (parent_dev = dev; parent_dev; parent_dev = parent_dev->parent)
- if (!of_map_rid(parent_dev->of_node, rid_in, "msi-map",
- "msi-map-mask", np, &rid_out))
- break;
- return rid_out;
+ 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_map_rid - Map a MSI requester ID for a device.
+ * 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: device node of the expected msi controller.
- * @rid_in: unmapped MSI requester ID for the device.
+ * @msi_np: Pointer to target MSI controller node
+ * @id_in: Device ID.
*
* Walk up the device hierarchy looking for devices with a "msi-map"
- * property. If found, apply the mapping to @rid_in.
+ * or "msi-parent" property. If found, apply the mapping to @id_in.
+ * If @msi_np points to a non-NULL device node pointer, only entries targeting
+ * that node will be matched; if it points to a NULL value, it will receive the
+ * device node of the first matching target phandle, with a reference held.
*
- * Returns the mapped MSI requester ID.
+ * Returns: The mapped MSI id.
*/
-u32 of_msi_map_rid(struct device *dev, struct device_node *msi_np, u32 rid_in)
+u32 of_msi_xlate(struct device *dev, struct device_node **msi_np, u32 id_in)
{
- return __of_msi_map_rid(dev, &msi_np, rid_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) {
+ if (!of_map_id(parent_dev->of_node, id_in, "msi-map",
+ "msi-map-mask", msi_np, &id_out))
+ break;
+ if (!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.
- * @rid: Requester ID for the device.
+ * @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 rid)
+struct irq_domain *of_msi_map_get_device_domain(struct device *dev, u32 id,
+ u32 bus_token)
{
struct device_node *np = NULL;
- __of_msi_map_rid(dev, &np, rid);
- return irq_find_matching_host(np, DOMAIN_BUS_PCI_MSI);
+ of_msi_xlate(dev, &np, id);
+ return irq_find_matching_host(np, bus_token);
}
/**
@@ -633,53 +783,36 @@ struct irq_domain *of_msi_map_get_device_domain(struct device *dev, u32 rid)
* @np: device node for @dev
* @token: bus type for this domain
*
- * Parse the msi-parent property (both the simple and the complex
- * versions), and returns the corresponding MSI 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,
- struct device_node *np,
+ const struct device_node *np,
enum irq_domain_bus_token token)
{
- struct device_node *msi_np;
+ struct of_phandle_iterator it;
struct irq_domain *d;
+ int err;
- /* Check for a single msi-parent property */
- msi_np = of_parse_phandle(np, "msi-parent", 0);
- if (msi_np && !of_property_read_bool(msi_np, "#msi-cells")) {
- d = irq_find_matching_host(msi_np, token);
- if (!d)
- of_node_put(msi_np);
- return d;
- }
-
- if (token == DOMAIN_BUS_PLATFORM_MSI) {
- /* Check for the complex msi-parent version */
- struct of_phandle_args args;
- int index = 0;
-
- while (!of_parse_phandle_with_args(np, "msi-parent",
- "#msi-cells",
- index, &args)) {
- d = irq_find_matching_host(args.np, token);
- if (d)
- return d;
-
- of_node_put(args.np);
- index++;
+ 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, struct device_node *np)
+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));
diff --git a/drivers/of/kexec.c b/drivers/of/kexec.c
new file mode 100644
index 000000000000..1ee2d31816ae
--- /dev/null
+++ b/drivers/of/kexec.c
@@ -0,0 +1,516 @@
+// 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;
+ unsigned long start_pfn, end_pfn;
+ 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;
+
+ /*
+ * Calculate the PFNs for the buffer and ensure
+ * they are with in addressable memory.
+ */
+ start_pfn = PHYS_PFN(tmp_addr);
+ end_pfn = PHYS_PFN(tmp_addr + tmp_size - 1);
+ if (!page_is_ram(start_pfn) || !page_is_ram(end_pfn)) {
+ pr_warn("IMA buffer at 0x%lx, size = 0x%zx beyond memory\n",
+ tmp_addr, tmp_size);
+ return -EINVAL;
+ }
+
+ *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_free_late(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;
+
+#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;
+}
diff --git a/drivers/of/kobj.c b/drivers/of/kobj.c
index c72eef988041..1bb61a2c3399 100644
--- a/drivers/of/kobj.c
+++ b/drivers/of/kobj.c
@@ -5,13 +5,13 @@
#include "of_private.h"
/* true when node is initialized */
-static int of_node_is_initialized(struct device_node *node)
+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(struct device_node *node)
+int of_node_is_attached(const struct device_node *node)
{
return node && node->kobj.state_in_sysfs;
}
@@ -24,12 +24,12 @@ static void of_node_release(struct kobject *kobj)
}
#endif /* CONFIG_OF_DYNAMIC */
-struct kobj_type of_node_ktype = {
+const struct kobj_type of_node_ktype = {
.release = of_node_release,
};
static ssize_t of_node_property_read(struct file *filp, struct kobject *kobj,
- struct bin_attribute *bin_attr, char *buf,
+ const struct bin_attribute *bin_attr, char *buf,
loff_t offset, size_t count)
{
struct property *pp = container_of(bin_attr, struct property, attr);
@@ -37,7 +37,7 @@ static ssize_t of_node_property_read(struct file *filp, struct kobject *kobj,
}
/* always return newly allocated name, caller must free after use */
-static const char *safe_name(struct kobject *kobj, const char *orig_name)
+static const char *safe_name(const struct kobject *kobj, const char *orig_name)
{
const char *name = orig_name;
struct kernfs_node *kn;
@@ -84,7 +84,7 @@ int __of_add_property_sysfs(struct device_node *np, struct property *pp)
return rc;
}
-void __of_sysfs_remove_bin_file(struct device_node *np, struct property *prop)
+void __of_sysfs_remove_bin_file(struct device_node *np, const struct property *prop)
{
if (!IS_ENABLED(CONFIG_SYSFS))
return;
@@ -93,7 +93,7 @@ void __of_sysfs_remove_bin_file(struct device_node *np, struct property *prop)
kfree(prop->attr.attr.name);
}
-void __of_remove_property_sysfs(struct device_node *np, struct property *prop)
+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))
@@ -101,7 +101,7 @@ void __of_remove_property_sysfs(struct device_node *np, struct property *prop)
}
void __of_update_property_sysfs(struct device_node *np, struct property *newprop,
- struct property *oldprop)
+ const struct property *oldprop)
{
/* At early boot, bail out and defer setup to of_init() */
if (!of_kset)
@@ -119,7 +119,7 @@ int __of_attach_node_sysfs(struct device_node *np)
struct property *pp;
int rc;
- if (!of_kset)
+ if (!IS_ENABLED(CONFIG_SYSFS) || !of_kset)
return 0;
np->kobj.kset = of_kset;
@@ -134,8 +134,6 @@ int __of_attach_node_sysfs(struct device_node *np)
if (!name)
return -ENOMEM;
- of_node_get(np);
-
rc = kobject_add(&np->kobj, parent, "%s", name);
kfree(name);
if (rc)
@@ -144,6 +142,7 @@ int __of_attach_node_sysfs(struct device_node *np)
for_each_property_of_node(np, pp)
__of_add_property_sysfs(np, pp);
+ of_node_get(np);
return 0;
}
diff --git a/drivers/of/kunit_overlay_test.dtso b/drivers/of/kunit_overlay_test.dtso
new file mode 100644
index 000000000000..85f20b4b4c16
--- /dev/null
+++ b/drivers/of/kunit_overlay_test.dtso
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+&{/} {
+ kunit-test {
+ compatible = "test,empty";
+ };
+};
diff --git a/drivers/of/module.c b/drivers/of/module.c
new file mode 100644
index 000000000000..1e735fc130ad
--- /dev/null
+++ b/drivers/of/module.c
@@ -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);
diff --git a/drivers/of/of_kunit_helpers.c b/drivers/of/of_kunit_helpers.c
new file mode 100644
index 000000000000..f6ed1af8b62a
--- /dev/null
+++ b/drivers/of/of_kunit_helpers.c
@@ -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");
diff --git a/drivers/of/of_mdio.c b/drivers/of/of_mdio.c
deleted file mode 100644
index 5ad1342f5682..000000000000
--- a/drivers/of/of_mdio.c
+++ /dev/null
@@ -1,529 +0,0 @@
-/*
- * OF helpers for the MDIO (Ethernet PHY) API
- *
- * Copyright (c) 2009 Secret Lab Technologies, Ltd.
- *
- * This file is released under the GPLv2
- *
- * This file provides helper functions for extracting PHY device information
- * out of the OpenFirmware device tree and using it to populate an mii_bus.
- */
-
-#include <linux/kernel.h>
-#include <linux/device.h>
-#include <linux/netdevice.h>
-#include <linux/err.h>
-#include <linux/phy.h>
-#include <linux/phy_fixed.h>
-#include <linux/of.h>
-#include <linux/of_gpio.h>
-#include <linux/of_irq.h>
-#include <linux/of_mdio.h>
-#include <linux/of_net.h>
-#include <linux/module.h>
-
-#define DEFAULT_GPIO_RESET_DELAY 10 /* in microseconds */
-
-MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>");
-MODULE_LICENSE("GPL");
-
-/* Extract the clause 22 phy ID from the compatible string of the form
- * ethernet-phy-idAAAA.BBBB */
-static int of_get_phy_id(struct device_node *device, u32 *phy_id)
-{
- struct property *prop;
- const char *cp;
- unsigned int upper, lower;
-
- of_property_for_each_string(device, "compatible", prop, cp) {
- if (sscanf(cp, "ethernet-phy-id%4x.%4x", &upper, &lower) == 2) {
- *phy_id = ((upper & 0xFFFF) << 16) | (lower & 0xFFFF);
- return 0;
- }
- }
- return -EINVAL;
-}
-
-static int of_mdiobus_register_phy(struct mii_bus *mdio,
- struct device_node *child, u32 addr)
-{
- struct phy_device *phy;
- bool is_c45;
- int rc;
- u32 phy_id;
-
- is_c45 = of_device_is_compatible(child,
- "ethernet-phy-ieee802.3-c45");
-
- if (!is_c45 && !of_get_phy_id(child, &phy_id))
- phy = phy_device_create(mdio, addr, phy_id, 0, NULL);
- else
- phy = get_phy_device(mdio, addr, is_c45);
- if (IS_ERR(phy))
- return PTR_ERR(phy);
-
- rc = of_irq_get(child, 0);
- if (rc == -EPROBE_DEFER) {
- phy_device_free(phy);
- return rc;
- }
- if (rc > 0) {
- phy->irq = rc;
- mdio->irq[addr] = rc;
- } else {
- phy->irq = mdio->irq[addr];
- }
-
- if (of_property_read_bool(child, "broken-turn-around"))
- mdio->phy_ignore_ta_mask |= 1 << addr;
-
- of_property_read_u32(child, "reset-assert-us",
- &phy->mdio.reset_assert_delay);
- of_property_read_u32(child, "reset-deassert-us",
- &phy->mdio.reset_deassert_delay);
-
- /* Associate the OF node with the device structure so it
- * can be looked up later */
- of_node_get(child);
- phy->mdio.dev.of_node = child;
- phy->mdio.dev.fwnode = of_fwnode_handle(child);
-
- /* All data is now stored in the phy struct;
- * register it */
- rc = phy_device_register(phy);
- if (rc) {
- phy_device_free(phy);
- of_node_put(child);
- return rc;
- }
-
- dev_dbg(&mdio->dev, "registered phy %pOFn at address %i\n",
- child, addr);
- return 0;
-}
-
-static int of_mdiobus_register_device(struct mii_bus *mdio,
- struct device_node *child, u32 addr)
-{
- struct mdio_device *mdiodev;
- int rc;
-
- mdiodev = mdio_device_create(mdio, addr);
- if (IS_ERR(mdiodev))
- return PTR_ERR(mdiodev);
-
- /* Associate the OF node with the device structure so it
- * can be looked up later.
- */
- of_node_get(child);
- mdiodev->dev.of_node = child;
- mdiodev->dev.fwnode = of_fwnode_handle(child);
-
- /* All data is now stored in the mdiodev struct; register it. */
- rc = mdio_device_register(mdiodev);
- if (rc) {
- mdio_device_free(mdiodev);
- of_node_put(child);
- return rc;
- }
-
- dev_dbg(&mdio->dev, "registered mdio device %pOFn at address %i\n",
- child, addr);
- return 0;
-}
-
-/* The following is a list of PHY compatible strings which appear in
- * some DTBs. The compatible string is never matched against a PHY
- * driver, so is pointless. We only expect devices which are not PHYs
- * to have a compatible string, so they can be matched to an MDIO
- * driver. Encourage users to upgrade their DT blobs to remove these.
- */
-static const struct of_device_id whitelist_phys[] = {
- { .compatible = "brcm,40nm-ephy" },
- { .compatible = "broadcom,bcm5241" },
- { .compatible = "marvell,88E1111", },
- { .compatible = "marvell,88e1116", },
- { .compatible = "marvell,88e1118", },
- { .compatible = "marvell,88e1145", },
- { .compatible = "marvell,88e1149r", },
- { .compatible = "marvell,88e1310", },
- { .compatible = "marvell,88E1510", },
- { .compatible = "marvell,88E1514", },
- { .compatible = "moxa,moxart-rtl8201cp", },
- {}
-};
-
-/*
- * Return true if the child node is for a phy. It must either:
- * o Compatible string of "ethernet-phy-idX.X"
- * o Compatible string of "ethernet-phy-ieee802.3-c45"
- * o Compatible string of "ethernet-phy-ieee802.3-c22"
- * o In the white list above (and issue a warning)
- * o No compatibility string
- *
- * A device which is not a phy is expected to have a compatible string
- * indicating what sort of device it is.
- */
-static bool of_mdiobus_child_is_phy(struct device_node *child)
-{
- u32 phy_id;
-
- if (of_get_phy_id(child, &phy_id) != -EINVAL)
- return true;
-
- if (of_device_is_compatible(child, "ethernet-phy-ieee802.3-c45"))
- return true;
-
- if (of_device_is_compatible(child, "ethernet-phy-ieee802.3-c22"))
- return true;
-
- if (of_match_node(whitelist_phys, child)) {
- pr_warn(FW_WARN
- "%pOF: Whitelisted compatible string. Please remove\n",
- child);
- return true;
- }
-
- if (!of_find_property(child, "compatible", NULL))
- return true;
-
- return false;
-}
-
-/**
- * of_mdiobus_register - Register mii_bus and create PHYs from the device tree
- * @mdio: pointer to mii_bus structure
- * @np: pointer to device_node of MDIO bus.
- *
- * This function registers the mii_bus structure and registers a phy_device
- * for each child node of @np.
- */
-int of_mdiobus_register(struct mii_bus *mdio, struct device_node *np)
-{
- struct device_node *child;
- bool scanphys = false;
- int addr, rc;
-
- if (!np)
- return mdiobus_register(mdio);
-
- /* Do not continue if the node is disabled */
- if (!of_device_is_available(np))
- return -ENODEV;
-
- /* Mask out all PHYs from auto probing. Instead the PHYs listed in
- * the device tree are populated after the bus has been registered */
- mdio->phy_mask = ~0;
-
- mdio->dev.of_node = np;
- mdio->dev.fwnode = of_fwnode_handle(np);
-
- /* Get bus level PHY reset GPIO details */
- mdio->reset_delay_us = DEFAULT_GPIO_RESET_DELAY;
- of_property_read_u32(np, "reset-delay-us", &mdio->reset_delay_us);
-
- /* Register the MDIO bus */
- rc = mdiobus_register(mdio);
- if (rc)
- return rc;
-
- /* Loop over the child nodes and register a phy_device for each phy */
- for_each_available_child_of_node(np, child) {
- addr = of_mdio_parse_addr(&mdio->dev, child);
- if (addr < 0) {
- scanphys = true;
- continue;
- }
-
- if (of_mdiobus_child_is_phy(child))
- rc = of_mdiobus_register_phy(mdio, child, addr);
- else
- rc = of_mdiobus_register_device(mdio, child, addr);
-
- if (rc == -ENODEV)
- dev_err(&mdio->dev,
- "MDIO device at address %d is missing.\n",
- addr);
- else if (rc)
- goto unregister;
- }
-
- if (!scanphys)
- return 0;
-
- /* auto scan for PHYs with empty reg property */
- for_each_available_child_of_node(np, child) {
- /* Skip PHYs with reg property set */
- if (of_find_property(child, "reg", NULL))
- continue;
-
- for (addr = 0; addr < PHY_MAX_ADDR; addr++) {
- /* skip already registered PHYs */
- if (mdiobus_is_registered_device(mdio, addr))
- continue;
-
- /* be noisy to encourage people to set reg property */
- dev_info(&mdio->dev, "scan phy %pOFn at address %i\n",
- child, addr);
-
- if (of_mdiobus_child_is_phy(child)) {
- rc = of_mdiobus_register_phy(mdio, child, addr);
- if (rc && rc != -ENODEV)
- goto unregister;
- }
- }
- }
-
- return 0;
-
-unregister:
- mdiobus_unregister(mdio);
- return rc;
-}
-EXPORT_SYMBOL(of_mdiobus_register);
-
-/* Helper function for of_phy_find_device */
-static int of_phy_match(struct device *dev, void *phy_np)
-{
- return dev->of_node == phy_np;
-}
-
-/**
- * of_phy_find_device - Give a PHY node, find the phy_device
- * @phy_np: Pointer to the phy's device tree node
- *
- * If successful, returns a pointer to the phy_device with the embedded
- * struct device refcount incremented by one, or NULL on failure.
- */
-struct phy_device *of_phy_find_device(struct device_node *phy_np)
-{
- struct device *d;
- struct mdio_device *mdiodev;
-
- if (!phy_np)
- return NULL;
-
- d = bus_find_device(&mdio_bus_type, NULL, phy_np, of_phy_match);
- if (d) {
- mdiodev = to_mdio_device(d);
- if (mdiodev->flags & MDIO_DEVICE_FLAG_PHY)
- return to_phy_device(d);
- put_device(d);
- }
-
- return NULL;
-}
-EXPORT_SYMBOL(of_phy_find_device);
-
-/**
- * of_phy_connect - Connect to the phy described in the device tree
- * @dev: pointer to net_device claiming the phy
- * @phy_np: Pointer to device tree node for the PHY
- * @hndlr: Link state callback for the network device
- * @flags: flags to pass to the PHY
- * @iface: PHY data interface type
- *
- * If successful, returns a pointer to the phy_device with the embedded
- * struct device refcount incremented by one, or NULL on failure. The
- * refcount must be dropped by calling phy_disconnect() or phy_detach().
- */
-struct phy_device *of_phy_connect(struct net_device *dev,
- struct device_node *phy_np,
- void (*hndlr)(struct net_device *), u32 flags,
- phy_interface_t iface)
-{
- struct phy_device *phy = of_phy_find_device(phy_np);
- int ret;
-
- if (!phy)
- return NULL;
-
- phy->dev_flags = flags;
-
- ret = phy_connect_direct(dev, phy, hndlr, iface);
-
- /* refcount is held by phy_connect_direct() on success */
- put_device(&phy->mdio.dev);
-
- return ret ? NULL : phy;
-}
-EXPORT_SYMBOL(of_phy_connect);
-
-/**
- * of_phy_get_and_connect
- * - Get phy node and connect to the phy described in the device tree
- * @dev: pointer to net_device claiming the phy
- * @np: Pointer to device tree node for the net_device claiming the phy
- * @hndlr: Link state callback for the network device
- *
- * If successful, returns a pointer to the phy_device with the embedded
- * struct device refcount incremented by one, or NULL on failure. The
- * refcount must be dropped by calling phy_disconnect() or phy_detach().
- */
-struct phy_device *of_phy_get_and_connect(struct net_device *dev,
- struct device_node *np,
- void (*hndlr)(struct net_device *))
-{
- phy_interface_t iface;
- struct device_node *phy_np;
- struct phy_device *phy;
- int ret;
-
- iface = of_get_phy_mode(np);
- if (iface < 0)
- return NULL;
- if (of_phy_is_fixed_link(np)) {
- ret = of_phy_register_fixed_link(np);
- if (ret < 0) {
- netdev_err(dev, "broken fixed-link specification\n");
- return NULL;
- }
- phy_np = of_node_get(np);
- } else {
- phy_np = of_parse_phandle(np, "phy-handle", 0);
- if (!phy_np)
- return NULL;
- }
-
- phy = of_phy_connect(dev, phy_np, hndlr, 0, iface);
-
- of_node_put(phy_np);
-
- return phy;
-}
-EXPORT_SYMBOL(of_phy_get_and_connect);
-
-/**
- * of_phy_attach - Attach to a PHY without starting the state machine
- * @dev: pointer to net_device claiming the phy
- * @phy_np: Node pointer for the PHY
- * @flags: flags to pass to the PHY
- * @iface: PHY data interface type
- *
- * If successful, returns a pointer to the phy_device with the embedded
- * struct device refcount incremented by one, or NULL on failure. The
- * refcount must be dropped by calling phy_disconnect() or phy_detach().
- */
-struct phy_device *of_phy_attach(struct net_device *dev,
- struct device_node *phy_np, u32 flags,
- phy_interface_t iface)
-{
- struct phy_device *phy = of_phy_find_device(phy_np);
- int ret;
-
- if (!phy)
- return NULL;
-
- ret = phy_attach_direct(dev, phy, flags, iface);
-
- /* refcount is held by phy_attach_direct() on success */
- put_device(&phy->mdio.dev);
-
- return ret ? NULL : phy;
-}
-EXPORT_SYMBOL(of_phy_attach);
-
-/*
- * of_phy_is_fixed_link() and of_phy_register_fixed_link() must
- * support two DT bindings:
- * - the old DT binding, where 'fixed-link' was a property with 5
- * cells encoding various informations about the fixed PHY
- * - the new DT binding, where 'fixed-link' is a sub-node of the
- * Ethernet device.
- */
-bool of_phy_is_fixed_link(struct device_node *np)
-{
- struct device_node *dn;
- int len, err;
- const char *managed;
-
- /* New binding */
- dn = of_get_child_by_name(np, "fixed-link");
- if (dn) {
- of_node_put(dn);
- return true;
- }
-
- err = of_property_read_string(np, "managed", &managed);
- if (err == 0 && strcmp(managed, "auto") != 0)
- return true;
-
- /* Old binding */
- if (of_get_property(np, "fixed-link", &len) &&
- len == (5 * sizeof(__be32)))
- return true;
-
- return false;
-}
-EXPORT_SYMBOL(of_phy_is_fixed_link);
-
-int of_phy_register_fixed_link(struct device_node *np)
-{
- struct fixed_phy_status status = {};
- struct device_node *fixed_link_node;
- u32 fixed_link_prop[5];
- const char *managed;
- int link_gpio = -1;
-
- if (of_property_read_string(np, "managed", &managed) == 0 &&
- strcmp(managed, "in-band-status") == 0) {
- /* status is zeroed, namely its .link member */
- goto register_phy;
- }
-
- /* New binding */
- fixed_link_node = of_get_child_by_name(np, "fixed-link");
- if (fixed_link_node) {
- status.link = 1;
- status.duplex = of_property_read_bool(fixed_link_node,
- "full-duplex");
- if (of_property_read_u32(fixed_link_node, "speed",
- &status.speed)) {
- of_node_put(fixed_link_node);
- return -EINVAL;
- }
- status.pause = of_property_read_bool(fixed_link_node, "pause");
- status.asym_pause = of_property_read_bool(fixed_link_node,
- "asym-pause");
- link_gpio = of_get_named_gpio_flags(fixed_link_node,
- "link-gpios", 0, NULL);
- of_node_put(fixed_link_node);
- if (link_gpio == -EPROBE_DEFER)
- return -EPROBE_DEFER;
-
- goto register_phy;
- }
-
- /* Old binding */
- if (of_property_read_u32_array(np, "fixed-link", fixed_link_prop,
- ARRAY_SIZE(fixed_link_prop)) == 0) {
- status.link = 1;
- status.duplex = fixed_link_prop[1];
- status.speed = fixed_link_prop[2];
- status.pause = fixed_link_prop[3];
- status.asym_pause = fixed_link_prop[4];
- goto register_phy;
- }
-
- return -ENODEV;
-
-register_phy:
- return PTR_ERR_OR_ZERO(fixed_phy_register(PHY_POLL, &status, link_gpio,
- np));
-}
-EXPORT_SYMBOL(of_phy_register_fixed_link);
-
-void of_phy_deregister_fixed_link(struct device_node *np)
-{
- struct phy_device *phydev;
-
- phydev = of_phy_find_device(np);
- if (!phydev)
- return;
-
- fixed_phy_unregister(phydev);
-
- put_device(&phydev->mdio.dev); /* of_phy_find_device() */
- phy_device_free(phydev); /* fixed_phy_register() */
-}
-EXPORT_SYMBOL(of_phy_deregister_fixed_link);
diff --git a/drivers/of/of_net.c b/drivers/of/of_net.c
deleted file mode 100644
index 810ab0fbcccb..000000000000
--- a/drivers/of/of_net.c
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * OF helpers for network devices.
- *
- * This file is released under the GPLv2
- *
- * Initially copied out of arch/powerpc/kernel/prom_parse.c
- */
-#include <linux/etherdevice.h>
-#include <linux/kernel.h>
-#include <linux/nvmem-consumer.h>
-#include <linux/of_net.h>
-#include <linux/phy.h>
-#include <linux/export.h>
-
-/**
- * of_get_phy_mode - Get phy mode for given device_node
- * @np: Pointer to the given device_node
- *
- * The function gets phy interface string from property 'phy-mode' or
- * 'phy-connection-type', and return its index in phy_modes table, or errno in
- * error case.
- */
-int of_get_phy_mode(struct device_node *np)
-{
- const char *pm;
- int err, i;
-
- err = of_property_read_string(np, "phy-mode", &pm);
- if (err < 0)
- err = of_property_read_string(np, "phy-connection-type", &pm);
- if (err < 0)
- return err;
-
- for (i = 0; i < PHY_INTERFACE_MODE_MAX; i++)
- if (!strcasecmp(pm, phy_modes(i)))
- return i;
-
- return -ENODEV;
-}
-EXPORT_SYMBOL_GPL(of_get_phy_mode);
-
-static const void *of_get_mac_addr(struct device_node *np, const char *name)
-{
- struct property *pp = of_find_property(np, name, NULL);
-
- if (pp && pp->length == ETH_ALEN && is_valid_ether_addr(pp->value))
- return pp->value;
- return NULL;
-}
-
-/**
- * Search the device tree for the best MAC address to use. 'mac-address' is
- * checked first, because that is supposed to contain to "most recent" MAC
- * address. If that isn't set, then 'local-mac-address' is checked next,
- * because that is the default address. If that isn't set, then the obsolete
- * 'address' is checked, just in case we're using an old device tree.
- *
- * Note that the 'address' property is supposed to contain a virtual address of
- * the register set, but some DTS files have redefined that property to be the
- * MAC address.
- *
- * All-zero MAC addresses are rejected, because those could be properties that
- * exist in the device tree, but were not set by U-Boot. For example, the
- * DTS could define 'mac-address' and 'local-mac-address', with zero MAC
- * addresses. Some older U-Boots only initialized 'local-mac-address'. In
- * this case, the real MAC is in 'local-mac-address', and 'mac-address' exists
- * but is all zeros.
-*/
-const void *of_get_mac_address(struct device_node *np)
-{
- const void *addr;
-
- addr = of_get_mac_addr(np, "mac-address");
- if (addr)
- return addr;
-
- addr = of_get_mac_addr(np, "local-mac-address");
- if (addr)
- return addr;
-
- return of_get_mac_addr(np, "address");
-}
-EXPORT_SYMBOL(of_get_mac_address);
diff --git a/drivers/of/of_numa.c b/drivers/of/of_numa.c
index fe6b13608e51..cd2dc8e825c9 100644
--- a/drivers/of/of_numa.c
+++ b/drivers/of/of_numa.c
@@ -10,12 +10,10 @@
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/nodemask.h>
+#include <linux/numa_memblks.h>
#include <asm/numa.h>
-/* define default numa node to 0 */
-#define DEFAULT_NODE 0
-
/*
* Even though we connect cpus to numa domains later in SMP
* init, we need to know the node ids now for all cpus.
@@ -44,7 +42,7 @@ static int __init of_numa_parse_memory_nodes(void)
struct device_node *np = NULL;
struct resource rsrc;
u32 nid;
- int i, r;
+ int i, r = -EINVAL;
for_each_node_by_type(np, "memory") {
r = of_property_read_u32(np, "numa-node-id", &nid);
@@ -61,8 +59,11 @@ static int __init of_numa_parse_memory_nodes(void)
r = -EINVAL;
}
- for (i = 0; !r && !of_address_to_resource(np, i, &rsrc); i++)
+ 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);
@@ -71,7 +72,7 @@ static int __init of_numa_parse_memory_nodes(void)
}
}
- return 0;
+ return r;
}
static int __init of_numa_parse_distance_map_v1(struct device_node *map)
@@ -111,6 +112,8 @@ static int __init of_numa_parse_distance_map_v1(struct device_node *map)
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 */
diff --git a/drivers/of/of_private.h b/drivers/of/of_private.h
index 24786818e32e..df0bb00349e0 100644
--- a/drivers/of/of_private.h
+++ b/drivers/of/of_private.h
@@ -8,6 +8,9 @@
* 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
@@ -24,7 +27,7 @@ struct alias_prop {
const char *alias;
struct device_node *np;
int id;
- char stem[0];
+ char stem[];
};
#if defined(CONFIG_SPARC)
@@ -36,9 +39,13 @@ struct alias_prop {
#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);
@@ -57,12 +64,18 @@ static inline int of_property_notify(int action, struct device_node *np,
}
#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(struct device_node *node);
+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, struct property *prop);
+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,
- struct property *oldprop);
+ const struct property *oldprop);
int __of_attach_node_sysfs(struct device_node *np);
void __of_detach_node_sysfs(struct device_node *np);
#else
@@ -70,9 +83,9 @@ static inline int __of_add_property_sysfs(struct device_node *np, struct propert
{
return 0;
}
-static inline void __of_remove_property_sysfs(struct device_node *np, struct property *prop) {}
+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, struct property *oldprop) {}
+ struct property *newprop, const struct property *oldprop) {}
static inline int __of_attach_node_sysfs(struct device_node *np)
{
return 0;
@@ -84,15 +97,11 @@ static inline void __of_detach_node_sysfs(struct device_node *np) {}
int of_resolve_phandles(struct device_node *tree);
#endif
-#if defined(CONFIG_OF_DYNAMIC)
-void __of_free_phandle_cache_entry(phandle handle);
-#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);
-int of_free_phandle_cache(void);
-void of_populate_phandle_cache(void);
#else
static inline void of_overlay_mutex_lock(void) {};
static inline void of_overlay_mutex_unlock(void) {};
@@ -110,6 +119,8 @@ extern void *__unflatten_device_tree(const void *blob,
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.
*
@@ -118,10 +129,11 @@ extern void *__unflatten_device_tree(const void *blob,
* 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(struct device_node *parent,
+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);
@@ -129,22 +141,14 @@ struct device_node *__of_find_node_by_full_path(struct device_node *node,
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_add_property_sysfs(struct device_node *np,
- struct property *prop);
extern int __of_remove_property(struct device_node *np, struct property *prop);
-extern void __of_remove_property_sysfs(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_update_property_sysfs(struct device_node *np,
- struct property *newprop, struct property *oldprop);
-extern int __of_attach_node_sysfs(struct device_node *np);
extern void __of_detach_node(struct device_node *np);
-extern void __of_detach_node_sysfs(struct device_node *np);
extern void __of_sysfs_remove_bin_file(struct device_node *np,
- struct property *prop);
+ const struct property *prop);
/* illegal phandle value (set when unresolved) */
#define OF_PHANDLE_ILLEGAL 0xdeadbeef
@@ -158,4 +162,61 @@ extern void __of_sysfs_remove_bin_file(struct device_node *np,
#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_reg_nodes(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 */
diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c
index 1977ee0adcb1..5619ec917858 100644
--- a/drivers/of/of_reserved_mem.c
+++ b/drivers/of/of_reserved_mem.c
@@ -12,6 +12,8 @@
#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>
@@ -21,50 +23,98 @@
#include <linux/sort.h>
#include <linux/slab.h>
#include <linux/memblock.h>
+#include <linux/kmemleak.h>
+#include <linux/cma.h>
+#include <linux/dma-map-ops.h>
-#define MAX_RESERVED_REGIONS 32
-static struct reserved_mem reserved_mem[MAX_RESERVED_REGIONS];
+#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;
-int __init __weak early_init_dt_alloc_reserved_memory_arch(phys_addr_t size,
+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;
- /*
- * We use __memblock_alloc_base() because memblock_alloc_base()
- * panic()s on allocation failure.
- */
+ int err = 0;
+
end = !end ? MEMBLOCK_ALLOC_ANYWHERE : end;
align = !align ? SMP_CACHE_BYTES : align;
- base = __memblock_alloc_base(size, align, end);
+ base = memblock_phys_alloc_range(size, align, start, end);
if (!base)
return -ENOMEM;
- /*
- * Check if the allocated region fits in to start..end window
- */
- if (base < start) {
- memblock_free(base, size);
- return -ENOMEM;
+ *res_base = base;
+ if (nomap) {
+ err = memblock_mark_nomap(base, size);
+ if (err)
+ memblock_phys_free(base, size);
}
- *res_base = base;
- if (nomap)
- return memblock_remove(base, size);
- return 0;
+ if (!err)
+ kmemleak_ignore_phys(base);
+
+ return err;
}
-/**
- * res_mem_save_node() - save fdt node for second pass initialization
+/*
+ * 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 void __init alloc_reserved_mem_array(void)
+{
+ struct reserved_mem *new_array;
+ size_t alloc_size, copy_size, memset_size;
+
+ alloc_size = array_size(total_reserved_mem_cnt, sizeof(*new_array));
+ if (alloc_size == SIZE_MAX) {
+ pr_err("Failed to allocate memory for reserved_mem array with err: %d", -EOVERFLOW);
+ return;
+ }
+
+ new_array = memblock_alloc(alloc_size, SMP_CACHE_BYTES);
+ if (!new_array) {
+ pr_err("Failed to allocate memory for reserved_mem array with err: %d", -ENOMEM);
+ return;
+ }
+
+ copy_size = array_size(reserved_mem_count, sizeof(*new_array));
+ if (copy_size == SIZE_MAX) {
+ memblock_free(new_array, alloc_size);
+ total_reserved_mem_cnt = MAX_RESERVED_REGIONS;
+ pr_err("Failed to allocate memory for reserved_mem array with err: %d", -EOVERFLOW);
+ return;
+ }
+
+ 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;
+}
+
+static void __init fdt_init_reserved_mem_node(struct reserved_mem *rmem);
+/*
+ * fdt_reserved_mem_save_node() - save fdt node for second pass initialization
*/
-void __init fdt_reserved_mem_save_node(unsigned long node, const char *uname,
- phys_addr_t base, phys_addr_t size)
+static void __init fdt_reserved_mem_save_node(unsigned long node, const char *uname,
+ phys_addr_t base, phys_addr_t size)
{
struct reserved_mem *rmem = &reserved_mem[reserved_mem_count];
- if (reserved_mem_count == ARRAY_SIZE(reserved_mem)) {
- pr_err("not enough space all defined regions.\n");
+ if (reserved_mem_count == total_reserved_mem_cnt) {
+ pr_err("not enough space for all defined regions.\n");
return;
}
@@ -73,23 +123,273 @@ void __init fdt_reserved_mem_save_node(unsigned long node, const char *uname,
rmem->base = base;
rmem->size = size;
+ /* Call the region specific initialization function */
+ fdt_init_reserved_mem_node(rmem);
+
reserved_mem_count++;
return;
}
+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 all memory described in 'reg' property
+ */
+static int __init __reserved_mem_reserve_reg(unsigned long node,
+ const char *uname)
+{
+ phys_addr_t base, size;
+ int i, len;
+ const __be32 *prop;
+ bool nomap;
+
+ prop = of_flat_dt_get_addr_size_prop(node, "reg", &len);
+ if (!prop)
+ return -ENOENT;
+
+ nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL;
+
+ for (i = 0; i < len; i++) {
+ u64 b, s;
+
+ of_flat_dt_read_addr_size(prop, i, &b, &s);
+
+ base = b;
+ size = s;
+
+ if (size && early_init_dt_reserve_memory(base, size, nomap) == 0) {
+ /* Architecture specific contiguous memory fixup. */
+ if (of_flat_dt_is_compatible(node, "shared-dma-pool") &&
+ of_get_flat_dt_prop(node, "reusable", NULL))
+ dma_contiguous_early_fixup(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 void __init __rmem_check_for_overlap(void);
+
/**
- * res_mem_alloc_size() - allocate reserved memory described by 'size', 'align'
- * and 'alloc-ranges' properties
+ * fdt_scan_reserved_mem_reg_nodes() - Store info for the "reg" defined
+ * reserved memory regions.
+ *
+ * This function is used to scan through the DT and store the
+ * information for the reserved memory regions that are defined using
+ * the "reg" property. The region node number, name, base address, and
+ * size are all stored in the reserved_mem array by calling the
+ * fdt_reserved_mem_save_node() function.
*/
-static int __init __reserved_mem_alloc_size(unsigned long node,
- const char *uname, phys_addr_t *res_base, phys_addr_t *res_size)
+void __init fdt_scan_reserved_mem_reg_nodes(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 */
+ alloc_reserved_mem_array();
+
+ if (__reserved_mem_check_root(node)) {
+ pr_err("Reserved memory: unsupported node format, ignoring\n");
+ return;
+ }
+
+ fdt_for_each_subnode(child, fdt, node) {
+ const char *uname;
+ u64 b, s;
+
+ if (!of_fdt_device_is_available(fdt, child))
+ continue;
+
+ if (!of_flat_dt_get_addr_size(child, "reg", &b, &s))
+ continue;
+
+ base = b;
+ size = s;
+
+ if (size) {
+ uname = fdt_get_name(fdt, child, NULL);
+ fdt_reserved_mem_save_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() - scan a single FDT node for reserved memory
+ */
+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)
+ return -ENODEV;
+
+ if (__reserved_mem_check_root(node) != 0) {
+ pr_err("Reserved memory: unsupported node format, ignoring\n");
+ 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)) {
+ 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)
{
- int t_len = (dt_root_addr_cells + dt_root_size_cells) * sizeof(__be32);
phys_addr_t start = 0, end = 0;
phys_addr_t base = 0, align = 0, size;
- int len;
+ int i, len;
const __be32 *prop;
- int nomap;
+ bool nomap;
int ret;
prop = of_get_flat_dt_prop(node, "size", &len);
@@ -102,8 +402,6 @@ static int __init __reserved_mem_alloc_size(unsigned long node,
}
size = dt_mem_next_cell(dt_root_size_cells, &prop);
- nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL;
-
prop = of_get_flat_dt_prop(node, "alignment", &len);
if (prop) {
if (len != dt_root_addr_cells * sizeof(__be32)) {
@@ -114,73 +412,68 @@ static int __init __reserved_mem_alloc_size(unsigned long node,
align = dt_mem_next_cell(dt_root_addr_cells, &prop);
}
+ nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL;
+
/* Need adjust the alignment to satisfy the CMA requirement */
if (IS_ENABLED(CONFIG_CMA)
&& of_flat_dt_is_compatible(node, "shared-dma-pool")
&& of_get_flat_dt_prop(node, "reusable", NULL)
- && !of_get_flat_dt_prop(node, "no-map", NULL)) {
- unsigned long order =
- max_t(unsigned long, MAX_ORDER - 1, pageblock_order);
-
- align = max(align, (phys_addr_t)PAGE_SIZE << order);
- }
+ && !nomap)
+ align = max_t(phys_addr_t, align, CMA_MIN_ALIGNMENT_BYTES);
- prop = of_get_flat_dt_prop(node, "alloc-ranges", &len);
+ prop = of_flat_dt_get_addr_size_prop(node, "alloc-ranges", &len);
if (prop) {
+ for (i = 0; i < len; i++) {
+ u64 b, s;
- if (len % t_len != 0) {
- pr_err("invalid alloc-ranges property in '%s', skipping node.\n",
- uname);
- return -EINVAL;
- }
-
- base = 0;
+ of_flat_dt_read_addr_size(prop, i, &b, &s);
- while (len > 0) {
- start = dt_mem_next_cell(dt_root_addr_cells, &prop);
- end = start + dt_mem_next_cell(dt_root_size_cells,
- &prop);
+ start = b;
+ end = b + s;
- ret = early_init_dt_alloc_reserved_memory_arch(size,
- align, start, end, nomap, &base);
+ 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 %ld MiB\n",
+ pr_debug("allocated memory for '%s' node: base %pa, size %lu MiB\n",
uname, &base,
- (unsigned long)size / SZ_1M);
+ (unsigned long)(size / SZ_1M));
break;
}
- len -= t_len;
}
-
} 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 %ld MiB\n",
- uname, &base, (unsigned long)size / SZ_1M);
+ pr_debug("allocated memory for '%s' node: base %pa, size %lu MiB\n",
+ uname, &base, (unsigned long)(size / SZ_1M));
}
if (base == 0) {
- pr_info("failed to allocate memory for node '%s'\n", uname);
+ pr_err("failed to allocate memory for node '%s': size %lu MiB\n",
+ uname, (unsigned long)(size / SZ_1M));
return -ENOMEM;
}
-
- *res_base = base;
- *res_size = size;
-
+ /* Architecture specific contiguous memory fixup. */
+ if (of_flat_dt_is_compatible(node, "shared-dma-pool") &&
+ of_get_flat_dt_prop(node, "reusable", NULL))
+ dma_contiguous_early_fixup(base, size);
+ /* Save region in the reserved_mem array */
+ fdt_reserved_mem_save_node(node, uname, base, size);
return 0;
}
static const struct of_device_id __rmem_of_table_sentinel
- __used __section(__reservedmem_of_table_end);
+ __used __section("__reservedmem_of_table_end");
-/**
- * res_mem_init_node() - call region specific reserved memory init code
+/*
+ * __reserved_mem_init_node() - call region specific reserved memory init code
*/
static int __init __reserved_mem_init_node(struct reserved_mem *rmem)
{
extern const struct of_device_id __reservedmem_of_table[];
const struct of_device_id *i;
+ int ret = -ENOENT;
for (i = __reservedmem_of_table; i < &__rmem_of_table_sentinel; i++) {
reservedmem_of_init_fn initfn = i->data;
@@ -189,13 +482,14 @@ static int __init __reserved_mem_init_node(struct reserved_mem *rmem)
if (!of_flat_dt_is_compatible(rmem->fdt_node, compat))
continue;
- if (initfn(rmem) == 0) {
+ ret = initfn(rmem);
+ if (ret == 0) {
pr_info("initialized node %s, compatible id %s\n",
rmem->name, compat);
- return 0;
+ break;
}
}
- return -ENOENT;
+ return ret;
}
static int __init __rmem_cmp(const void *a, const void *b)
@@ -208,6 +502,21 @@ static int __init __rmem_cmp(const void *a, const void *b)
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;
+
+ if (ra->fdt_node < rb->fdt_node)
+ return -1;
+ if (ra->fdt_node > rb->fdt_node)
+ return 1;
+
return 0;
}
@@ -225,8 +534,7 @@ static void __init __rmem_check_for_overlap(void)
this = &reserved_mem[i];
next = &reserved_mem[i + 1];
- if (!(this->base && next->base))
- continue;
+
if (this->base + this->size > next->base) {
phys_addr_t this_end, next_end;
@@ -240,49 +548,40 @@ static void __init __rmem_check_for_overlap(void)
}
/**
- * fdt_init_reserved_mem - allocate and init all saved reserved memory regions
+ * fdt_init_reserved_mem_node() - Initialize a reserved memory region
+ * @rmem: reserved_mem struct of the memory region to be initialized.
+ *
+ * This function is used to call the region specific initialization
+ * function for a reserved memory region.
*/
-void __init fdt_init_reserved_mem(void)
+static void __init fdt_init_reserved_mem_node(struct reserved_mem *rmem)
{
- int i;
-
- /* check for overlapping reserved regions */
- __rmem_check_for_overlap();
-
- for (i = 0; i < reserved_mem_count; i++) {
- struct reserved_mem *rmem = &reserved_mem[i];
- unsigned long node = rmem->fdt_node;
- int len;
- const __be32 *prop;
- int err = 0;
+ unsigned long node = rmem->fdt_node;
+ int err = 0;
+ bool nomap;
- prop = of_get_flat_dt_prop(node, "phandle", &len);
- if (!prop)
- prop = of_get_flat_dt_prop(node, "linux,phandle", &len);
- if (prop)
- rmem->phandle = of_read_number(prop, len/4);
+ nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL;
- if (rmem->size == 0)
- err = __reserved_mem_alloc_size(node, rmem->name,
- &rmem->base, &rmem->size);
- if (err == 0)
- __reserved_mem_init_node(rmem);
+ err = __reserved_mem_init_node(rmem);
+ if (err != 0 && err != -ENOENT) {
+ pr_info("node %s compatible matching fail\n", rmem->name);
+ if (nomap)
+ memblock_clear_nomap(rmem->base, rmem->size);
+ else
+ memblock_phys_free(rmem->base, rmem->size);
+ } 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");
}
}
-static inline struct reserved_mem *__find_rmem(struct device_node *node)
-{
- unsigned int i;
-
- if (!node->phandle)
- return NULL;
-
- for (i = 0; i < reserved_mem_count; i++)
- if (reserved_mem[i].phandle == node->phandle)
- return &reserved_mem[i];
- return NULL;
-}
-
struct rmem_assigned_device {
struct device *dev;
struct reserved_mem *rmem;
@@ -322,7 +621,12 @@ int of_reserved_mem_device_init_by_idx(struct device *dev,
if (!target)
return -ENODEV;
- rmem = __find_rmem(target);
+ 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)
@@ -340,10 +644,6 @@ int of_reserved_mem_device_init_by_idx(struct device *dev,
mutex_lock(&of_rmem_assigned_device_mutex);
list_add(&rd->list, &of_rmem_assigned_device_list);
mutex_unlock(&of_rmem_assigned_device_mutex);
- /* ensure that dma_ops is set for virtual devices
- * using reserved memory
- */
- of_dma_configure(dev, np, true);
dev_info(dev, "assigned reserved memory node %s\n", rmem->name);
} else {
@@ -355,6 +655,25 @@ int of_reserved_mem_device_init_by_idx(struct device *dev,
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
*
@@ -363,24 +682,22 @@ EXPORT_SYMBOL_GPL(of_reserved_mem_device_init_by_idx);
*/
void of_reserved_mem_device_release(struct device *dev)
{
- struct rmem_assigned_device *rd;
- struct reserved_mem *rmem = NULL;
+ struct rmem_assigned_device *rd, *tmp;
+ LIST_HEAD(release_list);
mutex_lock(&of_rmem_assigned_device_mutex);
- list_for_each_entry(rd, &of_rmem_assigned_device_list, list) {
- if (rd->dev == dev) {
- rmem = rd->rmem;
- list_del(&rd->list);
- kfree(rd);
- break;
- }
+ 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);
- if (!rmem || !rmem->ops || !rmem->ops->device_release)
- return;
+ 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);
- rmem->ops->device_release(rmem, dev);
+ kfree(rd);
+ }
}
EXPORT_SYMBOL_GPL(of_reserved_mem_device_release);
@@ -409,3 +726,83 @@ struct reserved_mem *of_reserved_mem_lookup(struct device_node *np)
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 __free(device_node) *target = 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);
diff --git a/drivers/of/of_test.c b/drivers/of/of_test.c
new file mode 100644
index 000000000000..8bba5a72c9c7
--- /dev/null
+++ b/drivers/of/of_test.c
@@ -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");
diff --git a/drivers/of/overlay.c b/drivers/of/overlay.c
index c423e94baf0f..5b4f42230e6c 100644
--- a/drivers/of/overlay.c
+++ b/drivers/of/overlay.c
@@ -45,8 +45,8 @@ struct target {
/**
* struct fragment - info about fragment nodes in overlay expanded device tree
- * @target: target of the overlay operation
* @overlay: pointer to the __overlay__ node
+ * @target: target of the overlay operation
*/
struct fragment {
struct device_node *overlay;
@@ -57,8 +57,10 @@ struct fragment {
* struct overlay_changeset
* @id: changeset identifier
* @ovcs_list: list on which we are located
- * @fdt: FDT that was unflattened to create @overlay_tree
- * @overlay_tree: expanded device tree that contains the fragment nodes
+ * @new_fdt: Memory allocated to hold unflattened aligned FDT
+ * @overlay_mem: the memory chunk that contains @overlay_root
+ * @overlay_root: expanded device tree that contains the fragment nodes
+ * @notify_state: most recent notify action used on overlay
* @count: count of fragment structures
* @fragments: fragment nodes in the overlay expanded device tree
* @symbols_fragment: last element of @fragments[] is the __symbols__ node
@@ -67,8 +69,10 @@ struct fragment {
struct overlay_changeset {
int id;
struct list_head ovcs_list;
- const void *fdt;
- struct device_node *overlay_tree;
+ const void *new_fdt;
+ const void *overlay_mem;
+ struct device_node *overlay_root;
+ enum of_overlay_notify_action notify_state;
int count;
struct fragment *fragments;
bool symbols_fragment;
@@ -80,6 +84,12 @@ static int devicetree_state_flags;
#define DTSF_APPLY_FAIL 0x01
#define DTSF_REVERT_FAIL 0x02
+static int of_prop_val_eq(const struct property *p1, const struct property *p2)
+{
+ return p1->length == p2->length &&
+ !memcmp(p1->value, p2->value, (size_t)p1->length);
+}
+
/*
* If a changeset apply or revert encounters an error, an attempt will
* be made to undo partial changes, but may fail. If the undo fails
@@ -115,7 +125,6 @@ void of_overlay_mutex_unlock(void)
mutex_unlock(&of_overlay_phandle_mutex);
}
-
static LIST_HEAD(ovcs_list);
static DEFINE_IDR(ovcs_idr);
@@ -126,7 +135,7 @@ static BLOCKING_NOTIFIER_HEAD(overlay_notify_chain);
* @nb: Notifier block to register
*
* Register for notification on overlay operations on device tree nodes. The
- * reported actions definied by @of_reconfig_change. The notifier callback
+ * reported actions defined by @of_reconfig_change. The notifier callback
* furthermore receives a pointer to the affected device tree node.
*
* Note that a notifier callback is not supposed to store pointers to a device
@@ -140,7 +149,7 @@ int of_overlay_notifier_register(struct notifier_block *nb)
EXPORT_SYMBOL_GPL(of_overlay_notifier_register);
/**
- * of_overlay_notifier_register() - Unregister notifier for overlay operations
+ * of_overlay_notifier_unregister() - Unregister notifier for overlay operations
* @nb: Notifier block to unregister
*/
int of_overlay_notifier_unregister(struct notifier_block *nb)
@@ -149,19 +158,14 @@ int of_overlay_notifier_unregister(struct notifier_block *nb)
}
EXPORT_SYMBOL_GPL(of_overlay_notifier_unregister);
-static char *of_overlay_action_name[] = {
- "pre-apply",
- "post-apply",
- "pre-remove",
- "post-remove",
-};
-
static int overlay_notify(struct overlay_changeset *ovcs,
enum of_overlay_notify_action action)
{
struct of_overlay_notify_data nd;
int i, ret;
+ ovcs->notify_state = action;
+
for (i = 0; i < ovcs->count; i++) {
struct fragment *fragment = &ovcs->fragments[i];
@@ -170,12 +174,10 @@ static int overlay_notify(struct overlay_changeset *ovcs,
ret = blocking_notifier_call_chain(&overlay_notify_chain,
action, &nd);
- if (ret == NOTIFY_OK || ret == NOTIFY_STOP)
- return 0;
- if (ret) {
+ if (notifier_to_errno(ret)) {
ret = notifier_to_errno(ret);
pr_err("overlay changeset %s notifier error %d, target: %pOF\n",
- of_overlay_action_name[action], ret, nd.target);
+ of_overlay_action_name(action), ret, nd.target);
return ret;
}
}
@@ -185,7 +187,7 @@ static int overlay_notify(struct overlay_changeset *ovcs,
/*
* The values of properties in the "/__symbols__" node are paths in
- * the ovcs->overlay_tree. When duplicating the properties, the paths
+ * the ovcs->overlay_root. When duplicating the properties, the paths
* need to be adjusted to be the correct path for the live device tree.
*
* The paths refer to a node in the subtree of a fragment node's "__overlay__"
@@ -221,7 +223,7 @@ static struct property *dup_and_fixup_symbol_prop(
if (path_len < 1)
return NULL;
- fragment_node = __of_find_node_by_path(ovcs->overlay_tree, path + 1);
+ fragment_node = __of_find_node_by_path(ovcs->overlay_root, path + 1);
overlay_node = __of_find_node_by_path(fragment_node, "__overlay__/");
of_node_put(fragment_node);
of_node_put(overlay_node);
@@ -261,12 +263,12 @@ static struct property *dup_and_fixup_symbol_prop(
of_property_set_flag(new_prop, OF_DYNAMIC);
+ kfree(target_path);
+
return new_prop;
err_free_new_prop:
- kfree(new_prop->name);
- kfree(new_prop->value);
- kfree(new_prop);
+ __of_prop_free(new_prop);
err_free_target_path:
kfree(target_path);
@@ -296,21 +298,19 @@ err_free_target_path:
*
* Update of property in symbols node is not allowed.
*
- * Returns 0 on success, -ENOMEM if memory allocation failure, or -EINVAL if
+ * Return: 0 on success, -ENOMEM if memory allocation failure, or -EINVAL if
* invalid @overlay.
*/
static int add_changeset_property(struct overlay_changeset *ovcs,
- struct target *target, struct property *overlay_prop,
+ struct target *target, const struct property *overlay_prop,
bool is_symbols_prop)
{
- struct property *new_prop = NULL, *prop;
+ struct property *new_prop = NULL;
+ const struct property *prop;
int ret = 0;
- bool check_for_non_overlay_node = false;
if (target->in_livetree)
- if (!of_prop_cmp(overlay_prop->name, "name") ||
- !of_prop_cmp(overlay_prop->name, "phandle") ||
- !of_prop_cmp(overlay_prop->name, "linux,phandle"))
+ if (is_pseudo_property(overlay_prop->name))
return 0;
if (target->in_livetree)
@@ -318,6 +318,25 @@ static int add_changeset_property(struct overlay_changeset *ovcs,
else
prop = NULL;
+ if (prop) {
+ if (!of_prop_cmp(prop->name, "#address-cells")) {
+ if (!of_prop_val_eq(prop, overlay_prop)) {
+ pr_err("ERROR: changing value of #address-cells is not allowed in %pOF\n",
+ target->np);
+ ret = -EINVAL;
+ }
+ return ret;
+
+ } else if (!of_prop_cmp(prop->name, "#size-cells")) {
+ if (!of_prop_val_eq(prop, overlay_prop)) {
+ pr_err("ERROR: changing value of #size-cells is not allowed in %pOF\n",
+ target->np);
+ ret = -EINVAL;
+ }
+ return ret;
+ }
+ }
+
if (is_symbols_prop) {
if (prop)
return -EINVAL;
@@ -330,41 +349,23 @@ static int add_changeset_property(struct overlay_changeset *ovcs,
return -ENOMEM;
if (!prop) {
- check_for_non_overlay_node = true;
if (!target->in_livetree) {
new_prop->next = target->np->deadprops;
target->np->deadprops = new_prop;
}
ret = of_changeset_add_property(&ovcs->cset, target->np,
new_prop);
- } else if (!of_prop_cmp(prop->name, "#address-cells")) {
- if (!of_prop_val_eq(prop, new_prop)) {
- pr_err("ERROR: changing value of #address-cells is not allowed in %pOF\n",
- target->np);
- ret = -EINVAL;
- }
- } else if (!of_prop_cmp(prop->name, "#size-cells")) {
- if (!of_prop_val_eq(prop, new_prop)) {
- pr_err("ERROR: changing value of #size-cells is not allowed in %pOF\n",
- target->np);
- ret = -EINVAL;
- }
} else {
- check_for_non_overlay_node = true;
ret = of_changeset_update_property(&ovcs->cset, target->np,
new_prop);
}
- if (check_for_non_overlay_node &&
- !of_node_check_flag(target->np, OF_OVERLAY))
+ if (!of_node_check_flag(target->np, OF_OVERLAY))
pr_err("WARNING: memory leak will occur if overlay removed, property: %pOF/%s\n",
target->np, new_prop->name);
- if (ret) {
- kfree(new_prop->name);
- kfree(new_prop->value);
- kfree(new_prop);
- }
+ if (ret)
+ __of_prop_free(new_prop);
return ret;
}
@@ -398,11 +399,11 @@ static int add_changeset_property(struct overlay_changeset *ovcs,
*
* NOTE_2: Multiple mods of created nodes not supported.
*
- * Returns 0 on success, -ENOMEM if memory allocation failure, or -EINVAL if
+ * Return: 0 on success, -ENOMEM if memory allocation failure, or -EINVAL if
* invalid @overlay.
*/
static int add_changeset_node(struct overlay_changeset *ovcs,
- struct target *target, struct device_node *node)
+ struct target *target, const struct device_node *node)
{
const char *node_kbasename;
const __be32 *phandle;
@@ -470,13 +471,12 @@ static int add_changeset_node(struct overlay_changeset *ovcs,
*
* Do not allow symbols node to have any children.
*
- * Returns 0 on success, -ENOMEM if memory allocation failure, or -EINVAL if
+ * Return: 0 on success, -ENOMEM if memory allocation failure, or -EINVAL if
* invalid @overlay_node.
*/
static int build_changeset_next_level(struct overlay_changeset *ovcs,
struct target *target, const struct device_node *overlay_node)
{
- struct device_node *child;
struct property *prop;
int ret;
@@ -489,12 +489,11 @@ static int build_changeset_next_level(struct overlay_changeset *ovcs,
}
}
- for_each_child_of_node(overlay_node, child) {
+ for_each_child_of_node_scoped(overlay_node, child) {
ret = add_changeset_node(ovcs, target, child);
if (ret) {
pr_debug("Failed to apply node @%pOF/%pOFn, err=%d\n",
target->np, child, ret);
- of_node_put(child);
return ret;
}
}
@@ -544,7 +543,7 @@ static int find_dup_cset_node_entry(struct overlay_changeset *ovcs,
fn_1 = kasprintf(GFP_KERNEL, "%pOF", ce_1->np);
fn_2 = kasprintf(GFP_KERNEL, "%pOF", ce_2->np);
- node_path_match = !strcmp(fn_1, fn_2);
+ node_path_match = !fn_1 || !fn_2 || !strcmp(fn_1, fn_2);
kfree(fn_1);
kfree(fn_2);
if (node_path_match) {
@@ -579,7 +578,7 @@ static int find_dup_cset_prop(struct overlay_changeset *ovcs,
fn_1 = kasprintf(GFP_KERNEL, "%pOF", ce_1->np);
fn_2 = kasprintf(GFP_KERNEL, "%pOF", ce_2->np);
- node_path_match = !strcmp(fn_1, fn_2);
+ node_path_match = !fn_1 || !fn_2 || !strcmp(fn_1, fn_2);
kfree(fn_1);
kfree(fn_2);
if (node_path_match &&
@@ -601,7 +600,7 @@ static int find_dup_cset_prop(struct overlay_changeset *ovcs,
* the same node or duplicate {add, delete, or update} properties entries
* for the same property.
*
- * Returns 0 on success, or -EINVAL if duplicate changeset entry found.
+ * Return: 0 on success, or -EINVAL if duplicate changeset entry found.
*/
static int changeset_dup_entry_check(struct overlay_changeset *ovcs)
{
@@ -625,7 +624,7 @@ static int changeset_dup_entry_check(struct overlay_changeset *ovcs)
* any portions of the changeset that were successfully created will remain
* in @ovcs->cset.
*
- * Returns 0 on success, -ENOMEM if memory allocation failure, or -EINVAL if
+ * Return: 0 on success, -ENOMEM if memory allocation failure, or -EINVAL if
* invalid overlay in @ovcs->fragments[].
*/
static int build_changeset(struct overlay_changeset *ovcs)
@@ -681,9 +680,11 @@ static int build_changeset(struct overlay_changeset *ovcs)
* 1) "target" property containing the phandle of the target
* 2) "target-path" property containing the path of the target
*/
-static struct device_node *find_target(struct device_node *info_node)
+static struct device_node *find_target(const struct device_node *info_node,
+ const struct device_node *target_base)
{
struct device_node *node;
+ char *target_path;
const char *path;
u32 val;
int ret;
@@ -699,10 +700,23 @@ static struct device_node *find_target(struct device_node *info_node)
ret = of_property_read_string(info_node, "target-path", &path);
if (!ret) {
- node = of_find_node_by_path(path);
- if (!node)
- pr_err("find target, node: %pOF, path '%s' not found\n",
- info_node, path);
+ if (target_base) {
+ target_path = kasprintf(GFP_KERNEL, "%pOF%s", target_base, path);
+ if (!target_path)
+ return NULL;
+ node = of_find_node_by_path(target_path);
+ if (!node) {
+ pr_err("find target, node: %pOF, path '%s' not found\n",
+ info_node, target_path);
+ }
+ kfree(target_path);
+ } else {
+ node = of_find_node_by_path(path);
+ if (!node) {
+ pr_err("find target, node: %pOF, path '%s' not found\n",
+ info_node, path);
+ }
+ }
return node;
}
@@ -713,53 +727,50 @@ static struct device_node *find_target(struct device_node *info_node)
/**
* init_overlay_changeset() - initialize overlay changeset from overlay tree
- * @ovcs: Overlay changeset to build
- * @fdt: the FDT that was unflattened to create @tree
- * @tree: Contains all the overlay fragments and overlay fixup nodes
+ * @ovcs: Overlay changeset to build
+ * @target_base: Point to the target node to apply overlay
*
* Initialize @ovcs. Populate @ovcs->fragments with node information from
- * the top level of @tree. The relevant top level nodes are the fragment
- * nodes and the __symbols__ node. Any other top level node will be ignored.
+ * the top level of @overlay_root. The relevant top level nodes are the
+ * fragment nodes and the __symbols__ node. Any other top level node will
+ * be ignored. Populate other @ovcs fields.
*
- * Returns 0 on success, -ENOMEM if memory allocation failure, -EINVAL if error
- * detected in @tree, or -ENOSPC if idr_alloc() error.
+ * Return: 0 on success, -ENOMEM if memory allocation failure, -EINVAL if error
+ * detected in @overlay_root. On error return, the caller of
+ * init_overlay_changeset() must call free_overlay_changeset().
*/
static int init_overlay_changeset(struct overlay_changeset *ovcs,
- const void *fdt, struct device_node *tree)
+ const struct device_node *target_base)
{
struct device_node *node, *overlay_node;
struct fragment *fragment;
struct fragment *fragments;
- int cnt, id, ret;
+ int cnt, ret;
+
+ /*
+ * None of the resources allocated by this function will be freed in
+ * the error paths. Instead the caller of this function is required
+ * to call free_overlay_changeset() (which will free the resources)
+ * if error return.
+ */
/*
* Warn for some issues. Can not return -EINVAL for these until
* of_unittest_apply_overlay() is fixed to pass these checks.
*/
- if (!of_node_check_flag(tree, OF_DYNAMIC))
- pr_debug("%s() tree is not dynamic\n", __func__);
-
- if (!of_node_check_flag(tree, OF_DETACHED))
- pr_debug("%s() tree is not detached\n", __func__);
-
- if (!of_node_is_root(tree))
- pr_debug("%s() tree is not root\n", __func__);
-
- ovcs->overlay_tree = tree;
- ovcs->fdt = fdt;
-
- INIT_LIST_HEAD(&ovcs->ovcs_list);
+ if (!of_node_check_flag(ovcs->overlay_root, OF_DYNAMIC))
+ pr_debug("%s() ovcs->overlay_root is not dynamic\n", __func__);
- of_changeset_init(&ovcs->cset);
+ if (!of_node_check_flag(ovcs->overlay_root, OF_DETACHED))
+ pr_debug("%s() ovcs->overlay_root is not detached\n", __func__);
- id = idr_alloc(&ovcs_idr, ovcs, 1, 0, GFP_KERNEL);
- if (id <= 0)
- return id;
+ if (!of_node_is_root(ovcs->overlay_root))
+ pr_debug("%s() ovcs->overlay_root is not root\n", __func__);
cnt = 0;
/* fragment nodes */
- for_each_child_of_node(tree, node) {
+ for_each_child_of_node(ovcs->overlay_root, node) {
overlay_node = of_get_child_by_name(node, "__overlay__");
if (overlay_node) {
cnt++;
@@ -767,7 +778,7 @@ static int init_overlay_changeset(struct overlay_changeset *ovcs,
}
}
- node = of_get_child_by_name(tree, "__symbols__");
+ node = of_get_child_by_name(ovcs->overlay_root, "__symbols__");
if (node) {
cnt++;
of_node_put(node);
@@ -776,22 +787,24 @@ static int init_overlay_changeset(struct overlay_changeset *ovcs,
fragments = kcalloc(cnt, sizeof(*fragments), GFP_KERNEL);
if (!fragments) {
ret = -ENOMEM;
- goto err_free_idr;
+ goto err_out;
}
+ ovcs->fragments = fragments;
cnt = 0;
- for_each_child_of_node(tree, node) {
+ for_each_child_of_node(ovcs->overlay_root, node) {
overlay_node = of_get_child_by_name(node, "__overlay__");
if (!overlay_node)
continue;
fragment = &fragments[cnt];
fragment->overlay = overlay_node;
- fragment->target = find_target(node);
+ fragment->target = find_target(node, target_base);
if (!fragment->target) {
of_node_put(fragment->overlay);
ret = -EINVAL;
- goto err_free_fragments;
+ of_node_put(node);
+ goto err_out;
}
cnt++;
@@ -801,7 +814,7 @@ static int init_overlay_changeset(struct overlay_changeset *ovcs,
* if there is a symbols fragment in ovcs->fragments[i] it is
* the final element in the array
*/
- node = of_get_child_by_name(tree, "__symbols__");
+ node = of_get_child_by_name(ovcs->overlay_root, "__symbols__");
if (node) {
ovcs->symbols_fragment = 1;
fragment = &fragments[cnt];
@@ -811,7 +824,8 @@ static int init_overlay_changeset(struct overlay_changeset *ovcs,
if (!fragment->target) {
pr_err("symbols in overlay, but not in live tree\n");
ret = -EINVAL;
- goto err_free_fragments;
+ of_node_put(node);
+ goto err_out;
}
cnt++;
@@ -820,20 +834,14 @@ static int init_overlay_changeset(struct overlay_changeset *ovcs,
if (!cnt) {
pr_err("no fragments or symbols in overlay\n");
ret = -EINVAL;
- goto err_free_fragments;
+ goto err_out;
}
- ovcs->id = id;
ovcs->count = cnt;
- ovcs->fragments = fragments;
return 0;
-err_free_fragments:
- kfree(fragments);
-err_free_idr:
- idr_remove(&ovcs_idr, id);
-
+err_out:
pr_err("%s() failed, ret = %d\n", __func__, ret);
return ret;
@@ -846,21 +854,34 @@ static void free_overlay_changeset(struct overlay_changeset *ovcs)
if (ovcs->cset.entries.next)
of_changeset_destroy(&ovcs->cset);
- if (ovcs->id)
+ if (ovcs->id) {
idr_remove(&ovcs_idr, ovcs->id);
+ list_del(&ovcs->ovcs_list);
+ ovcs->id = 0;
+ }
+
for (i = 0; i < ovcs->count; i++) {
of_node_put(ovcs->fragments[i].target);
of_node_put(ovcs->fragments[i].overlay);
}
kfree(ovcs->fragments);
+
/*
- * There should be no live pointers into ovcs->overlay_tree and
- * ovcs->fdt due to the policy that overlay notifiers are not allowed
- * to retain pointers into the overlay devicetree.
+ * There should be no live pointers into ovcs->overlay_mem and
+ * ovcs->new_fdt due to the policy that overlay notifiers are not
+ * allowed to retain pointers into the overlay devicetree other
+ * than during the window from OF_OVERLAY_PRE_APPLY overlay
+ * notifiers until the OF_OVERLAY_POST_REMOVE overlay notifiers.
+ *
+ * A memory leak will occur here if within the window.
*/
- kfree(ovcs->overlay_tree);
- kfree(ovcs->fdt);
+
+ if (ovcs->notify_state == OF_OVERLAY_INIT ||
+ ovcs->notify_state == OF_OVERLAY_POST_REMOVE) {
+ kfree(ovcs->overlay_mem);
+ kfree(ovcs->new_fdt);
+ }
kfree(ovcs);
}
@@ -868,28 +889,14 @@ static void free_overlay_changeset(struct overlay_changeset *ovcs)
* internal documentation
*
* of_overlay_apply() - Create and apply an overlay changeset
- * @fdt: the FDT that was unflattened to create @tree
- * @tree: Expanded overlay device tree
- * @ovcs_id: Pointer to overlay changeset id
+ * @ovcs: overlay changeset
+ * @base: point to the target node to apply overlay
*
* Creates and applies an overlay changeset.
*
- * If an error occurs in a pre-apply notifier, then no changes are made
- * to the device tree.
- *
-
- * A non-zero return value will not have created the changeset if error is from:
- * - parameter checks
- * - building the changeset
- * - overlay changeset pre-apply notifier
- *
* If an error is returned by an overlay changeset pre-apply notifier
* then no further overlay changeset pre-apply notifier will be called.
*
- * A non-zero return value will have created the changeset if error is from:
- * - overlay changeset entry notifier
- * - overlay changeset post-apply notifier
- *
* If an error is returned by an overlay changeset post-apply notifier
* then no further overlay changeset post-apply notifier will be called.
*
@@ -903,62 +910,30 @@ static void free_overlay_changeset(struct overlay_changeset *ovcs)
* following attempt to apply or remove an overlay changeset will be
* refused.
*
- * Returns 0 on success, or a negative error number. Overlay changeset
- * id is returned to *ovcs_id.
+ * Returns 0 on success, or a negative error number. On error return,
+ * the caller of of_overlay_apply() must call free_overlay_changeset().
*/
-static int of_overlay_apply(const void *fdt, struct device_node *tree,
- int *ovcs_id)
+static int of_overlay_apply(struct overlay_changeset *ovcs,
+ const struct device_node *base)
{
- struct overlay_changeset *ovcs;
int ret = 0, ret_revert, ret_tmp;
- /*
- * As of this point, fdt and tree belong to the overlay changeset.
- * overlay changeset code is responsible for freeing them.
- */
-
- if (devicetree_corrupt()) {
- pr_err("devicetree state suspect, refuse to apply overlay\n");
- kfree(fdt);
- kfree(tree);
- ret = -EBUSY;
- goto out;
- }
-
- ovcs = kzalloc(sizeof(*ovcs), GFP_KERNEL);
- if (!ovcs) {
- kfree(fdt);
- kfree(tree);
- ret = -ENOMEM;
- goto out;
- }
-
- of_overlay_mutex_lock();
- mutex_lock(&of_mutex);
-
- ret = of_resolve_phandles(tree);
+ ret = of_resolve_phandles(ovcs->overlay_root);
if (ret)
- goto err_free_tree;
+ goto out;
- ret = init_overlay_changeset(ovcs, fdt, tree);
+ ret = init_overlay_changeset(ovcs, base);
if (ret)
- goto err_free_tree;
+ goto out;
- /*
- * after overlay_notify(), ovcs->overlay_tree related pointers may have
- * leaked to drivers, so can not kfree() tree, aka ovcs->overlay_tree;
- * and can not free fdt, aka ovcs->fdt
- */
ret = overlay_notify(ovcs, OF_OVERLAY_PRE_APPLY);
- if (ret) {
- pr_err("overlay changeset pre-apply notify error %d\n", ret);
- goto err_free_overlay_changeset;
- }
+ if (ret)
+ goto out;
ret = build_changeset(ovcs);
if (ret)
- goto err_free_overlay_changeset;
+ goto out;
ret_revert = 0;
ret = __of_changeset_apply_entries(&ovcs->cset, &ret_revert);
@@ -968,39 +943,18 @@ static int of_overlay_apply(const void *fdt, struct device_node *tree,
ret_revert);
devicetree_state_flags |= DTSF_APPLY_FAIL;
}
- goto err_free_overlay_changeset;
+ goto out;
}
- of_populate_phandle_cache();
-
ret = __of_changeset_apply_notify(&ovcs->cset);
if (ret)
pr_err("overlay apply changeset entry notify error %d\n", ret);
/* notify failure is not fatal, continue */
- list_add_tail(&ovcs->ovcs_list, &ovcs_list);
- *ovcs_id = ovcs->id;
-
ret_tmp = overlay_notify(ovcs, OF_OVERLAY_POST_APPLY);
- if (ret_tmp) {
- pr_err("overlay changeset post-apply notify error %d\n",
- ret_tmp);
+ if (ret_tmp)
if (!ret)
ret = ret_tmp;
- }
-
- goto out_unlock;
-
-err_free_tree:
- kfree(fdt);
- kfree(tree);
-
-err_free_overlay_changeset:
- free_overlay_changeset(ovcs);
-
-out_unlock:
- mutex_unlock(&of_mutex);
- of_overlay_mutex_unlock();
out:
pr_debug("%s() err=%d\n", __func__, ret);
@@ -1008,16 +962,42 @@ out:
return ret;
}
+/**
+ * of_overlay_fdt_apply() - Create and apply an overlay changeset
+ * @overlay_fdt: pointer to overlay FDT
+ * @overlay_fdt_size: number of bytes in @overlay_fdt
+ * @ret_ovcs_id: pointer for returning created changeset id
+ * @base: pointer for the target node to apply overlay
+ *
+ * Creates and applies an overlay changeset.
+ *
+ * See of_overlay_apply() for important behavior information.
+ *
+ * Return: 0 on success, or a negative error number. *@ret_ovcs_id is set to
+ * the value of overlay changeset id, which can be passed to of_overlay_remove()
+ * to remove the overlay.
+ *
+ * On error return, the changeset may be partially applied. This is especially
+ * likely if an OF_OVERLAY_POST_APPLY notifier returns an error. In this case
+ * the caller should call of_overlay_remove() with the value in *@ret_ovcs_id.
+ */
+
int of_overlay_fdt_apply(const void *overlay_fdt, u32 overlay_fdt_size,
- int *ovcs_id)
+ int *ret_ovcs_id, const struct device_node *base)
{
- const void *new_fdt;
+ void *new_fdt;
+ void *new_fdt_align;
+ void *overlay_mem;
int ret;
u32 size;
- struct device_node *overlay_root;
+ struct overlay_changeset *ovcs;
- *ovcs_id = 0;
- ret = 0;
+ *ret_ovcs_id = 0;
+
+ if (devicetree_corrupt()) {
+ pr_err("devicetree state suspect, refuse to apply overlay\n");
+ return -EBUSY;
+ }
if (overlay_fdt_size < sizeof(struct fdt_header) ||
fdt_check_header(overlay_fdt)) {
@@ -1029,38 +1009,67 @@ int of_overlay_fdt_apply(const void *overlay_fdt, u32 overlay_fdt_size,
if (overlay_fdt_size < size)
return -EINVAL;
+ ovcs = kzalloc(sizeof(*ovcs), GFP_KERNEL);
+ if (!ovcs)
+ return -ENOMEM;
+
+ of_overlay_mutex_lock();
+ mutex_lock(&of_mutex);
+
+ /*
+ * ovcs->notify_state must be set to OF_OVERLAY_INIT before allocating
+ * ovcs resources, implicitly set by kzalloc() of ovcs
+ */
+
+ ovcs->id = idr_alloc(&ovcs_idr, ovcs, 1, 0, GFP_KERNEL);
+ if (ovcs->id <= 0) {
+ ret = ovcs->id;
+ goto err_free_ovcs;
+ }
+
+ INIT_LIST_HEAD(&ovcs->ovcs_list);
+ list_add_tail(&ovcs->ovcs_list, &ovcs_list);
+ of_changeset_init(&ovcs->cset);
+
/*
* Must create permanent copy of FDT because of_fdt_unflatten_tree()
* will create pointers to the passed in FDT in the unflattened tree.
*/
- new_fdt = kmemdup(overlay_fdt, size, GFP_KERNEL);
- if (!new_fdt)
- return -ENOMEM;
+ new_fdt = kmalloc(size + FDT_ALIGN_SIZE, GFP_KERNEL);
+ if (!new_fdt) {
+ ret = -ENOMEM;
+ goto err_free_ovcs;
+ }
+ ovcs->new_fdt = new_fdt;
- of_fdt_unflatten_tree(new_fdt, NULL, &overlay_root);
- if (!overlay_root) {
+ new_fdt_align = PTR_ALIGN(new_fdt, FDT_ALIGN_SIZE);
+ memcpy(new_fdt_align, overlay_fdt, size);
+
+ overlay_mem = of_fdt_unflatten_tree(new_fdt_align, NULL,
+ &ovcs->overlay_root);
+ if (!overlay_mem) {
pr_err("unable to unflatten overlay_fdt\n");
ret = -EINVAL;
- goto out_free_new_fdt;
+ goto err_free_ovcs;
}
+ ovcs->overlay_mem = overlay_mem;
- ret = of_overlay_apply(new_fdt, overlay_root, ovcs_id);
- if (ret < 0) {
- /*
- * new_fdt and overlay_root now belong to the overlay
- * changeset.
- * overlay changeset code is responsible for freeing them.
- */
- goto out;
- }
-
- return 0;
-
+ ret = of_overlay_apply(ovcs, base);
+ /*
+ * If of_overlay_apply() error, calling free_overlay_changeset() may
+ * result in a memory leak if the apply partly succeeded, so do NOT
+ * goto err_free_ovcs. Instead, the caller of of_overlay_fdt_apply()
+ * can call of_overlay_remove();
+ */
+ *ret_ovcs_id = ovcs->id;
+ goto out_unlock;
-out_free_new_fdt:
- kfree(new_fdt);
+err_free_ovcs:
+ free_overlay_changeset(ovcs);
-out:
+out_unlock:
+ mutex_unlock(&of_mutex);
+ of_overlay_mutex_unlock();
return ret;
}
EXPORT_SYMBOL_GPL(of_overlay_fdt_apply);
@@ -1070,18 +1079,14 @@ EXPORT_SYMBOL_GPL(of_overlay_fdt_apply);
*
* Returns 1 if @np is @tree or is contained in @tree, else 0
*/
-static int find_node(struct device_node *tree, struct device_node *np)
+static int find_node(const struct device_node *tree, struct device_node *np)
{
- struct device_node *child;
-
if (tree == np)
return 1;
- for_each_child_of_node(tree, child) {
- if (find_node(child, np)) {
- of_node_put(child);
+ for_each_child_of_node_scoped(tree, child) {
+ if (find_node(child, np))
return 1;
- }
}
return 0;
@@ -1130,7 +1135,7 @@ static int node_overlaps_later_cs(struct overlay_changeset *remove_ovcs,
* The topmost check is done by exploiting this property. For each
* affected device node in the log list we check if this overlay is
* the one closest to the tail. If another overlay has affected this
- * device node and is closest to the tail, then removal is not permited.
+ * device node and is closest to the tail, then removal is not permitted.
*/
static int overlay_removal_is_ok(struct overlay_changeset *remove_ovcs)
{
@@ -1177,7 +1182,7 @@ static int overlay_removal_is_ok(struct overlay_changeset *remove_ovcs)
* If an error is returned by an overlay changeset post-remove notifier
* then no further overlay changeset post-remove notifier will be called.
*
- * Returns 0 on success, or a negative error number. *ovcs_id is set to
+ * Return: 0 on success, or a negative error number. *@ovcs_id is set to
* zero after reverting the changeset, even if a subsequent error occurs.
*/
int of_overlay_remove(int *ovcs_id)
@@ -1185,7 +1190,8 @@ int of_overlay_remove(int *ovcs_id)
struct overlay_changeset *ovcs;
int ret, ret_apply, ret_tmp;
- ret = 0;
+ if (*ovcs_id == 0)
+ return 0;
if (devicetree_corrupt()) {
pr_err("suspect devicetree state, refuse to remove overlay\n");
@@ -1199,37 +1205,24 @@ int of_overlay_remove(int *ovcs_id)
if (!ovcs) {
ret = -ENODEV;
pr_err("remove: Could not find overlay #%d\n", *ovcs_id);
- goto out_unlock;
+ goto err_unlock;
}
if (!overlay_removal_is_ok(ovcs)) {
ret = -EBUSY;
- goto out_unlock;
+ goto err_unlock;
}
ret = overlay_notify(ovcs, OF_OVERLAY_PRE_REMOVE);
- if (ret) {
- pr_err("overlay changeset pre-remove notify error %d\n", ret);
- goto out_unlock;
- }
-
- list_del(&ovcs->ovcs_list);
-
- /*
- * Disable phandle cache. Avoids race condition that would arise
- * from removing cache entry when the associated node is deleted.
- */
- of_free_phandle_cache();
+ if (ret)
+ goto err_unlock;
ret_apply = 0;
ret = __of_changeset_revert_entries(&ovcs->cset, &ret_apply);
-
- of_populate_phandle_cache();
-
if (ret) {
if (ret_apply)
devicetree_state_flags |= DTSF_REVERT_FAIL;
- goto out_unlock;
+ goto err_unlock;
}
ret = __of_changeset_revert_notify(&ovcs->cset);
@@ -1239,17 +1232,24 @@ int of_overlay_remove(int *ovcs_id)
*ovcs_id = 0;
+ /*
+ * Note that the overlay memory will be kfree()ed by
+ * free_overlay_changeset() even if the notifier for
+ * OF_OVERLAY_POST_REMOVE returns an error.
+ */
ret_tmp = overlay_notify(ovcs, OF_OVERLAY_POST_REMOVE);
- if (ret_tmp) {
- pr_err("overlay changeset post-remove notify error %d\n",
- ret_tmp);
+ if (ret_tmp)
if (!ret)
ret = ret_tmp;
- }
free_overlay_changeset(ovcs);
-out_unlock:
+err_unlock:
+ /*
+ * If jumped over free_overlay_changeset(), then did not kfree()
+ * overlay related memory. This is a memory leak unless a subsequent
+ * of_overlay_remove() of this overlay is successful.
+ */
mutex_unlock(&of_mutex);
out:
@@ -1264,7 +1264,7 @@ EXPORT_SYMBOL_GPL(of_overlay_remove);
*
* Removes all overlays from the system in the correct order.
*
- * Returns 0 on success, or a negative error number
+ * Return: 0 on success, or a negative error number
*/
int of_overlay_remove_all(void)
{
diff --git a/drivers/of/overlay_test.c b/drivers/of/overlay_test.c
new file mode 100644
index 000000000000..c787524c5a88
--- /dev/null
+++ b/drivers/of/overlay_test.c
@@ -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");
diff --git a/drivers/of/pdt.c b/drivers/of/pdt.c
index 7eda43c66c91..cb0cb374b21f 100644
--- a/drivers/of/pdt.c
+++ b/drivers/of/pdt.c
@@ -19,6 +19,8 @@
#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)
diff --git a/drivers/of/platform.c b/drivers/of/platform.c
index 04ad312fd85b..f77cb19973a5 100644
--- a/drivers/of/platform.c
+++ b/drivers/of/platform.c
@@ -17,30 +17,12 @@
#include <linux/slab.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
-#include <linux/of_iommu.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
+#include <linux/sysfb.h>
-const struct of_device_id of_default_bus_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 */
-};
-
-static const struct of_device_id of_skipped_node_table[] = {
- { .compatible = "operating-points-v2", },
- {} /* Empty terminated list */
-};
-
-static int of_dev_node_match(struct device *dev, void *data)
-{
- return dev->of_node == data;
-}
+#include "of_private.h"
/**
* of_find_device_by_node - Find the platform_device associated with a node
@@ -49,18 +31,55 @@ static int of_dev_node_match(struct device *dev, void *data)
* Takes a reference to the embedded struct device which needs to be dropped
* after use.
*
- * Returns platform_device pointer, or NULL if not found
+ * 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(&platform_bus_type, NULL, np, of_dev_node_match);
+ 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.
@@ -70,41 +89,6 @@ EXPORT_SYMBOL(of_find_device_by_node);
*/
/**
- * 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.
- */
-static void of_device_make_bus_id(struct device *dev)
-{
- struct device_node *node = dev->of_node;
- const __be32 *reg;
- u64 addr;
-
- /* 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) {
- dev_set_name(dev, dev_name(dev) ? "%llx.%pOFn:%s" : "%llx.%pOFn",
- (unsigned long long)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;
- }
-}
-
-/**
* 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.
@@ -115,39 +99,34 @@ struct platform_device *of_device_alloc(struct device_node *np,
struct device *parent)
{
struct platform_device *dev;
- int rc, i, num_reg = 0, num_irq;
- struct resource *res, temp_res;
+ int rc, i, num_reg = 0;
+ struct resource *res;
dev = platform_device_alloc("", PLATFORM_DEVID_NONE);
if (!dev)
return NULL;
- /* count the io and irq resources */
- while (of_address_to_resource(np, num_reg, &temp_res) == 0)
- num_reg++;
- num_irq = of_irq_count(np);
+ /* count the io resources */
+ num_reg = of_address_count(np);
/* Populate the resource table */
- if (num_irq || num_reg) {
- res = kcalloc(num_irq + num_reg, sizeof(*res), GFP_KERNEL);
+ if (num_reg) {
+ res = kcalloc(num_reg, sizeof(*res), GFP_KERNEL);
if (!res) {
platform_device_put(dev);
return NULL;
}
- dev->num_resources = num_reg + num_irq;
+ 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);
}
- if (of_irq_to_resource_table(np, res, num_irq) != num_irq)
- pr_debug("not all legacy IRQ resources mapped for %pOFn\n",
- np);
}
- dev->dev.of_node = of_node_get(np);
- dev->dev.fwnode = &np->fwnode;
+ /* 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)
@@ -166,7 +145,7 @@ EXPORT_SYMBOL(of_device_alloc);
* @platform_data: pointer to populate platform_data pointer with
* @parent: Linux device model parent device.
*
- * Returns pointer to created platform device, or NULL if a device was not
+ * 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(
@@ -177,6 +156,8 @@ static struct platform_device *of_platform_device_create_pdata(
{
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;
@@ -210,7 +191,7 @@ err_clear_flag:
* @bus_id: name to assign device
* @parent: Linux device model parent device.
*
- * Returns pointer to created platform device, or NULL if a device was not
+ * 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,
@@ -228,8 +209,7 @@ static struct amba_device *of_amba_device_create(struct device_node *node,
struct device *parent)
{
struct amba_device *dev;
- const void *prop;
- int i, ret;
+ int ret;
pr_debug("Creating amba device %pOF\n", node);
@@ -246,8 +226,7 @@ static struct amba_device *of_amba_device_create(struct device_node *node,
dev->dev.dma_mask = &dev->dev.coherent_dma_mask;
/* setup generic device info */
- dev->dev.of_node = of_node_get(node);
- dev->dev.fwnode = &node->fwnode;
+ device_set_node(&dev->dev, of_fwnode_handle(node));
dev->dev.parent = parent ? : &platform_bus;
dev->dev.platform_data = platform_data;
if (bus_id)
@@ -256,13 +235,7 @@ static struct amba_device *of_amba_device_create(struct device_node *node,
of_device_make_bus_id(&dev->dev);
/* Allow the HW Peripheral ID to be overridden */
- prop = of_get_property(node, "arm,primecell-periphid", NULL);
- if (prop)
- dev->periphid = of_read_ulong(prop, 1);
-
- /* Decode the IRQs and address ranges */
- for (i = 0; i < AMBA_NR_IRQS; i++)
- dev->irq[i] = irq_of_parse_and_map(node, i);
+ of_property_read_u32(node, "arm,primecell-periphid", &dev->periphid);
ret = of_address_to_resource(node, 0, &dev->res);
if (ret) {
@@ -296,8 +269,8 @@ static struct amba_device *of_amba_device_create(struct device_node *node,
}
#endif /* CONFIG_ARM_AMBA */
-/**
- * of_devname_lookup() - Given a device node, lookup the preferred Linux name
+/*
+ * 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)
@@ -355,14 +328,13 @@ static int of_platform_bus_create(struct device_node *bus,
struct device *parent, bool strict)
{
const struct of_dev_auxdata *auxdata;
- struct device_node *child;
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_get_property(bus, "compatible", NULL))) {
+ if (strict && (!of_property_present(bus, "compatible"))) {
pr_debug("%s() - skipping %pOF, no compatible prop\n",
__func__, bus);
return 0;
@@ -399,13 +371,11 @@ static int of_platform_bus_create(struct device_node *bus,
if (!dev || !of_match_node(matches, bus))
return 0;
- for_each_child_of_node(bus, child) {
+ 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) {
- of_node_put(child);
+ if (rc)
break;
- }
}
of_node_set_flag(bus, OF_POPULATED_BUS);
return rc;
@@ -469,14 +439,13 @@ EXPORT_SYMBOL(of_platform_bus_probe);
* New board support should be using this function instead of
* of_platform_bus_probe().
*
- * Returns 0 on success, < 0 on failure.
+ * 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)
{
- struct device_node *child;
int rc = 0;
root = root ? of_node_get(root) : of_find_node_by_path("/");
@@ -486,13 +455,14 @@ int of_platform_populate(struct device_node *root,
pr_debug("%s()\n", __func__);
pr_debug(" starting at: %pOF\n", root);
- for_each_child_of_node(root, child) {
+ 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) {
- of_node_put(child);
+ if (rc)
break;
- }
}
+ device_links_supplier_sync_state_resume();
+
of_node_set_flag(root, OF_POPULATED_BUS);
of_node_put(root);
@@ -504,16 +474,28 @@ int of_platform_default_populate(struct device_node *root,
const struct of_dev_auxdata *lookup,
struct device *parent)
{
- return of_platform_populate(root, of_default_bus_match_table, lookup,
- 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);
-#ifndef CONFIG_PPC
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" },
{}
};
@@ -521,30 +503,107 @@ static int __init of_platform_default_populate_init(void)
{
struct device_node *node;
- if (!of_have_populated_dt())
- return -ENODEV;
+ 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;
+ }
+ }
- /*
- * Handle certain compatibles explicitly, since we don't want to create
- * platform_devices for every node in /reserved-memory with a
- * "compatible",
- */
- for_each_matching_node(node, reserved_mem_matches)
- of_platform_device_create(node, NULL, NULL);
+ /*
+ * 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";
- node = of_find_node_by_path("/firmware");
- if (node) {
- of_platform_populate(node, NULL, NULL, NULL);
- of_node_put(node);
- }
+ 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",
+ */
+ for_each_matching_node(node, reserved_mem_matches)
+ of_platform_device_create(node, NULL, NULL);
+
+ node = of_find_node_by_path("/firmware");
+ if (node) {
+ of_platform_populate(node, NULL, 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);
+ /* Populate everything else. */
+ of_platform_default_populate(NULL, NULL, NULL);
+ }
return 0;
}
arch_initcall_sync(of_platform_default_populate_init);
-#endif
+
+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)
{
@@ -575,14 +634,14 @@ EXPORT_SYMBOL_GPL(of_platform_device_destroy);
* @parent: device which children will be removed
*
* Complementary to of_platform_populate(), this function removes children
- * of the given device (and, recurrently, their children) that have been
+ * 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(parent, NULL, of_platform_device_destroy);
+ device_for_each_child_reverse(parent, NULL, of_platform_device_destroy);
of_node_clear_flag(parent->of_node, OF_POPULATED_BUS);
}
}
@@ -600,7 +659,7 @@ static void devm_of_platform_populate_release(struct device *dev, void *res)
* Similar to of_platform_populate(), but will automatically call
* of_platform_depopulate() when the device is unbound from the bus.
*
- * Returns 0 on success, < 0 on failure.
+ * Return: 0 on success, < 0 on failure.
*/
int devm_of_platform_populate(struct device *dev)
{
@@ -644,7 +703,7 @@ static int devm_of_platform_match(struct device *dev, void *res, void *data)
* @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, recurrently, their children) that have been
+ * 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).
*/
@@ -666,22 +725,30 @@ static int of_platform_notify(struct notifier_block *nb,
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:
- /* verify that the parent is a bus */
- if (!of_node_check_flag(rd->dn->parent, OF_POPULATED_BUS))
+ 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;
+ /*
+ * Clear the flag before adding the device so that fw_devlink
+ * doesn't skip adding consumers to this device.
+ */
+ rd->dn->fwnode.flags &= ~FWNODE_FLAG_NOT_DEVICE;
/* pdev_parent may be NULL when no bus platform device */
- pdev_parent = of_find_device_by_node(rd->dn->parent);
+ pdev_parent = of_find_device_by_node(parent);
pdev = of_platform_device_create(rd->dn, NULL,
pdev_parent ? &pdev_parent->dev : NULL);
- of_dev_put(pdev_parent);
+ platform_device_put(pdev_parent);
if (pdev == NULL) {
pr_err("%s: failed to create for '%pOF'\n",
@@ -706,7 +773,7 @@ static int of_platform_notify(struct notifier_block *nb,
of_platform_device_destroy(&pdev->dev, &children_left);
/* and put the reference of the find */
- of_dev_put(pdev);
+ platform_device_put(pdev);
break;
}
diff --git a/drivers/of/property.c b/drivers/of/property.c
index 8631efa1daa1..c1feb631e383 100644
--- a/drivers/of/property.c
+++ b/drivers/of/property.c
@@ -22,13 +22,62 @@
#define pr_fmt(fmt) "OF: " fmt
#include <linux/of.h>
+#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_graph.h>
+#include <linux/of_irq.h>
#include <linux/string.h>
+#include <linux/moduleparam.h>
#include "of_private.h"
/**
+ * of_property_read_bool - Find a property
+ * @np: device node from which the property value is to be read.
+ * @propname: name of the property to be searched.
+ *
+ * Search for a boolean property in a device node. Usage on non-boolean
+ * property types is deprecated.
+ *
+ * Return: true if the property exists false otherwise.
+ */
+bool of_property_read_bool(const struct device_node *np, const char *propname)
+{
+ struct property *prop = of_find_property(np, propname, NULL);
+
+ /*
+ * Boolean properties should not have a value. Testing for property
+ * presence should either use of_property_present() or just read the
+ * property value and check the returned error code.
+ */
+ if (prop && prop->length)
+ pr_warn("%pOF: Read of boolean property '%s' with a value.\n", np, propname);
+
+ return prop ? true : false;
+}
+EXPORT_SYMBOL(of_property_read_bool);
+
+/**
+ * of_graph_is_present() - check graph's presence
+ * @node: pointer to device_node containing graph port
+ *
+ * Return: True if @node has a port or ports (with a port) sub-node,
+ * false otherwise.
+ */
+bool of_graph_is_present(const struct device_node *node)
+{
+ struct device_node *ports __free(device_node) = of_get_child_by_name(node, "ports");
+
+ if (ports)
+ node = ports;
+
+ struct device_node *port __free(device_node) = of_get_child_by_name(node, "port");
+
+ return !!port;
+}
+EXPORT_SYMBOL(of_graph_is_present);
+
+/**
* of_property_count_elems_of_size - Count the number of elements in a property
*
* @np: device node from which the property value is to be read.
@@ -36,14 +85,16 @@
* @elem_size: size of the individual element
*
* Search for a property in a device node and count the number of elements of
- * size elem_size in it. Returns number of elements on sucess, -EINVAL if the
- * property does not exist or its length does not match a multiple of elem_size
- * and -ENODATA if the property does not have a value.
+ * size elem_size in it.
+ *
+ * Return: The number of elements on sucess, -EINVAL if the property does not
+ * exist or its length does not match a multiple of elem_size and -ENODATA if
+ * the property does not have a value.
*/
int of_property_count_elems_of_size(const struct device_node *np,
const char *propname, int elem_size)
{
- struct property *prop = of_find_property(np, propname, NULL);
+ const struct property *prop = of_find_property(np, propname, NULL);
if (!prop)
return -EINVAL;
@@ -70,15 +121,16 @@ EXPORT_SYMBOL_GPL(of_property_count_elems_of_size);
* @len: if !=NULL, actual length is written to here
*
* Search for a property in a device node and valid the requested size.
- * Returns the property value on success, -EINVAL if the property does not
- * exist, -ENODATA if property does not have a value, and -EOVERFLOW if the
+ *
+ * Return: The property value on success, -EINVAL if the property does not
+ * exist, -ENODATA if property does not have a value, and -EOVERFLOW if the
* property data is too small or too large.
*
*/
static void *of_find_property_value_of_size(const struct device_node *np,
const char *propname, u32 min, u32 max, size_t *len)
{
- struct property *prop = of_find_property(np, propname, NULL);
+ const struct property *prop = of_find_property(np, propname, NULL);
if (!prop)
return ERR_PTR(-EINVAL);
@@ -96,6 +148,39 @@ static void *of_find_property_value_of_size(const struct device_node *np,
}
/**
+ * of_property_read_u16_index - Find and read a u16 from a multi-value property.
+ *
+ * @np: device node from which the property value is to be read.
+ * @propname: name of the property to be searched.
+ * @index: index of the u16 in the list of values
+ * @out_value: pointer to return value, modified only if no error.
+ *
+ * Search for a property in a device node and read nth 16-bit value from
+ * it.
+ *
+ * Return: 0 on success, -EINVAL if the property does not exist,
+ * -ENODATA if property does not have a value, and -EOVERFLOW if the
+ * property data isn't large enough.
+ *
+ * The out_value is modified only if a valid u16 value can be decoded.
+ */
+int of_property_read_u16_index(const struct device_node *np,
+ const char *propname,
+ u32 index, u16 *out_value)
+{
+ const u16 *val = of_find_property_value_of_size(np, propname,
+ ((index + 1) * sizeof(*out_value)),
+ 0, NULL);
+
+ if (IS_ERR(val))
+ return PTR_ERR(val);
+
+ *out_value = be16_to_cpup(((__be16 *)val) + index);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(of_property_read_u16_index);
+
+/**
* of_property_read_u32_index - Find and read a u32 from a multi-value property.
*
* @np: device node from which the property value is to be read.
@@ -104,7 +189,9 @@ static void *of_find_property_value_of_size(const struct device_node *np,
* @out_value: pointer to return value, modified only if no error.
*
* Search for a property in a device node and read nth 32-bit value from
- * it. Returns 0 on success, -EINVAL if the property does not exist,
+ * it.
+ *
+ * Return: 0 on success, -EINVAL if the property does not exist,
* -ENODATA if property does not have a value, and -EOVERFLOW if the
* property data isn't large enough.
*
@@ -136,7 +223,9 @@ EXPORT_SYMBOL_GPL(of_property_read_u32_index);
* @out_value: pointer to return value, modified only if no error.
*
* Search for a property in a device node and read nth 64-bit value from
- * it. Returns 0 on success, -EINVAL if the property does not exist,
+ * it.
+ *
+ * Return: 0 on success, -EINVAL if the property does not exist,
* -ENODATA if property does not have a value, and -EOVERFLOW if the
* property data isn't large enough.
*
@@ -164,19 +253,21 @@ EXPORT_SYMBOL_GPL(of_property_read_u64_index);
*
* @np: device node from which the property value is to be read.
* @propname: name of the property to be searched.
- * @out_values: pointer to return value, modified only if return value is 0.
+ * @out_values: pointer to found values.
* @sz_min: minimum number of array elements to read
* @sz_max: maximum number of array elements to read, if zero there is no
* upper limit on the number of elements in the dts entry but only
* sz_min will be read.
*
* Search for a property in a device node and read 8-bit value(s) from
- * it. Returns number of elements read on success, -EINVAL if the property
- * does not exist, -ENODATA if property does not have a value, and -EOVERFLOW
- * if the property data is smaller than sz_min or longer than sz_max.
+ * it.
*
* dts entry of array should be like:
- * property = /bits/ 8 <0x50 0x60 0x70>;
+ * ``property = /bits/ 8 <0x50 0x60 0x70>;``
+ *
+ * Return: The number of elements read on success, -EINVAL if the property
+ * does not exist, -ENODATA if property does not have a value, and -EOVERFLOW
+ * if the property data is smaller than sz_min or longer than sz_max.
*
* The out_values is modified only if a valid u8 value can be decoded.
*/
@@ -212,19 +303,21 @@ EXPORT_SYMBOL_GPL(of_property_read_variable_u8_array);
*
* @np: device node from which the property value is to be read.
* @propname: name of the property to be searched.
- * @out_values: pointer to return value, modified only if return value is 0.
+ * @out_values: pointer to found values.
* @sz_min: minimum number of array elements to read
* @sz_max: maximum number of array elements to read, if zero there is no
* upper limit on the number of elements in the dts entry but only
* sz_min will be read.
*
* Search for a property in a device node and read 16-bit value(s) from
- * it. Returns number of elements read on success, -EINVAL if the property
- * does not exist, -ENODATA if property does not have a value, and -EOVERFLOW
- * if the property data is smaller than sz_min or longer than sz_max.
+ * it.
*
* dts entry of array should be like:
- * property = /bits/ 16 <0x5000 0x6000 0x7000>;
+ * ``property = /bits/ 16 <0x5000 0x6000 0x7000>;``
+ *
+ * Return: The number of elements read on success, -EINVAL if the property
+ * does not exist, -ENODATA if property does not have a value, and -EOVERFLOW
+ * if the property data is smaller than sz_min or longer than sz_max.
*
* The out_values is modified only if a valid u16 value can be decoded.
*/
@@ -260,14 +353,16 @@ EXPORT_SYMBOL_GPL(of_property_read_variable_u16_array);
*
* @np: device node from which the property value is to be read.
* @propname: name of the property to be searched.
- * @out_values: pointer to return value, modified only if return value is 0.
+ * @out_values: pointer to return found values.
* @sz_min: minimum number of array elements to read
* @sz_max: maximum number of array elements to read, if zero there is no
* upper limit on the number of elements in the dts entry but only
* sz_min will be read.
*
* Search for a property in a device node and read 32-bit value(s) from
- * it. Returns number of elements read on success, -EINVAL if the property
+ * it.
+ *
+ * Return: The number of elements read on success, -EINVAL if the property
* does not exist, -ENODATA if property does not have a value, and -EOVERFLOW
* if the property data is smaller than sz_min or longer than sz_max.
*
@@ -306,7 +401,9 @@ EXPORT_SYMBOL_GPL(of_property_read_variable_u32_array);
* @out_value: pointer to return value, modified only if return value is 0.
*
* Search for a property in a device node and read a 64-bit value from
- * it. Returns 0 on success, -EINVAL if the property does not exist,
+ * it.
+ *
+ * Return: 0 on success, -EINVAL if the property does not exist,
* -ENODATA if property does not have a value, and -EOVERFLOW if the
* property data isn't large enough.
*
@@ -334,14 +431,16 @@ EXPORT_SYMBOL_GPL(of_property_read_u64);
*
* @np: device node from which the property value is to be read.
* @propname: name of the property to be searched.
- * @out_values: pointer to return value, modified only if return value is 0.
+ * @out_values: pointer to found values.
* @sz_min: minimum number of array elements to read
* @sz_max: maximum number of array elements to read, if zero there is no
* upper limit on the number of elements in the dts entry but only
* sz_min will be read.
*
* Search for a property in a device node and read 64-bit value(s) from
- * it. Returns number of elements read on success, -EINVAL if the property
+ * it.
+ *
+ * Return: The number of elements read on success, -EINVAL if the property
* does not exist, -ENODATA if property does not have a value, and -EOVERFLOW
* if the property data is smaller than sz_min or longer than sz_max.
*
@@ -383,10 +482,14 @@ EXPORT_SYMBOL_GPL(of_property_read_variable_u64_array);
* return value is 0.
*
* Search for a property in a device tree node and retrieve a null
- * terminated string value (pointer to data, not a copy). Returns 0 on
- * success, -EINVAL if the property does not exist, -ENODATA if property
- * does not have a value, and -EILSEQ if the string is not null-terminated
- * within the length of the property data.
+ * terminated string value (pointer to data, not a copy).
+ *
+ * Return: 0 on success, -EINVAL if the property does not exist, -ENODATA if
+ * property does not have a value, and -EILSEQ if the string is not
+ * null-terminated within the length of the property data.
+ *
+ * Note that the empty string "" has length of 1, thus -ENODATA cannot
+ * be interpreted as an empty string.
*
* The out_string pointer is modified only if a valid string can be decoded.
*/
@@ -394,9 +497,10 @@ int of_property_read_string(const struct device_node *np, const char *propname,
const char **out_string)
{
const struct property *prop = of_find_property(np, propname, NULL);
+
if (!prop)
return -EINVAL;
- if (!prop->value)
+ if (!prop->length)
return -ENODATA;
if (strnlen(prop->value, prop->length) >= prop->length)
return -EILSEQ;
@@ -407,12 +511,17 @@ EXPORT_SYMBOL_GPL(of_property_read_string);
/**
* of_property_match_string() - Find string in a list and return index
- * @np: pointer to node containing string list property
+ * @np: pointer to the node containing the string list property
* @propname: string list property name
- * @string: pointer to string to search for in string list
+ * @string: pointer to the string to search for in the string list
+ *
+ * Search for an exact match of string in a device node property which is a
+ * string of lists.
*
- * This function searches a string list property and returns the index
- * of a specific string value.
+ * Return: the index of the first occurrence of the string on success, -EINVAL
+ * if the property does not exist, -ENODATA if the property does not have a
+ * value, and -EILSEQ if the string is not null-terminated within the length of
+ * the property data.
*/
int of_property_match_string(const struct device_node *np, const char *propname,
const char *string)
@@ -480,7 +589,7 @@ int of_property_read_string_helper(const struct device_node *np,
}
EXPORT_SYMBOL_GPL(of_property_read_string_helper);
-const __be32 *of_prop_next_u32(struct property *prop, const __be32 *cur,
+const __be32 *of_prop_next_u32(const struct property *prop, const __be32 *cur,
u32 *pu)
{
const void *curv = cur;
@@ -503,7 +612,7 @@ out_val:
}
EXPORT_SYMBOL_GPL(of_prop_next_u32);
-const char *of_prop_next_string(struct property *prop, const char *cur)
+const char *of_prop_next_string(const struct property *prop, const char *cur)
{
const void *curv = cur;
@@ -531,7 +640,8 @@ EXPORT_SYMBOL_GPL(of_prop_next_string);
int of_graph_parse_endpoint(const struct device_node *node,
struct of_endpoint *endpoint)
{
- struct device_node *port_node = of_get_parent(node);
+ struct device_node *port_node __free(device_node) =
+ of_get_parent(node);
WARN_ONCE(!port_node, "%s(): endpoint %pOF has no parent node\n",
__func__, node);
@@ -546,8 +656,6 @@ int of_graph_parse_endpoint(const struct device_node *node,
of_property_read_u32(port_node, "reg", &endpoint->port);
of_property_read_u32(node, "reg", &endpoint->id);
- of_node_put(port_node);
-
return 0;
}
EXPORT_SYMBOL(of_graph_parse_endpoint);
@@ -562,29 +670,90 @@ EXPORT_SYMBOL(of_graph_parse_endpoint);
*/
struct device_node *of_graph_get_port_by_id(struct device_node *parent, u32 id)
{
- struct device_node *node, *port;
+ struct device_node *node __free(device_node) = of_get_child_by_name(parent, "ports");
- node = of_get_child_by_name(parent, "ports");
if (node)
parent = node;
- for_each_child_of_node(parent, port) {
+ for_each_child_of_node_scoped(parent, port) {
u32 port_id = 0;
if (!of_node_name_eq(port, "port"))
continue;
of_property_read_u32(port, "reg", &port_id);
if (id == port_id)
- break;
+ return_ptr(port);
}
- of_node_put(node);
-
- return port;
+ return NULL;
}
EXPORT_SYMBOL(of_graph_get_port_by_id);
/**
+ * of_graph_get_next_port() - get next port node.
+ * @parent: pointer to the parent device node, or parent ports node
+ * @prev: previous port node, or NULL to get first
+ *
+ * Parent device node can be used as @parent whether device node has ports node
+ * or not. It will work same as ports@0 node.
+ *
+ * Return: A 'port' node pointer with refcount incremented. Refcount
+ * of the passed @prev node is decremented.
+ */
+struct device_node *of_graph_get_next_port(const struct device_node *parent,
+ struct device_node *prev)
+{
+ if (!parent)
+ return NULL;
+
+ if (!prev) {
+ struct device_node *node __free(device_node) =
+ of_get_child_by_name(parent, "ports");
+
+ if (node)
+ parent = node;
+
+ return of_get_child_by_name(parent, "port");
+ }
+
+ do {
+ prev = of_get_next_child(parent, prev);
+ if (!prev)
+ break;
+ } while (!of_node_name_eq(prev, "port"));
+
+ return prev;
+}
+EXPORT_SYMBOL(of_graph_get_next_port);
+
+/**
+ * of_graph_get_next_port_endpoint() - get next endpoint node in port.
+ * If it reached to end of the port, it will return NULL.
+ * @port: pointer to the target port node
+ * @prev: previous endpoint node, or NULL to get first
+ *
+ * Return: An 'endpoint' node pointer with refcount incremented. Refcount
+ * of the passed @prev node is decremented.
+ */
+struct device_node *of_graph_get_next_port_endpoint(const struct device_node *port,
+ struct device_node *prev)
+{
+ while (1) {
+ prev = of_get_next_child(port, prev);
+ if (!prev)
+ break;
+ if (WARN(!of_node_name_eq(prev, "endpoint"),
+ "non endpoint node is used (%pOF)", prev))
+ continue;
+
+ break;
+ }
+
+ return prev;
+}
+EXPORT_SYMBOL(of_graph_get_next_port_endpoint);
+
+/**
* of_graph_get_next_endpoint() - get next endpoint node
* @parent: pointer to the parent device node
* @prev: previous endpoint node, or NULL to get first
@@ -607,17 +776,9 @@ struct device_node *of_graph_get_next_endpoint(const struct device_node *parent,
* parent port node.
*/
if (!prev) {
- struct device_node *node;
-
- node = of_get_child_by_name(parent, "ports");
- if (node)
- parent = node;
-
- port = of_get_child_by_name(parent, "port");
- of_node_put(node);
-
+ port = of_graph_get_next_port(parent, NULL);
if (!port) {
- pr_err("graph: no port node found in %pOF\n", parent);
+ pr_debug("graph: no port node found in %pOF\n", parent);
return NULL;
}
} else {
@@ -633,7 +794,7 @@ struct device_node *of_graph_get_next_endpoint(const struct device_node *parent,
* getting the next child. If the previous endpoint is NULL this
* will return the first child.
*/
- endpoint = of_get_next_child(port, prev);
+ endpoint = of_graph_get_next_port_endpoint(port, prev);
if (endpoint) {
of_node_put(port);
return endpoint;
@@ -642,11 +803,9 @@ struct device_node *of_graph_get_next_endpoint(const struct device_node *parent,
/* No more endpoints under this port, try the next one. */
prev = NULL;
- do {
- port = of_get_next_child(parent, port);
- if (!port)
- return NULL;
- } while (!of_node_name_eq(port, "port"));
+ port = of_graph_get_next_port(parent, port);
+ if (!port)
+ return NULL;
}
}
EXPORT_SYMBOL(of_graph_get_next_endpoint);
@@ -659,7 +818,7 @@ EXPORT_SYMBOL(of_graph_get_next_endpoint);
*
* Return: An 'endpoint' node pointer which is identified by reg and at the same
* is the child of a port node identified by port_reg. reg and port_reg are
- * ignored when they are -1.
+ * ignored when they are -1. Use of_node_put() on the pointer when done.
*/
struct device_node *of_graph_get_endpoint_by_regs(
const struct device_node *parent, int port_reg, int reg)
@@ -715,7 +874,9 @@ struct device_node *of_graph_get_port_parent(struct device_node *node)
/* Walk 3 levels up only if there is 'ports' node. */
for (depth = 3; depth && node; depth--) {
node = of_get_next_parent(node);
- if (depth == 2 && !of_node_name_eq(node, "ports"))
+ if (depth == 2 && !of_node_name_eq(node, "ports") &&
+ !of_node_name_eq(node, "in-ports") &&
+ !of_node_name_eq(node, "out-ports"))
break;
}
return node;
@@ -732,16 +893,11 @@ EXPORT_SYMBOL(of_graph_get_port_parent);
struct device_node *of_graph_get_remote_port_parent(
const struct device_node *node)
{
- struct device_node *np, *pp;
-
/* Get remote endpoint node. */
- np = of_graph_get_remote_endpoint(node);
-
- pp = of_graph_get_port_parent(np);
+ struct device_node *np __free(device_node) =
+ of_graph_get_remote_endpoint(node);
- of_node_put(np);
-
- return pp;
+ return of_graph_get_port_parent(np);
}
EXPORT_SYMBOL(of_graph_get_remote_port_parent);
@@ -750,7 +906,7 @@ EXPORT_SYMBOL(of_graph_get_remote_port_parent);
* @node: pointer to a local endpoint device_node
*
* Return: Remote port node associated with remote endpoint node linked
- * to @node. Use of_node_put() on it when done.
+ * to @node. Use of_node_put() on it when done.
*/
struct device_node *of_graph_get_remote_port(const struct device_node *node)
{
@@ -764,10 +920,16 @@ struct device_node *of_graph_get_remote_port(const struct device_node *node)
}
EXPORT_SYMBOL(of_graph_get_remote_port);
-int of_graph_get_endpoint_count(const struct device_node *np)
+/**
+ * of_graph_get_endpoint_count() - get the number of endpoints in a device node
+ * @np: parent device node containing ports and endpoints
+ *
+ * Return: count of endpoint of this device node
+ */
+unsigned int of_graph_get_endpoint_count(const struct device_node *np)
{
struct device_node *endpoint;
- int num = 0;
+ unsigned int num = 0;
for_each_endpoint_of_node(np, endpoint)
num++;
@@ -777,13 +939,30 @@ int of_graph_get_endpoint_count(const struct device_node *np)
EXPORT_SYMBOL(of_graph_get_endpoint_count);
/**
+ * of_graph_get_port_count() - get the number of port in a device or ports node
+ * @np: pointer to the device or ports node
+ *
+ * Return: count of port of this device or ports node
+ */
+unsigned int of_graph_get_port_count(struct device_node *np)
+{
+ unsigned int num = 0;
+
+ for_each_of_graph_port(np, port)
+ num++;
+
+ return num;
+}
+EXPORT_SYMBOL(of_graph_get_port_count);
+
+/**
* of_graph_get_remote_node() - get remote parent device_node for given port/endpoint
* @node: pointer to parent device_node containing graph port/endpoint
* @port: identifier (value of reg property) of the parent port node
* @endpoint: identifier (value of reg property) of the endpoint node
*
* Return: Remote device node associated with remote endpoint node linked
- * to @node. Use of_node_put() on it when done.
+ * to @node. Use of_node_put() on it when done.
*/
struct device_node *of_graph_get_remote_node(const struct device_node *node,
u32 port, u32 endpoint)
@@ -829,9 +1008,29 @@ static bool of_fwnode_device_is_available(const struct fwnode_handle *fwnode)
return of_device_is_available(to_of_node(fwnode));
}
+static bool of_fwnode_device_dma_supported(const struct fwnode_handle *fwnode)
+{
+ return true;
+}
+
+static enum dev_dma_attr
+of_fwnode_device_get_dma_attr(const struct fwnode_handle *fwnode)
+{
+ if (of_dma_is_coherent(to_of_node(fwnode)))
+ return DEV_DMA_COHERENT;
+ else
+ return DEV_DMA_NON_COHERENT;
+}
+
static bool of_fwnode_property_present(const struct fwnode_handle *fwnode,
const char *propname)
{
+ return of_property_present(to_of_node(fwnode), propname);
+}
+
+static bool of_fwnode_property_read_bool(const struct fwnode_handle *fwnode,
+ const char *propname)
+{
return of_property_read_bool(to_of_node(fwnode), propname);
}
@@ -872,6 +1071,20 @@ of_fwnode_property_read_string_array(const struct fwnode_handle *fwnode,
of_property_count_strings(node, propname);
}
+static const char *of_fwnode_get_name(const struct fwnode_handle *fwnode)
+{
+ return kbasename(to_of_node(fwnode)->full_name);
+}
+
+static const char *of_fwnode_get_name_prefix(const struct fwnode_handle *fwnode)
+{
+ /* Root needs no prefix here (its name is "/"). */
+ if (!to_of_node(fwnode)->parent)
+ return "";
+
+ return "/";
+}
+
static struct fwnode_handle *
of_fwnode_get_parent(const struct fwnode_handle *fwnode)
{
@@ -918,8 +1131,10 @@ of_fwnode_get_reference_args(const struct fwnode_handle *fwnode,
nargs, index, &of_args);
if (ret < 0)
return ret;
- if (!args)
+ if (!args) {
+ of_node_put(of_args.np);
return 0;
+ }
args->nargs = of_args.args_count;
args->fwnode = of_fwnode_handle(of_args.np);
@@ -966,15 +1181,13 @@ static int of_fwnode_graph_parse_endpoint(const struct fwnode_handle *fwnode,
struct fwnode_endpoint *endpoint)
{
const struct device_node *node = to_of_node(fwnode);
- struct device_node *port_node = of_get_parent(node);
+ struct device_node *port_node __free(device_node) = of_get_parent(node);
endpoint->local_fwnode = fwnode;
of_property_read_u32(port_node, "reg", &endpoint->port);
of_property_read_u32(node, "reg", &endpoint->id);
- of_node_put(port_node);
-
return 0;
}
@@ -985,14 +1198,438 @@ of_fwnode_device_get_match_data(const struct fwnode_handle *fwnode,
return of_device_get_match_data(dev);
}
+static void of_link_to_phandle(struct device_node *con_np,
+ struct device_node *sup_np,
+ u8 flags)
+{
+ struct device_node *tmp_np __free(device_node) = of_node_get(sup_np);
+
+ /* Check that sup_np and its ancestors are available. */
+ while (tmp_np) {
+ if (of_fwnode_handle(tmp_np)->dev)
+ break;
+
+ if (!of_device_is_available(tmp_np))
+ return;
+
+ tmp_np = of_get_next_parent(tmp_np);
+ }
+
+ fwnode_link_add(of_fwnode_handle(con_np), of_fwnode_handle(sup_np), flags);
+}
+
+/**
+ * parse_prop_cells - Property parsing function for suppliers
+ *
+ * @np: Pointer to device tree node containing a list
+ * @prop_name: Name of property to be parsed. Expected to hold phandle values
+ * @index: For properties holding a list of phandles, this is the index
+ * into the list.
+ * @list_name: Property name that is known to contain list of phandle(s) to
+ * supplier(s)
+ * @cells_name: property name that specifies phandles' arguments count
+ *
+ * This is a helper function to parse properties that have a known fixed name
+ * and are a list of phandles and phandle arguments.
+ *
+ * Returns:
+ * - phandle node pointer with refcount incremented. Caller must of_node_put()
+ * on it when done.
+ * - NULL if no phandle found at index
+ */
+static struct device_node *parse_prop_cells(struct device_node *np,
+ const char *prop_name, int index,
+ const char *list_name,
+ const char *cells_name)
+{
+ struct of_phandle_args sup_args;
+
+ if (strcmp(prop_name, list_name))
+ return NULL;
+
+ if (__of_parse_phandle_with_args(np, list_name, cells_name, 0, index,
+ &sup_args))
+ return NULL;
+
+ return sup_args.np;
+}
+
+#define DEFINE_SIMPLE_PROP(fname, name, cells) \
+static struct device_node *parse_##fname(struct device_node *np, \
+ const char *prop_name, int index) \
+{ \
+ return parse_prop_cells(np, prop_name, index, name, cells); \
+}
+
+static int strcmp_suffix(const char *str, const char *suffix)
+{
+ unsigned int len, suffix_len;
+
+ len = strlen(str);
+ suffix_len = strlen(suffix);
+ if (len <= suffix_len)
+ return -1;
+ return strcmp(str + len - suffix_len, suffix);
+}
+
+/**
+ * parse_suffix_prop_cells - Suffix property parsing function for suppliers
+ *
+ * @np: Pointer to device tree node containing a list
+ * @prop_name: Name of property to be parsed. Expected to hold phandle values
+ * @index: For properties holding a list of phandles, this is the index
+ * into the list.
+ * @suffix: Property suffix that is known to contain list of phandle(s) to
+ * supplier(s)
+ * @cells_name: property name that specifies phandles' arguments count
+ *
+ * This is a helper function to parse properties that have a known fixed suffix
+ * and are a list of phandles and phandle arguments.
+ *
+ * Returns:
+ * - phandle node pointer with refcount incremented. Caller must of_node_put()
+ * on it when done.
+ * - NULL if no phandle found at index
+ */
+static struct device_node *parse_suffix_prop_cells(struct device_node *np,
+ const char *prop_name, int index,
+ const char *suffix,
+ const char *cells_name)
+{
+ struct of_phandle_args sup_args;
+
+ if (strcmp_suffix(prop_name, suffix))
+ return NULL;
+
+ if (of_parse_phandle_with_args(np, prop_name, cells_name, index,
+ &sup_args))
+ return NULL;
+
+ return sup_args.np;
+}
+
+#define DEFINE_SUFFIX_PROP(fname, suffix, cells) \
+static struct device_node *parse_##fname(struct device_node *np, \
+ const char *prop_name, int index) \
+{ \
+ return parse_suffix_prop_cells(np, prop_name, index, suffix, cells); \
+}
+
+/**
+ * struct supplier_bindings - Property parsing functions for suppliers
+ *
+ * @parse_prop: function name
+ * parse_prop() finds the node corresponding to a supplier phandle
+ * parse_prop.np: Pointer to device node holding supplier phandle property
+ * parse_prop.prop_name: Name of property holding a phandle value
+ * parse_prop.index: For properties holding a list of phandles, this is the
+ * index into the list
+ * @get_con_dev: If the consumer node containing the property is never converted
+ * to a struct device, implement this ops so fw_devlink can use it
+ * to find the true consumer.
+ * @optional: Describes whether a supplier is mandatory or not
+ * @fwlink_flags: Optional fwnode link flags to use when creating a fwnode link
+ * for this property.
+ *
+ * Returns:
+ * parse_prop() return values are
+ * - phandle node pointer with refcount incremented. Caller must of_node_put()
+ * on it when done.
+ * - NULL if no phandle found at index
+ */
+struct supplier_bindings {
+ struct device_node *(*parse_prop)(struct device_node *np,
+ const char *prop_name, int index);
+ struct device_node *(*get_con_dev)(struct device_node *np);
+ bool optional;
+ u8 fwlink_flags;
+};
+
+DEFINE_SIMPLE_PROP(clocks, "clocks", "#clock-cells")
+DEFINE_SIMPLE_PROP(interconnects, "interconnects", "#interconnect-cells")
+DEFINE_SIMPLE_PROP(iommus, "iommus", "#iommu-cells")
+DEFINE_SIMPLE_PROP(mboxes, "mboxes", "#mbox-cells")
+DEFINE_SIMPLE_PROP(io_channels, "io-channels", "#io-channel-cells")
+DEFINE_SIMPLE_PROP(io_backends, "io-backends", "#io-backend-cells")
+DEFINE_SIMPLE_PROP(dmas, "dmas", "#dma-cells")
+DEFINE_SIMPLE_PROP(power_domains, "power-domains", "#power-domain-cells")
+DEFINE_SIMPLE_PROP(hwlocks, "hwlocks", "#hwlock-cells")
+DEFINE_SIMPLE_PROP(extcon, "extcon", NULL)
+DEFINE_SIMPLE_PROP(nvmem_cells, "nvmem-cells", "#nvmem-cell-cells")
+DEFINE_SIMPLE_PROP(phys, "phys", "#phy-cells")
+DEFINE_SIMPLE_PROP(wakeup_parent, "wakeup-parent", NULL)
+DEFINE_SIMPLE_PROP(pinctrl0, "pinctrl-0", NULL)
+DEFINE_SIMPLE_PROP(pinctrl1, "pinctrl-1", NULL)
+DEFINE_SIMPLE_PROP(pinctrl2, "pinctrl-2", NULL)
+DEFINE_SIMPLE_PROP(pinctrl3, "pinctrl-3", NULL)
+DEFINE_SIMPLE_PROP(pinctrl4, "pinctrl-4", NULL)
+DEFINE_SIMPLE_PROP(pinctrl5, "pinctrl-5", NULL)
+DEFINE_SIMPLE_PROP(pinctrl6, "pinctrl-6", NULL)
+DEFINE_SIMPLE_PROP(pinctrl7, "pinctrl-7", NULL)
+DEFINE_SIMPLE_PROP(pinctrl8, "pinctrl-8", NULL)
+DEFINE_SIMPLE_PROP(pwms, "pwms", "#pwm-cells")
+DEFINE_SIMPLE_PROP(resets, "resets", "#reset-cells")
+DEFINE_SIMPLE_PROP(leds, "leds", NULL)
+DEFINE_SIMPLE_PROP(backlight, "backlight", NULL)
+DEFINE_SIMPLE_PROP(panel, "panel", NULL)
+DEFINE_SIMPLE_PROP(msi_parent, "msi-parent", "#msi-cells")
+DEFINE_SIMPLE_PROP(post_init_providers, "post-init-providers", NULL)
+DEFINE_SIMPLE_PROP(access_controllers, "access-controllers", "#access-controller-cells")
+DEFINE_SIMPLE_PROP(pses, "pses", "#pse-cells")
+DEFINE_SIMPLE_PROP(power_supplies, "power-supplies", NULL)
+DEFINE_SUFFIX_PROP(regulators, "-supply", NULL)
+DEFINE_SUFFIX_PROP(gpio, "-gpio", "#gpio-cells")
+
+static struct device_node *parse_gpios(struct device_node *np,
+ const char *prop_name, int index)
+{
+ if (!strcmp_suffix(prop_name, ",nr-gpios"))
+ return NULL;
+
+ return parse_suffix_prop_cells(np, prop_name, index, "-gpios",
+ "#gpio-cells");
+}
+
+static struct device_node *parse_iommu_maps(struct device_node *np,
+ const char *prop_name, int index)
+{
+ if (strcmp(prop_name, "iommu-map"))
+ return NULL;
+
+ return of_parse_phandle(np, prop_name, (index * 4) + 1);
+}
+
+static struct device_node *parse_gpio_compat(struct device_node *np,
+ const char *prop_name, int index)
+{
+ struct of_phandle_args sup_args;
+
+ if (strcmp(prop_name, "gpio") && strcmp(prop_name, "gpios"))
+ return NULL;
+
+ /*
+ * Ignore node with gpio-hog property since its gpios are all provided
+ * by its parent.
+ */
+ if (of_property_read_bool(np, "gpio-hog"))
+ return NULL;
+
+ if (of_parse_phandle_with_args(np, prop_name, "#gpio-cells", index,
+ &sup_args))
+ return NULL;
+
+ return sup_args.np;
+}
+
+static struct device_node *parse_interrupts(struct device_node *np,
+ const char *prop_name, int index)
+{
+ struct of_phandle_args sup_args;
+
+ if (!IS_ENABLED(CONFIG_OF_IRQ) || IS_ENABLED(CONFIG_PPC))
+ return NULL;
+
+ if (strcmp(prop_name, "interrupts") &&
+ strcmp(prop_name, "interrupts-extended"))
+ return NULL;
+
+ return of_irq_parse_one(np, index, &sup_args) ? NULL : sup_args.np;
+}
+
+static struct device_node *parse_interrupt_map(struct device_node *np,
+ const char *prop_name, int index)
+{
+ const __be32 *imap, *imap_end;
+ struct of_phandle_args sup_args;
+ u32 addrcells, intcells;
+ int imaplen;
+
+ if (!IS_ENABLED(CONFIG_OF_IRQ))
+ return NULL;
+
+ if (strcmp(prop_name, "interrupt-map"))
+ return NULL;
+
+ if (of_property_read_u32(np, "#interrupt-cells", &intcells))
+ return NULL;
+ addrcells = of_bus_n_addr_cells(np);
+
+ imap = of_get_property(np, "interrupt-map", &imaplen);
+ if (!imap)
+ return NULL;
+ imaplen /= sizeof(*imap);
+
+ imap_end = imap + imaplen;
+
+ for (int i = 0; imap + addrcells + intcells + 1 < imap_end; i++) {
+ imap += addrcells + intcells;
+
+ imap = of_irq_parse_imap_parent(imap, imap_end - imap, &sup_args);
+ if (!imap)
+ return NULL;
+
+ if (i == index)
+ return sup_args.np;
+
+ of_node_put(sup_args.np);
+ }
+
+ return NULL;
+}
+
+static struct device_node *parse_remote_endpoint(struct device_node *np,
+ const char *prop_name,
+ int index)
+{
+ /* Return NULL for index > 0 to signify end of remote-endpoints. */
+ if (index > 0 || strcmp(prop_name, "remote-endpoint"))
+ return NULL;
+
+ return of_graph_get_remote_port_parent(np);
+}
+
+static const struct supplier_bindings of_supplier_bindings[] = {
+ { .parse_prop = parse_clocks, },
+ { .parse_prop = parse_interconnects, },
+ { .parse_prop = parse_iommus, .optional = true, },
+ { .parse_prop = parse_iommu_maps, .optional = true, },
+ { .parse_prop = parse_mboxes, },
+ { .parse_prop = parse_io_channels, },
+ { .parse_prop = parse_io_backends, },
+ { .parse_prop = parse_dmas, .optional = true, },
+ { .parse_prop = parse_power_domains, },
+ { .parse_prop = parse_hwlocks, },
+ { .parse_prop = parse_extcon, },
+ { .parse_prop = parse_nvmem_cells, },
+ { .parse_prop = parse_phys, },
+ { .parse_prop = parse_wakeup_parent, },
+ { .parse_prop = parse_pinctrl0, },
+ { .parse_prop = parse_pinctrl1, },
+ { .parse_prop = parse_pinctrl2, },
+ { .parse_prop = parse_pinctrl3, },
+ { .parse_prop = parse_pinctrl4, },
+ { .parse_prop = parse_pinctrl5, },
+ { .parse_prop = parse_pinctrl6, },
+ { .parse_prop = parse_pinctrl7, },
+ { .parse_prop = parse_pinctrl8, },
+ {
+ .parse_prop = parse_remote_endpoint,
+ .get_con_dev = of_graph_get_port_parent,
+ },
+ { .parse_prop = parse_pwms, },
+ { .parse_prop = parse_resets, },
+ { .parse_prop = parse_leds, },
+ { .parse_prop = parse_backlight, },
+ { .parse_prop = parse_panel, },
+ { .parse_prop = parse_msi_parent, },
+ { .parse_prop = parse_pses, },
+ { .parse_prop = parse_power_supplies, },
+ { .parse_prop = parse_gpio_compat, },
+ { .parse_prop = parse_interrupts, },
+ { .parse_prop = parse_interrupt_map, },
+ { .parse_prop = parse_access_controllers, },
+ { .parse_prop = parse_regulators, },
+ { .parse_prop = parse_gpio, },
+ { .parse_prop = parse_gpios, },
+ {
+ .parse_prop = parse_post_init_providers,
+ .fwlink_flags = FWLINK_FLAG_IGNORE,
+ },
+ {}
+};
+
+/**
+ * of_link_property - Create device links to suppliers listed in a property
+ * @con_np: The consumer device tree node which contains the property
+ * @prop_name: Name of property to be parsed
+ *
+ * This function checks if the property @prop_name that is present in the
+ * @con_np device tree node is one of the known common device tree bindings
+ * that list phandles to suppliers. If @prop_name isn't one, this function
+ * doesn't do anything.
+ *
+ * If @prop_name is one, this function attempts to create fwnode links from the
+ * consumer device tree node @con_np to all the suppliers device tree nodes
+ * listed in @prop_name.
+ *
+ * Any failed attempt to create a fwnode link will NOT result in an immediate
+ * return. of_link_property() must create links to all the available supplier
+ * device tree nodes even when attempts to create a link to one or more
+ * suppliers fail.
+ */
+static int of_link_property(struct device_node *con_np, const char *prop_name)
+{
+ struct device_node *phandle;
+ const struct supplier_bindings *s = of_supplier_bindings;
+ unsigned int i = 0;
+ bool matched = false;
+
+ /* Do not stop at first failed link, link all available suppliers. */
+ while (!matched && s->parse_prop) {
+ if (s->optional && !fw_devlink_is_strict()) {
+ s++;
+ continue;
+ }
+
+ while ((phandle = s->parse_prop(con_np, prop_name, i))) {
+ struct device_node *con_dev_np __free(device_node) =
+ s->get_con_dev ? s->get_con_dev(con_np) : of_node_get(con_np);
+
+ matched = true;
+ i++;
+ of_link_to_phandle(con_dev_np, phandle, s->fwlink_flags);
+ of_node_put(phandle);
+ }
+ s++;
+ }
+ return 0;
+}
+
+static void __iomem *of_fwnode_iomap(struct fwnode_handle *fwnode, int index)
+{
+#ifdef CONFIG_OF_ADDRESS
+ return of_iomap(to_of_node(fwnode), index);
+#else
+ return NULL;
+#endif
+}
+
+static int of_fwnode_irq_get(const struct fwnode_handle *fwnode,
+ unsigned int index)
+{
+ return of_irq_get(to_of_node(fwnode), index);
+}
+
+static int of_fwnode_add_links(struct fwnode_handle *fwnode)
+{
+ const struct property *p;
+ struct device_node *con_np = to_of_node(fwnode);
+
+ if (IS_ENABLED(CONFIG_X86))
+ return 0;
+
+ if (!con_np)
+ return -EINVAL;
+
+ for_each_property_of_node(con_np, p)
+ of_link_property(con_np, p->name);
+
+ return 0;
+}
+
const struct fwnode_operations of_fwnode_ops = {
.get = of_fwnode_get,
.put = of_fwnode_put,
.device_is_available = of_fwnode_device_is_available,
.device_get_match_data = of_fwnode_device_get_match_data,
+ .device_dma_supported = of_fwnode_device_dma_supported,
+ .device_get_dma_attr = of_fwnode_device_get_dma_attr,
.property_present = of_fwnode_property_present,
+ .property_read_bool = of_fwnode_property_read_bool,
.property_read_int_array = of_fwnode_property_read_int_array,
.property_read_string_array = of_fwnode_property_read_string_array,
+ .get_name = of_fwnode_get_name,
+ .get_name_prefix = of_fwnode_get_name_prefix,
.get_parent = of_fwnode_get_parent,
.get_next_child_node = of_fwnode_get_next_child_node,
.get_named_child_node = of_fwnode_get_named_child_node,
@@ -1001,5 +1638,8 @@ const struct fwnode_operations of_fwnode_ops = {
.graph_get_remote_endpoint = of_fwnode_graph_get_remote_endpoint,
.graph_get_port_parent = of_fwnode_graph_get_port_parent,
.graph_parse_endpoint = of_fwnode_graph_parse_endpoint,
+ .iomap = of_fwnode_iomap,
+ .irq_get = of_fwnode_irq_get,
+ .add_links = of_fwnode_add_links,
};
EXPORT_SYMBOL_GPL(of_fwnode_ops);
diff --git a/drivers/of/resolver.c b/drivers/of/resolver.c
index c1b67dd7cd6e..86424e145919 100644
--- a/drivers/of/resolver.c
+++ b/drivers/of/resolver.c
@@ -8,6 +8,7 @@
#define pr_fmt(fmt) "OF: resolver: " fmt
+#include <linux/cleanup.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
@@ -41,7 +42,7 @@ static void adjust_overlay_phandles(struct device_node *overlay,
int phandle_delta)
{
struct device_node *child;
- struct property *prop;
+ const struct property *prop;
phandle phandle;
/* adjust node's phandle in node */
@@ -70,15 +71,15 @@ static void adjust_overlay_phandles(struct device_node *overlay,
}
static int update_usages_of_a_phandle_reference(struct device_node *overlay,
- struct property *prop_fixup, phandle phandle)
+ const struct property *prop_fixup, phandle phandle)
{
struct device_node *refnode;
- struct property *prop;
- char *value, *cur, *end, *node_path, *prop_name, *s;
+ 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;
- value = kmemdup(prop_fixup->value, prop_fixup->length, GFP_KERNEL);
if (!value)
return -ENOMEM;
@@ -89,23 +90,19 @@ static int update_usages_of_a_phandle_reference(struct device_node *overlay,
node_path = cur;
s = strchr(cur, ':');
- if (!s) {
- err = -EINVAL;
- goto err_fail;
- }
+ if (!s)
+ return -EINVAL;
*s++ = '\0';
prop_name = s;
s = strchr(s, ':');
- if (!s) {
- err = -EINVAL;
- goto err_fail;
- }
+ if (!s)
+ return -EINVAL;
*s++ = '\0';
err = kstrtoint(s, 10, &offset);
if (err)
- goto err_fail;
+ return err;
refnode = __of_find_node_by_full_path(of_node_get(overlay), node_path);
if (!refnode)
@@ -117,22 +114,16 @@ static int update_usages_of_a_phandle_reference(struct device_node *overlay,
}
of_node_put(refnode);
- if (!prop) {
- err = -ENOENT;
- goto err_fail;
- }
+ if (!prop)
+ return -ENOENT;
- if (offset < 0 || offset + sizeof(__be32) > prop->length) {
- err = -EINVAL;
- goto err_fail;
- }
+ if (offset < 0 || offset + sizeof(__be32) > prop->length)
+ return -EINVAL;
*(__be32 *)(prop->value + offset) = cpu_to_be32(phandle);
}
-err_fail:
- kfree(value);
- return err;
+ return 0;
}
/* compare nodes taking into account that 'name' strips out the @ part */
@@ -156,11 +147,11 @@ static int node_name_cmp(const struct device_node *dn1,
* 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(struct device_node *local_fixups,
- struct device_node *overlay, int phandle_delta)
+static int adjust_local_phandle_references(const struct device_node *local_fixups,
+ const struct device_node *overlay, int phandle_delta)
{
- struct device_node *child, *overlay_child;
- struct property *prop_fix, *prop;
+ struct device_node *overlay_child;
+ const struct property *prop_fix, *prop;
int err, i, count;
unsigned int off;
@@ -170,9 +161,7 @@ static int adjust_local_phandle_references(struct device_node *local_fixups,
for_each_property_of_node(local_fixups, prop_fix) {
/* skip properties added automatically */
- if (!of_prop_cmp(prop_fix->name, "name") ||
- !of_prop_cmp(prop_fix->name, "phandle") ||
- !of_prop_cmp(prop_fix->name, "linux,phandle"))
+ if (is_pseudo_property(prop_fix->name))
continue;
if ((prop_fix->length % 4) != 0 || prop_fix->length == 0)
@@ -203,11 +192,13 @@ static int adjust_local_phandle_references(struct device_node *local_fixups,
* The roots of the subtrees are the overlay's __local_fixups__ node
* and the overlay's root node.
*/
- for_each_child_of_node(local_fixups, child) {
+ for_each_child_of_node_scoped(local_fixups, child) {
for_each_child_of_node(overlay, overlay_child)
- if (!node_name_cmp(child, overlay_child))
+ if (!node_name_cmp(child, overlay_child)) {
+ of_node_put(overlay_child);
break;
+ }
if (!overlay_child)
return -EINVAL;
@@ -256,25 +247,22 @@ static int adjust_local_phandle_references(struct device_node *local_fixups,
*/
int of_resolve_phandles(struct device_node *overlay)
{
- struct device_node *child, *local_fixups, *refnode;
- struct device_node *tree_symbols, *overlay_fixups;
+ 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;
- tree_symbols = NULL;
-
if (!overlay) {
pr_err("null overlay\n");
- err = -EINVAL;
- goto out;
+ return -EINVAL;
}
if (!of_node_check_flag(overlay, OF_DETACHED)) {
pr_err("overlay not detached\n");
- err = -EINVAL;
- goto out;
+ return -EINVAL;
}
phandle_delta = live_tree_max_phandle() + 1;
@@ -286,7 +274,7 @@ int of_resolve_phandles(struct device_node *overlay)
err = adjust_local_phandle_references(local_fixups, overlay, phandle_delta);
if (err)
- goto out;
+ return err;
overlay_fixups = NULL;
@@ -295,16 +283,13 @@ int of_resolve_phandles(struct device_node *overlay)
overlay_fixups = child;
}
- if (!overlay_fixups) {
- err = 0;
- goto out;
- }
+ if (!overlay_fixups)
+ return 0;
- tree_symbols = of_find_node_by_path("/__symbols__");
+ 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");
- err = -EINVAL;
- goto out;
+ return -EINVAL;
}
for_each_property_of_node(overlay_fixups, prop) {
@@ -315,14 +300,15 @@ int of_resolve_phandles(struct device_node *overlay)
err = of_property_read_string(tree_symbols,
prop->name, &refpath);
- if (err)
- goto out;
+ 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) {
- err = -ENOENT;
- goto out;
- }
+ if (!refnode)
+ return -ENOENT;
phandle = refnode->phandle;
of_node_put(refnode);
@@ -332,11 +318,8 @@ int of_resolve_phandles(struct device_node *overlay)
break;
}
-out:
if (err)
pr_err("overlay phandle fixup failed: %d\n", err);
- of_node_put(tree_symbols);
-
return err;
}
EXPORT_SYMBOL_GPL(of_resolve_phandles);
diff --git a/drivers/of/unittest-data/Makefile b/drivers/of/unittest-data/Makefile
index 9b6807065827..01a966e39f23 100644
--- a/drivers/of/unittest-data/Makefile
+++ b/drivers/of/unittest-data/Makefile
@@ -1,27 +1,40 @@
# SPDX-License-Identifier: GPL-2.0
-obj-y += testcases.dtb.o
-
-obj-$(CONFIG_OF_OVERLAY) += overlay.dtb.o \
- overlay_0.dtb.o \
- overlay_1.dtb.o \
- overlay_2.dtb.o \
- overlay_3.dtb.o \
- overlay_4.dtb.o \
- overlay_5.dtb.o \
- overlay_6.dtb.o \
- overlay_7.dtb.o \
- overlay_8.dtb.o \
- overlay_9.dtb.o \
- overlay_10.dtb.o \
- overlay_11.dtb.o \
- overlay_12.dtb.o \
- overlay_13.dtb.o \
- overlay_15.dtb.o \
- overlay_bad_add_dup_node.dtb.o \
- overlay_bad_add_dup_prop.dtb.o \
- overlay_bad_phandle.dtb.o \
- overlay_bad_symbol.dtb.o \
- overlay_base.dtb.o
+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 += -@
@@ -31,4 +44,63 @@ DTC_FLAGS_overlay_base += -@
DTC_FLAGS_testcases += -@
# suppress warnings about intentional errors
-DTC_FLAGS_testcases += -Wno-interrupts_property
+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
diff --git a/drivers/of/unittest-data/overlay.dts b/drivers/of/unittest-data/overlay.dtso
index 3bbc59e922fe..b3e807b99852 100644
--- a/drivers/of/unittest-data/overlay.dts
+++ b/drivers/of/unittest-data/overlay.dtso
@@ -3,13 +3,12 @@
/plugin/;
&electric_1 {
-
status = "okay";
hvac_2: hvac-large-1 {
compatible = "ot,hvac-large";
- heat-range = < 40 75 >;
- cool-range = < 65 80 >;
+ heat-range = <40 75>;
+ cool-range = <65 80>;
};
};
@@ -24,11 +23,11 @@
#size-cells = <1>;
track@30 {
- incline-up = < 48 32 16 >;
+ incline-up = <48 32 16>;
};
track@40 {
- incline-up = < 47 31 15 >;
+ incline-up = <47 31 15>;
};
};
@@ -36,29 +35,28 @@
#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 >;
+ 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 >;
+ spin-controller = <&spin_ctrl_1 3>;
+ spin-rph = <30>;
+ gondolas = <16>;
+ gondola-capacity = <6>;
ride_200_left: track@10 {
- reg = < 0x00000010 0x10 >;
+ reg = <0x00000010 0x10>;
};
ride_200_right: track@20 {
- reg = < 0x00000020 0x10 >;
+ reg = <0x00000020 0x10>;
};
};
};
&lights_2 {
-
status = "okay";
color = "purple", "white", "red", "green";
- rate = < 3 256 >;
+ rate = <3 256>;
};
diff --git a/drivers/of/unittest-data/overlay_0.dts b/drivers/of/unittest-data/overlay_0.dts
deleted file mode 100644
index ac0f9e0fe65f..000000000000
--- a/drivers/of/unittest-data/overlay_0.dts
+++ /dev/null
@@ -1,14 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/dts-v1/;
-/plugin/;
-
-/ {
- /* overlay_0 - enable using absolute target path */
-
- fragment@0 {
- target-path = "/testcase-data/overlay-node/test-bus/test-unittest0";
- __overlay__ {
- status = "okay";
- };
- };
-};
diff --git a/drivers/of/unittest-data/overlay_0.dtso b/drivers/of/unittest-data/overlay_0.dtso
new file mode 100644
index 000000000000..bb46582e0485
--- /dev/null
+++ b/drivers/of/unittest-data/overlay_0.dtso
@@ -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";
+};
diff --git a/drivers/of/unittest-data/overlay_1.dts b/drivers/of/unittest-data/overlay_1.dts
deleted file mode 100644
index e92a626e2948..000000000000
--- a/drivers/of/unittest-data/overlay_1.dts
+++ /dev/null
@@ -1,14 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/dts-v1/;
-/plugin/;
-
-/ {
- /* overlay_1 - disable using absolute target path */
-
- fragment@0 {
- target-path = "/testcase-data/overlay-node/test-bus/test-unittest1";
- __overlay__ {
- status = "disabled";
- };
- };
-};
diff --git a/drivers/of/unittest-data/overlay_1.dtso b/drivers/of/unittest-data/overlay_1.dtso
new file mode 100644
index 000000000000..9c0fc8ffa4a1
--- /dev/null
+++ b/drivers/of/unittest-data/overlay_1.dtso
@@ -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";
+};
diff --git a/drivers/of/unittest-data/overlay_10.dts b/drivers/of/unittest-data/overlay_10.dtso
index 73993bf23bf8..73993bf23bf8 100644
--- a/drivers/of/unittest-data/overlay_10.dts
+++ b/drivers/of/unittest-data/overlay_10.dtso
diff --git a/drivers/of/unittest-data/overlay_11.dts b/drivers/of/unittest-data/overlay_11.dtso
index 9a79b253a809..7d04ff503a18 100644
--- a/drivers/of/unittest-data/overlay_11.dts
+++ b/drivers/of/unittest-data/overlay_11.dtso
@@ -23,6 +23,5 @@
status = "okay";
reg = <1>;
};
-
};
};
diff --git a/drivers/of/unittest-data/overlay_12.dts b/drivers/of/unittest-data/overlay_12.dts
deleted file mode 100644
index ca3441e2cbec..000000000000
--- a/drivers/of/unittest-data/overlay_12.dts
+++ /dev/null
@@ -1,14 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/dts-v1/;
-/plugin/;
-
-/ {
- /* overlay_12 - enable using absolute target path (i2c) */
-
- fragment@0 {
- target-path = "/testcase-data/overlay-node/test-bus/i2c-test-bus/test-unittest12";
- __overlay__ {
- status = "okay";
- };
- };
-};
diff --git a/drivers/of/unittest-data/overlay_12.dtso b/drivers/of/unittest-data/overlay_12.dtso
new file mode 100644
index 000000000000..8d5087793eb4
--- /dev/null
+++ b/drivers/of/unittest-data/overlay_12.dtso
@@ -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";
+};
diff --git a/drivers/of/unittest-data/overlay_13.dts b/drivers/of/unittest-data/overlay_13.dts
deleted file mode 100644
index 3c30dec63894..000000000000
--- a/drivers/of/unittest-data/overlay_13.dts
+++ /dev/null
@@ -1,14 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/dts-v1/;
-/plugin/;
-
-/ {
- /* overlay_13 - disable using absolute target path (i2c) */
-
- fragment@0 {
- target-path = "/testcase-data/overlay-node/test-bus/i2c-test-bus/test-unittest13";
- __overlay__ {
- status = "disabled";
- };
- };
-};
diff --git a/drivers/of/unittest-data/overlay_13.dtso b/drivers/of/unittest-data/overlay_13.dtso
new file mode 100644
index 000000000000..da200ae94f45
--- /dev/null
+++ b/drivers/of/unittest-data/overlay_13.dtso
@@ -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";
+};
diff --git a/drivers/of/unittest-data/overlay_15.dts b/drivers/of/unittest-data/overlay_15.dtso
index 5728490474f6..ba02ae1fed38 100644
--- a/drivers/of/unittest-data/overlay_15.dts
+++ b/drivers/of/unittest-data/overlay_15.dtso
@@ -7,6 +7,7 @@
&unittest_i2c_test_bus {
#address-cells = <1>;
#size-cells = <0>;
+
test-unittest15 {
reg = <11>;
compatible = "unittest-i2c-mux";
diff --git a/drivers/of/unittest-data/overlay_16.dtso b/drivers/of/unittest-data/overlay_16.dtso
new file mode 100644
index 000000000000..80a46dc02581
--- /dev/null
+++ b/drivers/of/unittest-data/overlay_16.dtso
@@ -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>;
+ };
+};
diff --git a/drivers/of/unittest-data/overlay_17.dtso b/drivers/of/unittest-data/overlay_17.dtso
new file mode 100644
index 000000000000..5b8a2209177f
--- /dev/null
+++ b/drivers/of/unittest-data/overlay_17.dtso
@@ -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>;
+ };
+};
diff --git a/drivers/of/unittest-data/overlay_18.dtso b/drivers/of/unittest-data/overlay_18.dtso
new file mode 100644
index 000000000000..dcddd5f6d301
--- /dev/null
+++ b/drivers/of/unittest-data/overlay_18.dtso
@@ -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>;
+ };
+};
diff --git a/drivers/of/unittest-data/overlay_19.dtso b/drivers/of/unittest-data/overlay_19.dtso
new file mode 100644
index 000000000000..32b3ba0b66a3
--- /dev/null
+++ b/drivers/of/unittest-data/overlay_19.dtso
@@ -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>;
+ };
+};
diff --git a/drivers/of/unittest-data/overlay_2.dts b/drivers/of/unittest-data/overlay_2.dtso
index db8684ba89d9..db8684ba89d9 100644
--- a/drivers/of/unittest-data/overlay_2.dts
+++ b/drivers/of/unittest-data/overlay_2.dtso
diff --git a/drivers/of/unittest-data/overlay_20.dtso b/drivers/of/unittest-data/overlay_20.dtso
new file mode 100644
index 000000000000..d4a4f2f32ec7
--- /dev/null
+++ b/drivers/of/unittest-data/overlay_20.dtso
@@ -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>;
+ };
+};
diff --git a/drivers/of/unittest-data/overlay_3.dts b/drivers/of/unittest-data/overlay_3.dtso
index 40f289e7c237..40f289e7c237 100644
--- a/drivers/of/unittest-data/overlay_3.dts
+++ b/drivers/of/unittest-data/overlay_3.dtso
diff --git a/drivers/of/unittest-data/overlay_4.dts b/drivers/of/unittest-data/overlay_4.dtso
index a8a77ddf9abe..9b9eadddb4a0 100644
--- a/drivers/of/unittest-data/overlay_4.dts
+++ b/drivers/of/unittest-data/overlay_4.dtso
@@ -5,7 +5,6 @@
/* overlay_4 - test insertion of a full node */
&unittest_test_bus {
-
/* suppress DTC warning */
#address-cells = <1>;
#size-cells = <0>;
diff --git a/drivers/of/unittest-data/overlay_5.dts b/drivers/of/unittest-data/overlay_5.dtso
index 706f5f1b737c..706f5f1b737c 100644
--- a/drivers/of/unittest-data/overlay_5.dts
+++ b/drivers/of/unittest-data/overlay_5.dtso
diff --git a/drivers/of/unittest-data/overlay_6.dts b/drivers/of/unittest-data/overlay_6.dtso
index 21a7fa4ca45e..21a7fa4ca45e 100644
--- a/drivers/of/unittest-data/overlay_6.dts
+++ b/drivers/of/unittest-data/overlay_6.dtso
diff --git a/drivers/of/unittest-data/overlay_7.dts b/drivers/of/unittest-data/overlay_7.dtso
index 58ba1bb51b50..58ba1bb51b50 100644
--- a/drivers/of/unittest-data/overlay_7.dts
+++ b/drivers/of/unittest-data/overlay_7.dtso
diff --git a/drivers/of/unittest-data/overlay_8.dts b/drivers/of/unittest-data/overlay_8.dtso
index e9718d118e38..e9718d118e38 100644
--- a/drivers/of/unittest-data/overlay_8.dts
+++ b/drivers/of/unittest-data/overlay_8.dtso
diff --git a/drivers/of/unittest-data/overlay_9.dts b/drivers/of/unittest-data/overlay_9.dtso
index b35e23edae50..b35e23edae50 100644
--- a/drivers/of/unittest-data/overlay_9.dts
+++ b/drivers/of/unittest-data/overlay_9.dtso
diff --git a/drivers/of/unittest-data/overlay_bad_add_dup_node.dts b/drivers/of/unittest-data/overlay_bad_add_dup_node.dtso
index 145dfc3b1024..9b53412b2079 100644
--- a/drivers/of/unittest-data/overlay_bad_add_dup_node.dts
+++ b/drivers/of/unittest-data/overlay_bad_add_dup_node.dtso
@@ -13,16 +13,15 @@
*/
&electric_1 {
-
motor-1 {
controller {
- power_bus = < 0x1 0x2 >;
+ power_bus = <0x1 0x2>;
};
};
};
&spin_ctrl_1 {
- controller {
- power_bus_emergency = < 0x101 0x102 >;
- };
+ controller {
+ power_bus_emergency = <0x101 0x102>;
+ };
};
diff --git a/drivers/of/unittest-data/overlay_bad_add_dup_prop.dts b/drivers/of/unittest-data/overlay_bad_add_dup_prop.dts
deleted file mode 100644
index c190da54f175..000000000000
--- a/drivers/of/unittest-data/overlay_bad_add_dup_prop.dts
+++ /dev/null
@@ -1,24 +0,0 @@
-// 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 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.
- */
-
-&electric_1 {
-
- motor-1 {
- rpm_avail = < 100 >;
- };
-};
-
-&spin_ctrl_1 {
- rpm_avail = < 100 200 >;
-};
diff --git a/drivers/of/unittest-data/overlay_bad_add_dup_prop.dtso b/drivers/of/unittest-data/overlay_bad_add_dup_prop.dtso
new file mode 100644
index 000000000000..e03f791655b0
--- /dev/null
+++ b/drivers/of/unittest-data/overlay_bad_add_dup_prop.dtso
@@ -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>;
+ };
+};
diff --git a/drivers/of/unittest-data/overlay_bad_phandle.dts b/drivers/of/unittest-data/overlay_bad_phandle.dtso
index 83b797360318..a61ffc0738e3 100644
--- a/drivers/of/unittest-data/overlay_bad_phandle.dts
+++ b/drivers/of/unittest-data/overlay_bad_phandle.dtso
@@ -3,12 +3,11 @@
/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 >;
+ accelerate = <3>;
+ decelerate = <5>;
};
};
diff --git a/drivers/of/unittest-data/overlay_bad_symbol.dts b/drivers/of/unittest-data/overlay_bad_symbol.dtso
index 98c6d1de144a..07f730384cdd 100644
--- a/drivers/of/unittest-data/overlay_bad_symbol.dts
+++ b/drivers/of/unittest-data/overlay_bad_symbol.dtso
@@ -3,14 +3,13 @@
/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 >;
+ heat-range = <50 75>;
+ cool-range = <60 80>;
};
};
diff --git a/drivers/of/unittest-data/overlay_bad_unresolved.dtso b/drivers/of/unittest-data/overlay_bad_unresolved.dtso
new file mode 100644
index 000000000000..f34d8780d42d
--- /dev/null
+++ b/drivers/of/unittest-data/overlay_bad_unresolved.dtso
@@ -0,0 +1,7 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+&this_label_does_not_exist {
+ status = "okay";
+};
diff --git a/drivers/of/unittest-data/overlay_base.dtso b/drivers/of/unittest-data/overlay_base.dtso
new file mode 100644
index 000000000000..ab9014589c5d
--- /dev/null
+++ b/drivers/of/unittest-data/overlay_base.dtso
@@ -0,0 +1,5 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+#include "overlay_common.dtsi"
diff --git a/drivers/of/unittest-data/overlay_base.dts b/drivers/of/unittest-data/overlay_common.dtsi
index 99ab9d12d00b..a9d7cdbd5ddc 100644
--- a/drivers/of/unittest-data/overlay_base.dts
+++ b/drivers/of/unittest-data/overlay_common.dtsi
@@ -1,6 +1,4 @@
// SPDX-License-Identifier: GPL-2.0
-/dts-v1/;
-/plugin/;
/*
* Base device tree that overlays will be applied against.
@@ -18,19 +16,19 @@
electric_1: substation@100 {
compatible = "ot,big-volts-control";
- reg = < 0x00000100 0x100 >;
+ reg = <0x00000100 0x100>;
status = "disabled";
hvac_1: hvac-medium-1 {
compatible = "ot,hvac-medium";
- heat-range = < 50 75 >;
- cool-range = < 60 80 >;
+ heat-range = <50 75>;
+ cool-range = <60 80>;
};
spin_ctrl_1: motor-1 {
compatible = "ot,ferris-wheel-motor";
spin = "clockwise";
- rpm_avail = < 50 >;
+ rpm_avail = <50>;
};
spin_ctrl_2: motor-8 {
@@ -43,27 +41,27 @@
#size-cells = <1>;
compatible = "ot,rides";
status = "disabled";
- orientation = < 127 >;
+ 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 >;
+ 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 = <&spin_ctrl_2 5 &spin_ctrl_2 7>;
spin-controller-names = "track_1", "track_2";
- queues = < 2 >;
+ queues = <2>;
track@30 {
- reg = < 0x00000030 0x10 >;
+ reg = <0x00000030 0x10>;
};
track@40 {
- reg = < 0x00000040 0x10 >;
+ reg = <0x00000040 0x10>;
};
};
@@ -71,23 +69,21 @@
lights_1: lights@30000 {
compatible = "ot,work-lights";
- reg = < 0x00030000 0x1000 >;
+ reg = <0x00030000 0x1000>;
status = "disabled";
};
lights_2: lights@40000 {
compatible = "ot,show-lights";
- reg = < 0x00040000 0x1000 >;
+ reg = <0x00040000 0x1000>;
status = "disabled";
- rate = < 13 138 >;
+ rate = <13 138>;
};
retail_1: vending@50000 {
- reg = < 0x00050000 0x1000 >;
+ reg = <0x00050000 0x1000>;
compatible = "ot,tickets";
status = "disabled";
};
-
};
};
-
diff --git a/drivers/of/unittest-data/overlay_gpio_01.dtso b/drivers/of/unittest-data/overlay_gpio_01.dtso
new file mode 100644
index 000000000000..bb3a31a2137a
--- /dev/null
+++ b/drivers/of/unittest-data/overlay_gpio_01.dtso
@@ -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";
+ };
+ };
+};
diff --git a/drivers/of/unittest-data/overlay_gpio_02a.dtso b/drivers/of/unittest-data/overlay_gpio_02a.dtso
new file mode 100644
index 000000000000..da955537df74
--- /dev/null
+++ b/drivers/of/unittest-data/overlay_gpio_02a.dtso
@@ -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";
+ };
+};
diff --git a/drivers/of/unittest-data/overlay_gpio_02b.dtso b/drivers/of/unittest-data/overlay_gpio_02b.dtso
new file mode 100644
index 000000000000..79503965d3d7
--- /dev/null
+++ b/drivers/of/unittest-data/overlay_gpio_02b.dtso
@@ -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";
+ };
+ };
+};
diff --git a/drivers/of/unittest-data/overlay_gpio_03.dtso b/drivers/of/unittest-data/overlay_gpio_03.dtso
new file mode 100644
index 000000000000..d8c709616029
--- /dev/null
+++ b/drivers/of/unittest-data/overlay_gpio_03.dtso
@@ -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";
+ };
+ };
+};
diff --git a/drivers/of/unittest-data/overlay_gpio_04a.dtso b/drivers/of/unittest-data/overlay_gpio_04a.dtso
new file mode 100644
index 000000000000..de86511972c2
--- /dev/null
+++ b/drivers/of/unittest-data/overlay_gpio_04a.dtso
@@ -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";
+ };
+};
diff --git a/drivers/of/unittest-data/overlay_gpio_04b.dtso b/drivers/of/unittest-data/overlay_gpio_04b.dtso
new file mode 100644
index 000000000000..dc6eff22f927
--- /dev/null
+++ b/drivers/of/unittest-data/overlay_gpio_04b.dtso
@@ -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";
+ };
+ };
+};
diff --git a/drivers/of/unittest-data/overlay_pci_node.dtso b/drivers/of/unittest-data/overlay_pci_node.dtso
new file mode 100644
index 000000000000..c05e52e9e44a
--- /dev/null
+++ b/drivers/of/unittest-data/overlay_pci_node.dtso
@@ -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>;
+ };
+ };
+ };
+ };
+};
diff --git a/drivers/of/unittest-data/static_base_1.dts b/drivers/of/unittest-data/static_base_1.dts
new file mode 100644
index 000000000000..10556cb3f01f
--- /dev/null
+++ b/drivers/of/unittest-data/static_base_1.dts
@@ -0,0 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+
+#include "testcases_common.dtsi"
diff --git a/drivers/of/unittest-data/static_base_2.dts b/drivers/of/unittest-data/static_base_2.dts
new file mode 100644
index 000000000000..b0ea9504d6f3
--- /dev/null
+++ b/drivers/of/unittest-data/static_base_2.dts
@@ -0,0 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+
+#include "overlay_common.dtsi"
diff --git a/drivers/of/unittest-data/testcases.dtso b/drivers/of/unittest-data/testcases.dtso
new file mode 100644
index 000000000000..61cdd3d5fccb
--- /dev/null
+++ b/drivers/of/unittest-data/testcases.dtso
@@ -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 */
+ };
+ };
+
+};
diff --git a/drivers/of/unittest-data/testcases.dts b/drivers/of/unittest-data/testcases_common.dtsi
index 55fe0ee20109..1c2cdf353ae3 100644
--- a/drivers/of/unittest-data/testcases.dts
+++ b/drivers/of/unittest-data/testcases_common.dtsi
@@ -1,19 +1,21 @@
// SPDX-License-Identifier: GPL-2.0
-/dts-v1/;
-/plugin/;
/ {
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"
diff --git a/drivers/of/unittest-data/tests-address.dtsi b/drivers/of/unittest-data/tests-address.dtsi
new file mode 100644
index 000000000000..f02a181bb125
--- /dev/null
+++ b/drivers/of/unittest-data/tests-address.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>;
+ };
+ };
+ };
+ };
+ };
+ };
+ };
+};
diff --git a/drivers/of/unittest-data/tests-interrupts.dtsi b/drivers/of/unittest-data/tests-interrupts.dtsi
index ec175e800725..4ccb54f91c30 100644
--- a/drivers/of/unittest-data/tests-interrupts.dtsi
+++ b/drivers/of/unittest-data/tests-interrupts.dtsi
@@ -5,6 +5,7 @@
interrupts {
#address-cells = <1>;
#size-cells = <1>;
+
test_intc0: intc0 {
interrupt-controller;
#interrupt-cells = <1>;
@@ -31,9 +32,31 @@
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>;
};
+ 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>;
@@ -44,8 +67,18 @@
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>,
@@ -62,11 +95,10 @@
interrupts = <1>;
};
- testcase-device2 {
- compatible = "testcase-device";
- interrupt-parent = <&test_intc2>;
- interrupts = <1>; /* invalid specifier - too short */
- };
+ /*
+ * 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.
+ */
};
-
};
diff --git a/drivers/of/unittest-data/tests-lifecycle.dtsi b/drivers/of/unittest-data/tests-lifecycle.dtsi
new file mode 100644
index 000000000000..28509a8783a7
--- /dev/null
+++ b/drivers/of/unittest-data/tests-lifecycle.dtsi
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/ {
+ testcase-data {
+ refcount-node {
+ };
+ };
+};
diff --git a/drivers/of/unittest-data/tests-overlay.dtsi b/drivers/of/unittest-data/tests-overlay.dtsi
index 4ea024d908ee..eb35e8aa5d5a 100644
--- a/drivers/of/unittest-data/tests-overlay.dtsi
+++ b/drivers/of/unittest-data/tests-overlay.dtsi
@@ -3,7 +3,6 @@
/ {
testcase-data {
overlay-node {
-
/* test bus */
unittest_test_bus: test-bus {
compatible = "simple-bus";
diff --git a/drivers/of/unittest-data/tests-phandle.dtsi b/drivers/of/unittest-data/tests-phandle.dtsi
index 6b33be4c4416..554a996b2ef1 100644
--- a/drivers/of/unittest-data/tests-phandle.dtsi
+++ b/drivers/of/unittest-data/tests-phandle.dtsi
@@ -8,7 +8,9 @@
testcase: testcase-data {
security-password = "password";
duplicate-name = "duplicate";
+
duplicate-name { };
+
phandle-tests {
provider0: provider0 {
#phandle-cells = <0>;
@@ -38,6 +40,13 @@
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>,
@@ -64,7 +73,8 @@
<&provider4 4 0x100>,
<&provider4 0 0x61>,
<&provider0>,
- <&provider4 19 0x20>;
+ <&provider4 19 0x20>,
+ <&provider5 2 7>;
phandle-list-bad-phandle = <12345678 0 0>;
phandle-list-bad-args = <&provider2 1 0>,
<&provider4 0>;
diff --git a/drivers/of/unittest-data/tests-platform.dtsi b/drivers/of/unittest-data/tests-platform.dtsi
index fa39611071b3..59aa2a9731a7 100644
--- a/drivers/of/unittest-data/tests-platform.dtsi
+++ b/drivers/of/unittest-data/tests-platform.dtsi
@@ -33,6 +33,34 @@
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>;
+ };
+ };
};
};
};
diff --git a/drivers/of/unittest.c b/drivers/of/unittest.c
index 84427384654d..388e9ec2cccf 100644
--- a/drivers/of/unittest.c
+++ b/drivers/of/unittest.c
@@ -7,11 +7,13 @@
#include <linux/memblock.h>
#include <linux/clk.h>
+#include <linux/dma-direct.h> /* to test phys_to_dma/dma_to_phys */
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/hashtable.h>
#include <linux/libfdt.h>
#include <linux/of.h>
+#include <linux/of_address.h>
#include <linux/of_fdt.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
@@ -20,9 +22,12 @@
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/platform_device.h>
+#include <linux/pci.h>
+#include <linux/kernel.h>
#include <linux/i2c.h>
#include <linux/i2c-mux.h>
+#include <linux/gpio/driver.h>
#include <linux/bitops.h>
@@ -40,11 +45,38 @@ static struct unittest_results {
pr_err("FAIL %s():%i " fmt, __func__, __LINE__, ##__VA_ARGS__); \
} else { \
unittest_results.passed++; \
- pr_debug("pass %s():%i\n", __func__, __LINE__); \
+ pr_info("pass %s():%i\n", __func__, __LINE__); \
} \
failed; \
})
+#ifdef CONFIG_OF_KOBJ
+#define OF_KREF_READ(NODE) kref_read(&(NODE)->kobj.kref)
+#else
+#define OF_KREF_READ(NODE) 1
+#endif
+
+/*
+ * Expected message may have a message level other than KERN_INFO.
+ * Print the expected message only if the current loglevel will allow
+ * the actual message to print.
+ *
+ * Do not use EXPECT_BEGIN(), EXPECT_END(), EXPECT_NOT_BEGIN(), or
+ * EXPECT_NOT_END() to report messages expected to be reported or not
+ * reported by pr_debug().
+ */
+#define EXPECT_BEGIN(level, fmt, ...) \
+ printk(level pr_fmt("EXPECT \\ : ") fmt, ##__VA_ARGS__)
+
+#define EXPECT_END(level, fmt, ...) \
+ printk(level pr_fmt("EXPECT / : ") fmt, ##__VA_ARGS__)
+
+#define EXPECT_NOT_BEGIN(level, fmt, ...) \
+ printk(level pr_fmt("EXPECT_NOT \\ : ") fmt, ##__VA_ARGS__)
+
+#define EXPECT_NOT_END(level, fmt, ...) \
+ printk(level pr_fmt("EXPECT_NOT / : ") fmt, ##__VA_ARGS__)
+
static void __init of_unittest_find_node_by_name(void)
{
struct device_node *np;
@@ -52,7 +84,7 @@ static void __init of_unittest_find_node_by_name(void)
np = of_find_node_by_path("/testcase-data");
name = kasprintf(GFP_KERNEL, "%pOF", np);
- unittest(np && !strcmp("/testcase-data", name),
+ unittest(np && name && !strcmp("/testcase-data", name),
"find /testcase-data failed\n");
of_node_put(np);
kfree(name);
@@ -63,14 +95,14 @@ static void __init of_unittest_find_node_by_name(void)
np = of_find_node_by_path("/testcase-data/phandle-tests/consumer-a");
name = kasprintf(GFP_KERNEL, "%pOF", np);
- unittest(np && !strcmp("/testcase-data/phandle-tests/consumer-a", name),
+ unittest(np && name && !strcmp("/testcase-data/phandle-tests/consumer-a", name),
"find /testcase-data/phandle-tests/consumer-a failed\n");
of_node_put(np);
kfree(name);
np = of_find_node_by_path("testcase-alias");
name = kasprintf(GFP_KERNEL, "%pOF", np);
- unittest(np && !strcmp("/testcase-data", name),
+ unittest(np && name && !strcmp("/testcase-data", name),
"find testcase-alias failed\n");
of_node_put(np);
kfree(name);
@@ -81,7 +113,7 @@ static void __init of_unittest_find_node_by_name(void)
np = of_find_node_by_path("testcase-alias/phandle-tests/consumer-a");
name = kasprintf(GFP_KERNEL, "%pOF", np);
- unittest(np && !strcmp("/testcase-data/phandle-tests/consumer-a", name),
+ unittest(np && name && !strcmp("/testcase-data/phandle-tests/consumer-a", name),
"find testcase-alias/phandle-tests/consumer-a failed\n");
of_node_put(np);
kfree(name);
@@ -129,6 +161,15 @@ static void __init of_unittest_find_node_by_name(void)
"option alias path test, subcase #1 failed\n");
of_node_put(np);
+ np = of_find_node_opts_by_path("testcase-alias/phandle-tests/consumer-a:testaliasoption",
+ &options);
+ name = kasprintf(GFP_KERNEL, "%pOF", np);
+ unittest(np && name && !strcmp("/testcase-data/phandle-tests/consumer-a", name) &&
+ !strcmp("testaliasoption", options),
+ "option alias path test, subcase #2 failed\n");
+ of_node_put(np);
+ kfree(name);
+
np = of_find_node_opts_by_path("testcase-alias:testaliasoption", NULL);
unittest(np, "NULL option alias path test failed\n");
of_node_put(np);
@@ -207,27 +248,22 @@ static void __init of_unittest_dynamic(void)
static int __init of_unittest_check_node_linkage(struct device_node *np)
{
- struct device_node *child;
int count = 0, rc;
- for_each_child_of_node(np, child) {
+ for_each_child_of_node_scoped(np, child) {
if (child->parent != np) {
pr_err("Child node %pOFn links to wrong parent %pOFn\n",
child, np);
- rc = -EINVAL;
- goto put_child;
+ return -EINVAL;
}
rc = of_unittest_check_node_linkage(child);
if (rc < 0)
- goto put_child;
+ return rc;
count += rc;
}
return count + 1;
-put_child:
- of_node_put(child);
- return rc;
}
static void __init of_unittest_check_tree_linkage(void)
@@ -344,7 +380,7 @@ static void __init of_unittest_check_phandles(void)
}
nh = kzalloc(sizeof(*nh), GFP_KERNEL);
- if (WARN_ON(!nh))
+ if (!nh)
return;
nh->np = np;
@@ -430,6 +466,9 @@ static void __init of_unittest_parse_phandle_with_args(void)
unittest(passed, "index %i - data error on node %pOF rc=%i\n",
i, args.np, rc);
+
+ if (rc == 0)
+ of_node_put(args.np);
}
/* Check for missing list property */
@@ -443,36 +482,85 @@ static void __init of_unittest_parse_phandle_with_args(void)
/* Check for missing cells property */
memset(&args, 0, sizeof(args));
+
+ EXPECT_BEGIN(KERN_INFO,
+ "OF: /testcase-data/phandle-tests/consumer-a: could not get #phandle-cells-missing for /testcase-data/phandle-tests/provider1");
+
rc = of_parse_phandle_with_args(np, "phandle-list",
"#phandle-cells-missing", 0, &args);
+
+ EXPECT_END(KERN_INFO,
+ "OF: /testcase-data/phandle-tests/consumer-a: could not get #phandle-cells-missing for /testcase-data/phandle-tests/provider1");
+
unittest(rc == -EINVAL, "expected:%i got:%i\n", -EINVAL, rc);
+
+ EXPECT_BEGIN(KERN_INFO,
+ "OF: /testcase-data/phandle-tests/consumer-a: could not get #phandle-cells-missing for /testcase-data/phandle-tests/provider1");
+
rc = of_count_phandle_with_args(np, "phandle-list",
"#phandle-cells-missing");
+
+ EXPECT_END(KERN_INFO,
+ "OF: /testcase-data/phandle-tests/consumer-a: could not get #phandle-cells-missing for /testcase-data/phandle-tests/provider1");
+
unittest(rc == -EINVAL, "expected:%i got:%i\n", -EINVAL, rc);
/* Check for bad phandle in list */
memset(&args, 0, sizeof(args));
+
+ EXPECT_BEGIN(KERN_INFO,
+ "OF: /testcase-data/phandle-tests/consumer-a: could not find phandle");
+
rc = of_parse_phandle_with_args(np, "phandle-list-bad-phandle",
"#phandle-cells", 0, &args);
+
+ EXPECT_END(KERN_INFO,
+ "OF: /testcase-data/phandle-tests/consumer-a: could not find phandle");
+
unittest(rc == -EINVAL, "expected:%i got:%i\n", -EINVAL, rc);
+
+ EXPECT_BEGIN(KERN_INFO,
+ "OF: /testcase-data/phandle-tests/consumer-a: could not find phandle");
+
rc = of_count_phandle_with_args(np, "phandle-list-bad-phandle",
"#phandle-cells");
+
+ EXPECT_END(KERN_INFO,
+ "OF: /testcase-data/phandle-tests/consumer-a: could not find phandle");
+
unittest(rc == -EINVAL, "expected:%i got:%i\n", -EINVAL, rc);
/* Check for incorrectly formed argument list */
memset(&args, 0, sizeof(args));
+
+ EXPECT_BEGIN(KERN_INFO,
+ "OF: /testcase-data/phandle-tests/consumer-a: #phandle-cells = 3 found 1");
+
rc = of_parse_phandle_with_args(np, "phandle-list-bad-args",
"#phandle-cells", 1, &args);
+
+ EXPECT_END(KERN_INFO,
+ "OF: /testcase-data/phandle-tests/consumer-a: #phandle-cells = 3 found 1");
+
unittest(rc == -EINVAL, "expected:%i got:%i\n", -EINVAL, rc);
+
+ EXPECT_BEGIN(KERN_INFO,
+ "OF: /testcase-data/phandle-tests/consumer-a: #phandle-cells = 3 found 1");
+
rc = of_count_phandle_with_args(np, "phandle-list-bad-args",
"#phandle-cells");
+
+ EXPECT_END(KERN_INFO,
+ "OF: /testcase-data/phandle-tests/consumer-a: #phandle-cells = 3 found 1");
+
unittest(rc == -EINVAL, "expected:%i got:%i\n", -EINVAL, rc);
}
static void __init of_unittest_parse_phandle_with_args_map(void)
{
- struct device_node *np, *p0, *p1, *p2, *p3;
+ struct device_node *np, *p[6] = {};
struct of_phandle_args args;
+ unsigned int prefs[6];
int i, rc;
np = of_find_node_by_path("/testcase-data/phandle-tests/consumer-b");
@@ -481,34 +569,24 @@ static void __init of_unittest_parse_phandle_with_args_map(void)
return;
}
- p0 = of_find_node_by_path("/testcase-data/phandle-tests/provider0");
- if (!p0) {
- pr_err("missing testcase data\n");
- return;
- }
-
- p1 = of_find_node_by_path("/testcase-data/phandle-tests/provider1");
- if (!p1) {
- pr_err("missing testcase data\n");
- return;
- }
-
- p2 = of_find_node_by_path("/testcase-data/phandle-tests/provider2");
- if (!p2) {
- pr_err("missing testcase data\n");
- return;
- }
-
- p3 = of_find_node_by_path("/testcase-data/phandle-tests/provider3");
- if (!p3) {
- pr_err("missing testcase data\n");
- return;
+ p[0] = of_find_node_by_path("/testcase-data/phandle-tests/provider0");
+ p[1] = of_find_node_by_path("/testcase-data/phandle-tests/provider1");
+ p[2] = of_find_node_by_path("/testcase-data/phandle-tests/provider2");
+ p[3] = of_find_node_by_path("/testcase-data/phandle-tests/provider3");
+ p[4] = of_find_node_by_path("/testcase-data/phandle-tests/provider4");
+ p[5] = of_find_node_by_path("/testcase-data/phandle-tests/provider5");
+ for (i = 0; i < ARRAY_SIZE(p); ++i) {
+ if (!p[i]) {
+ pr_err("missing testcase data\n");
+ return;
+ }
+ prefs[i] = OF_KREF_READ(p[i]);
}
rc = of_count_phandle_with_args(np, "phandle-list", "#phandle-cells");
- unittest(rc == 7, "of_count_phandle_with_args() returned %i, expected 7\n", rc);
+ unittest(rc == 8, "of_count_phandle_with_args() returned %i, expected 8\n", rc);
- for (i = 0; i < 8; i++) {
+ for (i = 0; i < 9; i++) {
bool passed = true;
memset(&args, 0, sizeof(args));
@@ -519,13 +597,13 @@ static void __init of_unittest_parse_phandle_with_args_map(void)
switch (i) {
case 0:
passed &= !rc;
- passed &= (args.np == p1);
+ passed &= (args.np == p[1]);
passed &= (args.args_count == 1);
passed &= (args.args[0] == 1);
break;
case 1:
passed &= !rc;
- passed &= (args.np == p3);
+ passed &= (args.np == p[3]);
passed &= (args.args_count == 3);
passed &= (args.args[0] == 2);
passed &= (args.args[1] == 5);
@@ -536,28 +614,36 @@ static void __init of_unittest_parse_phandle_with_args_map(void)
break;
case 3:
passed &= !rc;
- passed &= (args.np == p0);
+ passed &= (args.np == p[0]);
passed &= (args.args_count == 0);
break;
case 4:
passed &= !rc;
- passed &= (args.np == p1);
+ passed &= (args.np == p[1]);
passed &= (args.args_count == 1);
passed &= (args.args[0] == 3);
break;
case 5:
passed &= !rc;
- passed &= (args.np == p0);
+ passed &= (args.np == p[0]);
passed &= (args.args_count == 0);
break;
case 6:
passed &= !rc;
- passed &= (args.np == p2);
+ passed &= (args.np == p[2]);
passed &= (args.args_count == 2);
passed &= (args.args[0] == 15);
passed &= (args.args[1] == 0x20);
break;
case 7:
+ passed &= !rc;
+ passed &= (args.np == p[3]);
+ passed &= (args.args_count == 3);
+ passed &= (args.args[0] == 2);
+ passed &= (args.args[1] == 5);
+ passed &= (args.args[2] == 3);
+ break;
+ case 8:
passed &= (rc == -ENOENT);
break;
default:
@@ -566,6 +652,9 @@ static void __init of_unittest_parse_phandle_with_args_map(void)
unittest(passed, "index %i - data error on node %s rc=%i\n",
i, args.np->full_name, rc);
+
+ if (rc == 0)
+ of_node_put(args.np);
}
/* Check for missing list property */
@@ -576,21 +665,49 @@ static void __init of_unittest_parse_phandle_with_args_map(void)
/* Check for missing cells,map,mask property */
memset(&args, 0, sizeof(args));
+
+ EXPECT_BEGIN(KERN_INFO,
+ "OF: /testcase-data/phandle-tests/consumer-b: could not get #phandle-missing-cells for /testcase-data/phandle-tests/provider1");
+
rc = of_parse_phandle_with_args_map(np, "phandle-list",
"phandle-missing", 0, &args);
+ EXPECT_END(KERN_INFO,
+ "OF: /testcase-data/phandle-tests/consumer-b: could not get #phandle-missing-cells for /testcase-data/phandle-tests/provider1");
+
unittest(rc == -EINVAL, "expected:%i got:%i\n", -EINVAL, rc);
/* Check for bad phandle in list */
memset(&args, 0, sizeof(args));
+
+ EXPECT_BEGIN(KERN_INFO,
+ "OF: /testcase-data/phandle-tests/consumer-b: could not find phandle 12345678");
+
rc = of_parse_phandle_with_args_map(np, "phandle-list-bad-phandle",
"phandle", 0, &args);
+ EXPECT_END(KERN_INFO,
+ "OF: /testcase-data/phandle-tests/consumer-b: could not find phandle 12345678");
+
unittest(rc == -EINVAL, "expected:%i got:%i\n", -EINVAL, rc);
/* Check for incorrectly formed argument list */
memset(&args, 0, sizeof(args));
+
+ EXPECT_BEGIN(KERN_INFO,
+ "OF: /testcase-data/phandle-tests/consumer-b: #phandle-cells = 2 found 1");
+
rc = of_parse_phandle_with_args_map(np, "phandle-list-bad-args",
"phandle", 1, &args);
+ EXPECT_END(KERN_INFO,
+ "OF: /testcase-data/phandle-tests/consumer-b: #phandle-cells = 2 found 1");
+
unittest(rc == -EINVAL, "expected:%i got:%i\n", -EINVAL, rc);
+
+ for (i = 0; i < ARRAY_SIZE(p); ++i) {
+ unittest(prefs[i] == OF_KREF_READ(p[i]),
+ "provider%d: expected:%d got:%d\n",
+ i, prefs[i], OF_KREF_READ(p[i]));
+ of_node_put(p[i]);
+ }
}
static void __init of_unittest_property_string(void)
@@ -687,29 +804,29 @@ static void __init of_unittest_property_copy(void)
new = __of_prop_dup(&p1, GFP_KERNEL);
unittest(new && propcmp(&p1, new), "empty property didn't copy correctly\n");
- kfree(new->value);
- kfree(new->name);
- kfree(new);
+ __of_prop_free(new);
new = __of_prop_dup(&p2, GFP_KERNEL);
unittest(new && propcmp(&p2, new), "non-empty property didn't copy correctly\n");
- kfree(new->value);
- kfree(new->name);
- kfree(new);
+ __of_prop_free(new);
#endif
}
static void __init of_unittest_changeset(void)
{
#ifdef CONFIG_OF_DYNAMIC
+ int ret;
struct property *ppadd, padd = { .name = "prop-add", .length = 1, .value = "" };
struct property *ppname_n1, pname_n1 = { .name = "name", .length = 3, .value = "n1" };
struct property *ppname_n2, pname_n2 = { .name = "name", .length = 3, .value = "n2" };
struct property *ppname_n21, pname_n21 = { .name = "name", .length = 3, .value = "n21" };
struct property *ppupdate, pupdate = { .name = "prop-update", .length = 5, .value = "abcd" };
struct property *ppremove;
- struct device_node *n1, *n2, *n21, *nchangeset, *nremove, *parent, *np;
+ struct device_node *n1, *n2, *n21, *n22, *nchangeset, *nremove, *parent, *np;
+ static const char * const str_array[] = { "str1", "str2", "str3" };
+ const u32 u32_array[] = { 1, 2, 3 };
struct of_changeset chgset;
+ const char *propstr = NULL;
n1 = __of_node_dup(NULL, "n1");
unittest(n1, "testcase setup failure\n");
@@ -763,6 +880,17 @@ static void __init of_unittest_changeset(void)
unittest(!of_changeset_add_property(&chgset, parent, ppadd), "fail add prop prop-add\n");
unittest(!of_changeset_update_property(&chgset, parent, ppupdate), "fail update prop\n");
unittest(!of_changeset_remove_property(&chgset, parent, ppremove), "fail remove prop\n");
+ n22 = of_changeset_create_node(&chgset, n2, "n22");
+ unittest(n22, "fail create n22\n");
+ unittest(!of_changeset_add_prop_string(&chgset, n22, "prop-str", "abcd"),
+ "fail add prop prop-str");
+ unittest(!of_changeset_add_prop_string_array(&chgset, n22, "prop-str-array",
+ (const char **)str_array,
+ ARRAY_SIZE(str_array)),
+ "fail add prop prop-str-array");
+ unittest(!of_changeset_add_prop_u32_array(&chgset, n22, "prop-u32-array",
+ u32_array, ARRAY_SIZE(u32_array)),
+ "fail add prop prop-u32-array");
unittest(!of_changeset_apply(&chgset), "apply failed\n");
@@ -772,13 +900,603 @@ static void __init of_unittest_changeset(void)
unittest((np = of_find_node_by_path("/testcase-data/changeset/n2/n21")),
"'%pOF' not added\n", n21);
of_node_put(np);
+ unittest((np = of_find_node_by_path("/testcase-data/changeset/n2/n22")),
+ "'%pOF' not added\n", n22);
+ of_node_put(np);
unittest(!of_changeset_revert(&chgset), "revert failed\n");
+ unittest(!of_find_node_by_path("/testcase-data/changeset/n2/n21"),
+ "'%pOF' still present after revert\n", n21);
+
+ unittest(of_property_present(parent, "prop-remove"),
+ "failed to find removed prop after revert\n");
+
+ ret = of_property_read_string(parent, "prop-update", &propstr);
+ unittest(!ret, "failed to find updated prop after revert\n");
+ if (!ret)
+ unittest(strcmp(propstr, "hello") == 0, "original value not in updated property after revert");
+
+ of_changeset_destroy(&chgset);
+
+ of_node_put(n1);
+ of_node_put(n2);
+ of_node_put(n21);
+ of_node_put(n22);
+#endif
+}
+
+static void __init __maybe_unused changeset_check_string(struct device_node *np,
+ const char *prop_name,
+ const char *expected_str)
+{
+ const char *str;
+ int ret;
+
+ ret = of_property_read_string(np, prop_name, &str);
+ if (unittest(ret == 0, "failed to read %s\n", prop_name))
+ return;
+
+ unittest(strcmp(str, expected_str) == 0,
+ "%s value mismatch (read '%s', exp '%s')\n",
+ prop_name, str, expected_str);
+}
+
+static void __init __maybe_unused changeset_check_string_array(struct device_node *np,
+ const char *prop_name,
+ const char * const *expected_array,
+ unsigned int count)
+{
+ const char *str;
+ unsigned int i;
+ int ret;
+ int cnt;
+
+ cnt = of_property_count_strings(np, prop_name);
+ if (unittest(cnt >= 0, "failed to get %s count\n", prop_name))
+ return;
+
+ if (unittest(cnt == count,
+ "%s count mismatch (read %d, exp %u)\n",
+ prop_name, cnt, count))
+ return;
+
+ for (i = 0; i < count; i++) {
+ ret = of_property_read_string_index(np, prop_name, i, &str);
+ if (unittest(ret == 0, "failed to read %s[%d]\n", prop_name, i))
+ continue;
+
+ unittest(strcmp(str, expected_array[i]) == 0,
+ "%s[%d] value mismatch (read '%s', exp '%s')\n",
+ prop_name, i, str, expected_array[i]);
+ }
+}
+
+static void __init __maybe_unused changeset_check_u32(struct device_node *np,
+ const char *prop_name,
+ u32 expected_u32)
+{
+ u32 val32;
+ int ret;
+
+ ret = of_property_read_u32(np, prop_name, &val32);
+ if (unittest(ret == 0, "failed to read %s\n", prop_name))
+ return;
+
+ unittest(val32 == expected_u32,
+ "%s value mismatch (read '%u', exp '%u')\n",
+ prop_name, val32, expected_u32);
+}
+
+static void __init __maybe_unused changeset_check_u32_array(struct device_node *np,
+ const char *prop_name,
+ const u32 *expected_array,
+ unsigned int count)
+{
+ unsigned int i;
+ u32 val32;
+ int ret;
+ int cnt;
+
+ cnt = of_property_count_u32_elems(np, prop_name);
+ if (unittest(cnt >= 0, "failed to get %s count\n", prop_name))
+ return;
+
+ if (unittest(cnt == count,
+ "%s count mismatch (read %d, exp %u)\n",
+ prop_name, cnt, count))
+ return;
+
+ for (i = 0; i < count; i++) {
+ ret = of_property_read_u32_index(np, prop_name, i, &val32);
+ if (unittest(ret == 0, "failed to read %s[%d]\n", prop_name, i))
+ continue;
+
+ unittest(val32 == expected_array[i],
+ "%s[%d] value mismatch (read '%u', exp '%u')\n",
+ prop_name, i, val32, expected_array[i]);
+ }
+}
+
+static void __init __maybe_unused changeset_check_bool(struct device_node *np,
+ const char *prop_name)
+{
+ unittest(of_property_read_bool(np, prop_name),
+ "%s value mismatch (read 'false', exp 'true')\n", prop_name);
+}
+
+static void __init of_unittest_changeset_prop(void)
+{
+#ifdef CONFIG_OF_DYNAMIC
+ static const char * const str_array[] = { "abc", "defg", "hij" };
+ static const u32 u32_array[] = { 123, 4567, 89, 10, 11 };
+ struct device_node *nchangeset, *np;
+ struct of_changeset chgset;
+ int ret;
+
+ nchangeset = of_find_node_by_path("/testcase-data/changeset");
+ if (!nchangeset) {
+ pr_err("missing testcase data\n");
+ return;
+ }
+
+ of_changeset_init(&chgset);
+
+ np = of_changeset_create_node(&chgset, nchangeset, "test-prop");
+ if (unittest(np, "failed to create test-prop node\n"))
+ goto end_changeset_destroy;
+
+ ret = of_changeset_add_prop_string(&chgset, np, "prop-string", "abcde");
+ unittest(ret == 0, "failed to add prop-string\n");
+
+ ret = of_changeset_add_prop_string_array(&chgset, np, "prop-string-array",
+ str_array, ARRAY_SIZE(str_array));
+ unittest(ret == 0, "failed to add prop-string-array\n");
+
+ ret = of_changeset_add_prop_u32(&chgset, np, "prop-u32", 1234);
+ unittest(ret == 0, "failed to add prop-u32\n");
+
+ ret = of_changeset_add_prop_u32_array(&chgset, np, "prop-u32-array",
+ u32_array, ARRAY_SIZE(u32_array));
+ unittest(ret == 0, "failed to add prop-u32-array\n");
+
+ ret = of_changeset_add_prop_bool(&chgset, np, "prop-bool");
+ unittest(ret == 0, "failed to add prop-bool\n");
+
+ of_node_put(np);
+
+ ret = of_changeset_apply(&chgset);
+ if (unittest(ret == 0, "failed to apply changeset\n"))
+ goto end_changeset_destroy;
+
+ np = of_find_node_by_path("/testcase-data/changeset/test-prop");
+ if (unittest(np, "failed to find test-prop node\n"))
+ goto end_revert_changeset;
+
+ changeset_check_string(np, "prop-string", "abcde");
+ changeset_check_string_array(np, "prop-string-array", str_array, ARRAY_SIZE(str_array));
+ changeset_check_u32(np, "prop-u32", 1234);
+ changeset_check_u32_array(np, "prop-u32-array", u32_array, ARRAY_SIZE(u32_array));
+ changeset_check_bool(np, "prop-bool");
+
+ of_node_put(np);
+
+end_revert_changeset:
+ ret = of_changeset_revert(&chgset);
+ unittest(ret == 0, "failed to revert changeset\n");
+
+end_changeset_destroy:
of_changeset_destroy(&chgset);
+ of_node_put(nchangeset);
+#endif
+}
+
+static void __init of_unittest_dma_get_max_cpu_address(void)
+{
+ struct device_node *np;
+ phys_addr_t cpu_addr;
+
+ if (!IS_ENABLED(CONFIG_OF_ADDRESS))
+ return;
+
+ np = of_find_node_by_path("/testcase-data/address-tests");
+ if (!np) {
+ pr_err("missing testcase data\n");
+ return;
+ }
+
+ cpu_addr = of_dma_get_max_cpu_address(np);
+ unittest(cpu_addr == 0x4fffffff,
+ "of_dma_get_max_cpu_address: wrong CPU addr %pad (expecting %x)\n",
+ &cpu_addr, 0x4fffffff);
+}
+
+static void __init of_unittest_dma_ranges_one(const char *path,
+ u64 expect_dma_addr, u64 expect_paddr)
+{
+#ifdef CONFIG_HAS_DMA
+ struct device_node *np;
+ const struct bus_dma_region *map = NULL;
+ int rc;
+
+ np = of_find_node_by_path(path);
+ if (!np) {
+ pr_err("missing testcase data\n");
+ return;
+ }
+
+ rc = of_dma_get_range(np, &map);
+
+ unittest(!rc, "of_dma_get_range failed on node %pOF rc=%i\n", np, rc);
+
+ if (!rc) {
+ phys_addr_t paddr;
+ dma_addr_t dma_addr;
+ struct device *dev_bogus;
+
+ dev_bogus = kzalloc(sizeof(struct device), GFP_KERNEL);
+ if (!dev_bogus) {
+ unittest(0, "kzalloc() failed\n");
+ kfree(map);
+ return;
+ }
+
+ dev_bogus->dma_range_map = map;
+ paddr = dma_to_phys(dev_bogus, expect_dma_addr);
+ dma_addr = phys_to_dma(dev_bogus, expect_paddr);
+
+ unittest(paddr == expect_paddr,
+ "of_dma_get_range: wrong phys addr %pap (expecting %llx) on node %pOF\n",
+ &paddr, expect_paddr, np);
+ unittest(dma_addr == expect_dma_addr,
+ "of_dma_get_range: wrong DMA addr %pad (expecting %llx) on node %pOF\n",
+ &dma_addr, expect_dma_addr, np);
+
+ kfree(map);
+ kfree(dev_bogus);
+ }
+ of_node_put(np);
#endif
}
+static void __init of_unittest_parse_dma_ranges(void)
+{
+ of_unittest_dma_ranges_one("/testcase-data/address-tests/device@70000000",
+ 0x0, 0x20000000);
+ if (IS_ENABLED(CONFIG_ARCH_DMA_ADDR_T_64BIT))
+ of_unittest_dma_ranges_one("/testcase-data/address-tests/bus@80000000/device@1000",
+ 0x100000000, 0x20000000);
+ of_unittest_dma_ranges_one("/testcase-data/address-tests/pci@90000000",
+ 0x80000000, 0x20000000);
+}
+
+static void __init of_unittest_pci_dma_ranges(void)
+{
+ struct device_node *np;
+ struct of_pci_range range;
+ struct of_pci_range_parser parser;
+ int i = 0;
+
+ if (!IS_ENABLED(CONFIG_PCI))
+ return;
+
+ np = of_find_node_by_path("/testcase-data/address-tests/pci@90000000");
+ if (!np) {
+ pr_err("missing testcase data\n");
+ return;
+ }
+
+ if (of_pci_dma_range_parser_init(&parser, np)) {
+ pr_err("missing dma-ranges property\n");
+ return;
+ }
+
+ /*
+ * Get the dma-ranges from the device tree
+ */
+ for_each_of_pci_range(&parser, &range) {
+ if (!i) {
+ unittest(range.size == 0x10000000,
+ "for_each_of_pci_range wrong size on node %pOF size=%llx\n",
+ np, range.size);
+ unittest(range.cpu_addr == 0x20000000,
+ "for_each_of_pci_range wrong CPU addr (%llx) on node %pOF",
+ range.cpu_addr, np);
+ unittest(range.pci_addr == 0x80000000,
+ "for_each_of_pci_range wrong DMA addr (%llx) on node %pOF",
+ range.pci_addr, np);
+ } else {
+ unittest(range.size == 0x10000000,
+ "for_each_of_pci_range wrong size on node %pOF size=%llx\n",
+ np, range.size);
+ unittest(range.cpu_addr == 0x40000000,
+ "for_each_of_pci_range wrong CPU addr (%llx) on node %pOF",
+ range.cpu_addr, np);
+ unittest(range.pci_addr == 0xc0000000,
+ "for_each_of_pci_range wrong DMA addr (%llx) on node %pOF",
+ range.pci_addr, np);
+ }
+ i++;
+ }
+
+ of_node_put(np);
+}
+
+static void __init of_unittest_pci_empty_dma_ranges(void)
+{
+ struct device_node *np;
+ struct of_pci_range range;
+ struct of_pci_range_parser parser;
+
+ if (!IS_ENABLED(CONFIG_PCI))
+ return;
+
+ np = of_find_node_by_path("/testcase-data/address-tests2/pcie@d1070000/pci@0,0/dev@0,0/local-bus@0");
+ if (!np) {
+ pr_err("missing testcase data\n");
+ return;
+ }
+
+ if (of_pci_dma_range_parser_init(&parser, np)) {
+ pr_err("missing dma-ranges property\n");
+ return;
+ }
+
+ /*
+ * Get the dma-ranges from the device tree
+ */
+ for_each_of_pci_range(&parser, &range) {
+ unittest(range.size == 0x10000000,
+ "for_each_of_pci_range wrong size on node %pOF size=%llx\n",
+ np, range.size);
+ unittest(range.cpu_addr == 0x00000000,
+ "for_each_of_pci_range wrong CPU addr (%llx) on node %pOF",
+ range.cpu_addr, np);
+ unittest(range.pci_addr == 0xc0000000,
+ "for_each_of_pci_range wrong DMA addr (%llx) on node %pOF",
+ range.pci_addr, np);
+ }
+
+ of_node_put(np);
+}
+
+static void __init of_unittest_bus_ranges(void)
+{
+ struct device_node *np;
+ struct of_range range;
+ struct of_range_parser parser;
+ struct resource res;
+ int ret, count, i = 0;
+
+ np = of_find_node_by_path("/testcase-data/address-tests");
+ if (!np) {
+ pr_err("missing testcase data\n");
+ return;
+ }
+
+ if (of_range_parser_init(&parser, np)) {
+ pr_err("missing ranges property\n");
+ return;
+ }
+
+ ret = of_range_to_resource(np, 1, &res);
+ unittest(!ret, "of_range_to_resource returned error (%d) node %pOF\n",
+ ret, np);
+ unittest(resource_type(&res) == IORESOURCE_MEM,
+ "of_range_to_resource wrong resource type on node %pOF res=%pR\n",
+ np, &res);
+ unittest(res.start == 0xd0000000,
+ "of_range_to_resource wrong resource start address on node %pOF res=%pR\n",
+ np, &res);
+ unittest(resource_size(&res) == 0x20000000,
+ "of_range_to_resource wrong resource start address on node %pOF res=%pR\n",
+ np, &res);
+
+ count = of_range_count(&parser);
+ unittest(count == 2,
+ "of_range_count wrong size on node %pOF count=%d\n",
+ np, count);
+
+ /*
+ * Get the "ranges" from the device tree
+ */
+ for_each_of_range(&parser, &range) {
+ unittest(range.flags == IORESOURCE_MEM,
+ "for_each_of_range wrong flags on node %pOF flags=%x (expected %x)\n",
+ np, range.flags, IORESOURCE_MEM);
+ if (!i) {
+ unittest(range.size == 0x50000000,
+ "for_each_of_range wrong size on node %pOF size=%llx\n",
+ np, range.size);
+ unittest(range.cpu_addr == 0x70000000,
+ "for_each_of_range wrong CPU addr (%llx) on node %pOF",
+ range.cpu_addr, np);
+ unittest(range.bus_addr == 0x70000000,
+ "for_each_of_range wrong bus addr (%llx) on node %pOF",
+ range.pci_addr, np);
+ } else {
+ unittest(range.size == 0x20000000,
+ "for_each_of_range wrong size on node %pOF size=%llx\n",
+ np, range.size);
+ unittest(range.cpu_addr == 0xd0000000,
+ "for_each_of_range wrong CPU addr (%llx) on node %pOF",
+ range.cpu_addr, np);
+ unittest(range.bus_addr == 0x00000000,
+ "for_each_of_range wrong bus addr (%llx) on node %pOF",
+ range.pci_addr, np);
+ }
+ i++;
+ }
+
+ of_node_put(np);
+}
+
+static void __init of_unittest_bus_3cell_ranges(void)
+{
+ struct device_node *np;
+ struct of_range range;
+ struct of_range_parser parser;
+ int i = 0;
+
+ np = of_find_node_by_path("/testcase-data/address-tests/bus@a0000000");
+ if (!np) {
+ pr_err("missing testcase data\n");
+ return;
+ }
+
+ if (of_range_parser_init(&parser, np)) {
+ pr_err("missing ranges property\n");
+ return;
+ }
+
+ /*
+ * Get the "ranges" from the device tree
+ */
+ for_each_of_range(&parser, &range) {
+ if (!i) {
+ unittest(range.flags == 0xf00baa,
+ "for_each_of_range wrong flags on node %pOF flags=%x\n",
+ np, range.flags);
+ unittest(range.size == 0x100000,
+ "for_each_of_range wrong size on node %pOF size=%llx\n",
+ np, range.size);
+ unittest(range.cpu_addr == 0xa0000000,
+ "for_each_of_range wrong CPU addr (%llx) on node %pOF",
+ range.cpu_addr, np);
+ unittest(range.bus_addr == 0x0,
+ "for_each_of_range wrong bus addr (%llx) on node %pOF",
+ range.pci_addr, np);
+ } else {
+ unittest(range.flags == 0xf00bee,
+ "for_each_of_range wrong flags on node %pOF flags=%x\n",
+ np, range.flags);
+ unittest(range.size == 0x200000,
+ "for_each_of_range wrong size on node %pOF size=%llx\n",
+ np, range.size);
+ unittest(range.cpu_addr == 0xb0000000,
+ "for_each_of_range wrong CPU addr (%llx) on node %pOF",
+ range.cpu_addr, np);
+ unittest(range.bus_addr == 0x100000000,
+ "for_each_of_range wrong bus addr (%llx) on node %pOF",
+ range.pci_addr, np);
+ }
+ i++;
+ }
+
+ of_node_put(np);
+}
+
+static void __init of_unittest_reg(void)
+{
+ struct device_node *np;
+ struct resource res;
+ int ret;
+ u64 addr, size;
+
+ np = of_find_node_by_path("/testcase-data/address-tests/bus@80000000/device@1000");
+ if (!np) {
+ pr_err("missing testcase data\n");
+ return;
+ }
+
+ ret = of_property_read_reg(np, 0, &addr, &size);
+ unittest(!ret, "of_property_read_reg(%pOF) returned error %d\n",
+ np, ret);
+ unittest(addr == 0x1000, "of_property_read_reg(%pOF) untranslated address (%llx) incorrect\n",
+ np, addr);
+
+ of_node_put(np);
+
+ np = of_find_node_by_path("/testcase-data/platform-tests-2/node/test-device@100");
+ if (!np) {
+ pr_err("missing testcase data\n");
+ return;
+ }
+
+ ret = of_address_to_resource(np, 0, &res);
+ unittest(ret == -EINVAL, "of_address_to_resource(%pOF) expected error on untranslatable address\n",
+ np);
+
+ of_node_put(np);
+
+}
+
+struct of_unittest_expected_res {
+ int index;
+ struct resource res;
+};
+
+static void __init of_unittest_check_addr(const char *node_path,
+ const struct of_unittest_expected_res *tab_exp,
+ unsigned int tab_exp_count)
+{
+ const struct of_unittest_expected_res *expected;
+ struct device_node *np;
+ struct resource res;
+ unsigned int count;
+ int ret;
+
+ if (!IS_ENABLED(CONFIG_OF_ADDRESS))
+ return;
+
+ np = of_find_node_by_path(node_path);
+ if (!np) {
+ pr_err("missing testcase data (%s)\n", node_path);
+ return;
+ }
+
+ expected = tab_exp;
+ count = tab_exp_count;
+ while (count--) {
+ ret = of_address_to_resource(np, expected->index, &res);
+ unittest(!ret, "of_address_to_resource(%pOF, %d) returned error %d\n",
+ np, expected->index, ret);
+ unittest(resource_type(&res) == resource_type(&expected->res) &&
+ res.start == expected->res.start &&
+ resource_size(&res) == resource_size(&expected->res),
+ "of_address_to_resource(%pOF, %d) wrong resource %pR, expected %pR\n",
+ np, expected->index, &res, &expected->res);
+ expected++;
+ }
+
+ of_node_put(np);
+}
+
+static const struct of_unittest_expected_res of_unittest_reg_2cell_expected_res[] = {
+ {.index = 0, .res = DEFINE_RES_MEM(0xa0a01000, 0x100) },
+ {.index = 1, .res = DEFINE_RES_MEM(0xa0a02000, 0x100) },
+ {.index = 2, .res = DEFINE_RES_MEM(0xc0c01000, 0x100) },
+ {.index = 3, .res = DEFINE_RES_MEM(0xd0d01000, 0x100) },
+};
+
+static const struct of_unittest_expected_res of_unittest_reg_3cell_expected_res[] = {
+ {.index = 0, .res = DEFINE_RES_MEM(0xa0a01000, 0x100) },
+ {.index = 1, .res = DEFINE_RES_MEM(0xa0b02000, 0x100) },
+ {.index = 2, .res = DEFINE_RES_MEM(0xc0c01000, 0x100) },
+ {.index = 3, .res = DEFINE_RES_MEM(0xc0c09000, 0x100) },
+ {.index = 4, .res = DEFINE_RES_MEM(0xd0d01000, 0x100) },
+};
+
+static const struct of_unittest_expected_res of_unittest_reg_pci_expected_res[] = {
+ {.index = 0, .res = DEFINE_RES_MEM(0xe8001000, 0x1000) },
+ {.index = 1, .res = DEFINE_RES_MEM(0xea002000, 0x2000) },
+};
+
+static void __init of_unittest_translate_addr(void)
+{
+ of_unittest_check_addr("/testcase-data/address-tests2/bus-2cell@10000000/device@100000",
+ of_unittest_reg_2cell_expected_res,
+ ARRAY_SIZE(of_unittest_reg_2cell_expected_res));
+
+ of_unittest_check_addr("/testcase-data/address-tests2/bus-3cell@20000000/local-bus@100000/device@f1001000",
+ of_unittest_reg_3cell_expected_res,
+ ARRAY_SIZE(of_unittest_reg_3cell_expected_res));
+
+ of_unittest_check_addr("/testcase-data/address-tests2/pcie@d1070000/pci@0,0/dev@0,0/local-bus@0/dev@e0000000",
+ of_unittest_reg_pci_expected_res,
+ ARRAY_SIZE(of_unittest_reg_pci_expected_res));
+}
+
static void __init of_unittest_parse_interrupts(void)
{
struct device_node *np;
@@ -916,6 +1634,12 @@ static void __init of_unittest_parse_interrupts_extended(void)
passed &= (args.args[1] == 14);
break;
case 6:
+ /*
+ * Tests child node that is missing property
+ * #address-cells. See the comments in
+ * drivers/of/unittest-data/tests-interrupts.dtsi
+ * nodes intmap1 and interrupts-extended0
+ */
passed &= !rc;
passed &= (args.args_count == 1);
passed &= (args.args[0] == 15);
@@ -930,6 +1654,72 @@ static void __init of_unittest_parse_interrupts_extended(void)
of_node_put(np);
}
+#if IS_ENABLED(CONFIG_OF_DYNAMIC)
+static void __init of_unittest_irq_refcount(void)
+{
+ struct of_phandle_args args;
+ struct device_node *intc0, *int_ext0;
+ struct device_node *int2, *intc_intmap0;
+ unsigned int ref_c0, ref_c1, ref_c2;
+ int rc;
+ bool passed;
+
+ if (of_irq_workarounds & OF_IMAP_OLDWORLD_MAC)
+ return;
+
+ intc0 = of_find_node_by_path("/testcase-data/interrupts/intc0");
+ int_ext0 = of_find_node_by_path("/testcase-data/interrupts/interrupts-extended0");
+ intc_intmap0 = of_find_node_by_path("/testcase-data/interrupts/intc-intmap0");
+ int2 = of_find_node_by_path("/testcase-data/interrupts/interrupts2");
+ if (!intc0 || !int_ext0 || !intc_intmap0 || !int2) {
+ pr_err("missing testcase data\n");
+ goto out;
+ }
+
+ /* Test refcount for API of_irq_parse_one() */
+ passed = true;
+ ref_c0 = OF_KREF_READ(intc0);
+ ref_c1 = ref_c0 + 1;
+ memset(&args, 0, sizeof(args));
+ rc = of_irq_parse_one(int_ext0, 0, &args);
+ ref_c2 = OF_KREF_READ(intc0);
+ of_node_put(args.np);
+
+ passed &= !rc;
+ passed &= (args.np == intc0);
+ passed &= (args.args_count == 1);
+ passed &= (args.args[0] == 1);
+ passed &= (ref_c1 == ref_c2);
+ unittest(passed, "IRQ refcount case #1 failed, original(%u) expected(%u) got(%u)\n",
+ ref_c0, ref_c1, ref_c2);
+
+ /* Test refcount for API of_irq_parse_raw() */
+ passed = true;
+ ref_c0 = OF_KREF_READ(intc_intmap0);
+ ref_c1 = ref_c0 + 1;
+ memset(&args, 0, sizeof(args));
+ rc = of_irq_parse_one(int2, 0, &args);
+ ref_c2 = OF_KREF_READ(intc_intmap0);
+ of_node_put(args.np);
+
+ passed &= !rc;
+ passed &= (args.np == intc_intmap0);
+ passed &= (args.args_count == 1);
+ passed &= (args.args[0] == 2);
+ passed &= (ref_c1 == ref_c2);
+ unittest(passed, "IRQ refcount case #2 failed, original(%u) expected(%u) got(%u)\n",
+ ref_c0, ref_c1, ref_c2);
+
+out:
+ of_node_put(int2);
+ of_node_put(intc_intmap0);
+ of_node_put(int_ext0);
+ of_node_put(intc0);
+}
+#else
+static inline void __init of_unittest_irq_refcount(void) { }
+#endif
+
static const struct of_device_id match_node_table[] = {
{ .data = "A", .name = "name0", }, /* Name alone is lowest priority */
{ .data = "B", .type = "type1", }, /* followed by type alone */
@@ -996,11 +1786,7 @@ static void __init of_unittest_match_node(void)
}
}
-static struct resource test_bus_res = {
- .start = 0xfffffff8,
- .end = 0xfffffff9,
- .flags = IORESOURCE_MEM,
-};
+static struct resource test_bus_res = DEFINE_RES_MEM(0xfffffff8, 2);
static const struct platform_device_info test_bus_info = {
.name = "unittest-bus",
};
@@ -1031,7 +1817,15 @@ static void __init of_unittest_platform_populate(void)
np = of_find_node_by_path("/testcase-data/testcase-device2");
pdev = of_find_device_by_node(np);
unittest(pdev, "device 2 creation failed\n");
+
+ EXPECT_BEGIN(KERN_INFO,
+ "platform testcase-data:testcase-device2: error -ENXIO: IRQ index 0 not found");
+
irq = platform_get_irq(pdev, 0);
+
+ EXPECT_END(KERN_INFO,
+ "platform testcase-data:testcase-device2: error -ENXIO: IRQ index 0 not found");
+
unittest(irq < 0 && irq != -EPROBE_DEFER,
"device parsing error failed - %d\n", irq);
}
@@ -1044,8 +1838,10 @@ static void __init of_unittest_platform_populate(void)
test_bus = platform_device_register_full(&test_bus_info);
rc = PTR_ERR_OR_ZERO(test_bus);
unittest(!rc, "testbus registration failed; rc=%i\n", rc);
- if (rc)
+ if (rc) {
+ of_node_put(np);
return;
+ }
test_bus->dev.of_node = np;
/*
@@ -1059,10 +1855,15 @@ static void __init of_unittest_platform_populate(void)
of_platform_populate(np, match, NULL, &test_bus->dev);
for_each_child_of_node(np, child) {
- for_each_child_of_node(child, grandchild)
- unittest(of_find_device_by_node(grandchild),
+ for_each_child_of_node(child, grandchild) {
+ if (!of_property_present(grandchild, "compatible"))
+ continue;
+ pdev = of_find_device_by_node(grandchild);
+ unittest(pdev,
"Could not create device for node '%pOFn'\n",
grandchild);
+ platform_device_put(pdev);
+ }
}
of_platform_depopulate(&test_bus->dev);
@@ -1116,15 +1917,22 @@ static void update_node_properties(struct device_node *np,
for (prop = np->properties; prop != NULL; prop = save_next) {
save_next = prop->next;
ret = of_add_property(dup, prop);
- if (ret)
+ if (ret) {
+ if (ret == -EEXIST && !strcmp(prop->name, "name"))
+ continue;
pr_err("unittest internal error: unable to add testdata property %pOF/%s",
np, prop->name);
+ }
}
}
/**
* attach_node_and_children - attaches nodes
- * and its children to live tree
+ * and its children to live tree.
+ * CAUTION: misleading function name - if node @np already exists in
+ * the live tree then children of @np are *not* attached to the live
+ * tree. This works for the current test devicetree nodes because such
+ * nodes do not have child nodes.
*
* @np: Node to attach to live tree
*/
@@ -1135,10 +1943,14 @@ static void attach_node_and_children(struct device_node *np)
const char *full_name;
full_name = kasprintf(GFP_KERNEL, "%pOF", np);
+ if (!full_name)
+ return;
if (!strcmp(full_name, "/__local_fixups__") ||
- !strcmp(full_name, "/__fixups__"))
+ !strcmp(full_name, "/__fixups__")) {
+ kfree(full_name);
return;
+ }
dup = of_find_node_by_path(full_name);
kfree(full_name);
@@ -1174,33 +1986,40 @@ static void attach_node_and_children(struct device_node *np)
static int __init unittest_data_add(void)
{
void *unittest_data;
- struct device_node *unittest_data_node, *np;
+ void *unittest_data_align;
+ struct device_node *unittest_data_node = NULL, *np;
/*
- * __dtb_testcases_begin[] and __dtb_testcases_end[] are magically
- * created by cmd_dt_S_dtb in scripts/Makefile.lib
+ * __dtbo_testcases_begin[] and __dtbo_testcases_end[] are magically
+ * created by cmd_wrap_S_dtbo in scripts/Makefile.dtbs
*/
- extern uint8_t __dtb_testcases_begin[];
- extern uint8_t __dtb_testcases_end[];
- const int size = __dtb_testcases_end - __dtb_testcases_begin;
+ extern uint8_t __dtbo_testcases_begin[];
+ extern uint8_t __dtbo_testcases_end[];
+ const int size = __dtbo_testcases_end - __dtbo_testcases_begin;
int rc;
+ void *ret;
if (!size) {
- pr_warn("%s: No testcase data to attach; not running tests\n",
- __func__);
+ pr_warn("%s: testcases is empty\n", __func__);
return -ENODATA;
}
/* creating copy */
- unittest_data = kmemdup(__dtb_testcases_begin, size, GFP_KERNEL);
-
- if (!unittest_data) {
- pr_warn("%s: Failed to allocate memory for unittest_data; "
- "not running tests\n", __func__);
+ unittest_data = kmalloc(size + FDT_ALIGN_SIZE, GFP_KERNEL);
+ if (!unittest_data)
return -ENOMEM;
+
+ unittest_data_align = PTR_ALIGN(unittest_data, FDT_ALIGN_SIZE);
+ memcpy(unittest_data_align, __dtbo_testcases_begin, size);
+
+ ret = of_fdt_unflatten_tree(unittest_data_align, NULL, &unittest_data_node);
+ if (!ret) {
+ pr_warn("%s: unflatten testcases tree failed\n", __func__);
+ kfree(unittest_data);
+ return -ENODATA;
}
- of_fdt_unflatten_tree(unittest_data, NULL, &unittest_data_node);
if (!unittest_data_node) {
- pr_warn("%s: No tree to attach; not running tests\n", __func__);
+ pr_warn("%s: testcases tree is empty\n", __func__);
+ kfree(unittest_data);
return -ENODATA;
}
@@ -1212,37 +2031,42 @@ static int __init unittest_data_add(void)
rc = of_resolve_phandles(unittest_data_node);
if (rc) {
pr_err("%s: Failed to resolve phandles (rc=%i)\n", __func__, rc);
- of_overlay_mutex_unlock();
- return -EINVAL;
+ rc = -EINVAL;
+ goto unlock;
}
+ /* attach the sub-tree to live tree */
if (!of_root) {
- of_root = unittest_data_node;
- for_each_of_allnodes(np)
- __of_attach_node_sysfs(np);
- of_aliases = of_find_node_by_path("/aliases");
- of_chosen = of_find_node_by_path("/chosen");
- of_overlay_mutex_unlock();
- return 0;
+ pr_warn("%s: no live tree to attach sub-tree\n", __func__);
+ kfree(unittest_data);
+ rc = -ENODEV;
+ goto unlock;
}
- /* attach the sub-tree to live tree */
+ EXPECT_BEGIN(KERN_INFO,
+ "Duplicate name in testcase-data, renamed to \"duplicate-name#1\"");
+
np = unittest_data_node->child;
while (np) {
struct device_node *next = np->sibling;
np->parent = of_root;
+ /* this will clear OF_DETACHED in np and children */
attach_node_and_children(np);
np = next;
}
+ EXPECT_END(KERN_INFO,
+ "Duplicate name in testcase-data, renamed to \"duplicate-name#1\"");
+
+unlock:
of_overlay_mutex_unlock();
- return 0;
+ return rc;
}
#ifdef CONFIG_OF_OVERLAY
-static int __init overlay_data_apply(const char *overlay_name, int *overlay_id);
+static int __init overlay_data_apply(const char *overlay_name, int *ovcs_id);
static int unittest_probe(struct platform_device *pdev)
{
@@ -1262,13 +2086,12 @@ static int unittest_probe(struct platform_device *pdev)
return 0;
}
-static int unittest_remove(struct platform_device *pdev)
+static void unittest_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
dev_dbg(dev, "%s for node @%pOF\n", __func__, np);
- return 0;
}
static const struct of_device_id unittest_match[] = {
@@ -1281,7 +2104,7 @@ static struct platform_driver unittest_driver = {
.remove = unittest_remove,
.driver = {
.name = "unittest",
- .of_match_table = of_match_ptr(unittest_match),
+ .of_match_table = unittest_match,
},
};
@@ -1311,6 +2134,214 @@ static int of_path_platform_device_exists(const char *path)
return pdev != NULL;
}
+#ifdef CONFIG_OF_GPIO
+
+struct unittest_gpio_dev {
+ struct gpio_chip chip;
+};
+
+static int unittest_gpio_chip_request_count;
+static int unittest_gpio_probe_count;
+static int unittest_gpio_probe_pass_count;
+
+static int unittest_gpio_chip_request(struct gpio_chip *chip, unsigned int offset)
+{
+ unittest_gpio_chip_request_count++;
+
+ pr_debug("%s(): %s %d %d\n", __func__, chip->label, offset,
+ unittest_gpio_chip_request_count);
+ return 0;
+}
+
+static int unittest_gpio_probe(struct platform_device *pdev)
+{
+ struct unittest_gpio_dev *devptr;
+ int ret;
+
+ unittest_gpio_probe_count++;
+
+ devptr = kzalloc(sizeof(*devptr), GFP_KERNEL);
+ if (!devptr)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, devptr);
+
+ devptr->chip.fwnode = dev_fwnode(&pdev->dev);
+ devptr->chip.label = "of-unittest-gpio";
+ devptr->chip.base = -1; /* dynamic allocation */
+ devptr->chip.ngpio = 5;
+ devptr->chip.request = unittest_gpio_chip_request;
+
+ ret = gpiochip_add_data(&devptr->chip, NULL);
+
+ unittest(!ret,
+ "gpiochip_add_data() for node @%pfw failed, ret = %d\n", devptr->chip.fwnode, ret);
+
+ if (!ret)
+ unittest_gpio_probe_pass_count++;
+ return ret;
+}
+
+static void unittest_gpio_remove(struct platform_device *pdev)
+{
+ struct unittest_gpio_dev *devptr = platform_get_drvdata(pdev);
+ struct device *dev = &pdev->dev;
+
+ dev_dbg(dev, "%s for node @%pfw\n", __func__, devptr->chip.fwnode);
+
+ if (devptr->chip.base != -1)
+ gpiochip_remove(&devptr->chip);
+
+ kfree(devptr);
+}
+
+static const struct of_device_id unittest_gpio_id[] = {
+ { .compatible = "unittest-gpio", },
+ {}
+};
+
+static struct platform_driver unittest_gpio_driver = {
+ .probe = unittest_gpio_probe,
+ .remove = unittest_gpio_remove,
+ .driver = {
+ .name = "unittest-gpio",
+ .of_match_table = unittest_gpio_id,
+ },
+};
+
+static void __init of_unittest_overlay_gpio(void)
+{
+ int chip_request_count;
+ int probe_pass_count;
+ int ret;
+
+ /*
+ * tests: apply overlays before registering driver
+ * Similar to installing a driver as a module, the
+ * driver is registered after applying the overlays.
+ *
+ * The overlays are applied by overlay_data_apply()
+ * instead of of_unittest_apply_overlay() so that they
+ * will not be tracked. Thus they will not be removed
+ * by of_unittest_remove_tracked_overlays().
+ *
+ * - apply overlay_gpio_01
+ * - apply overlay_gpio_02a
+ * - apply overlay_gpio_02b
+ * - register driver
+ *
+ * register driver will result in
+ * - probe and processing gpio hog for overlay_gpio_01
+ * - probe for overlay_gpio_02a
+ * - processing gpio for overlay_gpio_02b
+ */
+
+ probe_pass_count = unittest_gpio_probe_pass_count;
+ chip_request_count = unittest_gpio_chip_request_count;
+
+ /*
+ * overlay_gpio_01 contains gpio node and child gpio hog node
+ * overlay_gpio_02a contains gpio node
+ * overlay_gpio_02b contains child gpio hog node
+ */
+
+ unittest(overlay_data_apply("overlay_gpio_01", NULL),
+ "Adding overlay 'overlay_gpio_01' failed\n");
+
+ unittest(overlay_data_apply("overlay_gpio_02a", NULL),
+ "Adding overlay 'overlay_gpio_02a' failed\n");
+
+ unittest(overlay_data_apply("overlay_gpio_02b", NULL),
+ "Adding overlay 'overlay_gpio_02b' failed\n");
+
+ ret = platform_driver_register(&unittest_gpio_driver);
+ if (unittest(ret == 0, "could not register unittest gpio driver\n"))
+ return;
+
+ unittest(probe_pass_count + 2 == unittest_gpio_probe_pass_count,
+ "unittest_gpio_probe() failed or not called\n");
+
+ unittest(chip_request_count + 2 == unittest_gpio_chip_request_count,
+ "unittest_gpio_chip_request() called %d times (expected 1 time)\n",
+ unittest_gpio_chip_request_count - chip_request_count);
+
+ /*
+ * tests: apply overlays after registering driver
+ *
+ * Similar to a driver built-in to the kernel, the
+ * driver is registered before applying the overlays.
+ *
+ * overlay_gpio_03 contains gpio node and child gpio hog node
+ *
+ * - apply overlay_gpio_03
+ *
+ * apply overlay will result in
+ * - probe and processing gpio hog.
+ */
+
+ probe_pass_count = unittest_gpio_probe_pass_count;
+ chip_request_count = unittest_gpio_chip_request_count;
+
+ /* overlay_gpio_03 contains gpio node and child gpio hog node */
+
+ unittest(overlay_data_apply("overlay_gpio_03", NULL),
+ "Adding overlay 'overlay_gpio_03' failed\n");
+
+ unittest(probe_pass_count + 1 == unittest_gpio_probe_pass_count,
+ "unittest_gpio_probe() failed or not called\n");
+
+ unittest(chip_request_count + 1 == unittest_gpio_chip_request_count,
+ "unittest_gpio_chip_request() called %d times (expected 1 time)\n",
+ unittest_gpio_chip_request_count - chip_request_count);
+
+ /*
+ * overlay_gpio_04a contains gpio node
+ *
+ * - apply overlay_gpio_04a
+ *
+ * apply the overlay will result in
+ * - probe for overlay_gpio_04a
+ */
+
+ probe_pass_count = unittest_gpio_probe_pass_count;
+ chip_request_count = unittest_gpio_chip_request_count;
+
+ /* overlay_gpio_04a contains gpio node */
+
+ unittest(overlay_data_apply("overlay_gpio_04a", NULL),
+ "Adding overlay 'overlay_gpio_04a' failed\n");
+
+ unittest(probe_pass_count + 1 == unittest_gpio_probe_pass_count,
+ "unittest_gpio_probe() failed or not called\n");
+
+ /*
+ * overlay_gpio_04b contains child gpio hog node
+ *
+ * - apply overlay_gpio_04b
+ *
+ * apply the overlay will result in
+ * - processing gpio for overlay_gpio_04b
+ */
+
+ /* overlay_gpio_04b contains child gpio hog node */
+
+ unittest(overlay_data_apply("overlay_gpio_04b", NULL),
+ "Adding overlay 'overlay_gpio_04b' failed\n");
+
+ unittest(chip_request_count + 1 == unittest_gpio_chip_request_count,
+ "unittest_gpio_chip_request() called %d times (expected 1 time)\n",
+ unittest_gpio_chip_request_count - chip_request_count);
+}
+
+#else
+
+static void __init of_unittest_overlay_gpio(void)
+{
+ /* skip tests */
+}
+
+#endif
+
#if IS_BUILTIN(CONFIG_I2C)
/* get the i2c client device instantiated at the path */
@@ -1412,89 +2443,81 @@ static const char *overlay_name_from_nr(int nr)
static const char *bus_path = "/testcase-data/overlay-node/test-bus";
-/* it is guaranteed that overlay ids are assigned in sequence */
-#define MAX_UNITTEST_OVERLAYS 256
-static unsigned long overlay_id_bits[BITS_TO_LONGS(MAX_UNITTEST_OVERLAYS)];
-static int overlay_first_id = -1;
+#define MAX_TRACK_OVCS_IDS 256
-static void of_unittest_track_overlay(int id)
+static int track_ovcs_id[MAX_TRACK_OVCS_IDS];
+static int track_ovcs_id_overlay_nr[MAX_TRACK_OVCS_IDS];
+static int track_ovcs_id_cnt;
+
+static void of_unittest_track_overlay(int ovcs_id, int overlay_nr)
{
- if (overlay_first_id < 0)
- overlay_first_id = id;
- id -= overlay_first_id;
+ if (WARN_ON(track_ovcs_id_cnt >= MAX_TRACK_OVCS_IDS))
+ return;
- /* we shouldn't need that many */
- BUG_ON(id >= MAX_UNITTEST_OVERLAYS);
- overlay_id_bits[BIT_WORD(id)] |= BIT_MASK(id);
+ track_ovcs_id[track_ovcs_id_cnt] = ovcs_id;
+ track_ovcs_id_overlay_nr[track_ovcs_id_cnt] = overlay_nr;
+ track_ovcs_id_cnt++;
}
-static void of_unittest_untrack_overlay(int id)
+static void of_unittest_untrack_overlay(int ovcs_id)
{
- if (overlay_first_id < 0)
+ if (WARN_ON(track_ovcs_id_cnt < 1))
return;
- id -= overlay_first_id;
- BUG_ON(id >= MAX_UNITTEST_OVERLAYS);
- overlay_id_bits[BIT_WORD(id)] &= ~BIT_MASK(id);
-}
-static void of_unittest_destroy_tracked_overlays(void)
-{
- int id, ret, defers, ovcs_id;
+ track_ovcs_id_cnt--;
- if (overlay_first_id < 0)
- return;
+ /* If out of synch then test is broken. Do not try to recover. */
+ WARN_ON(track_ovcs_id[track_ovcs_id_cnt] != ovcs_id);
+}
- /* try until no defers */
- do {
- defers = 0;
- /* remove in reverse order */
- for (id = MAX_UNITTEST_OVERLAYS - 1; id >= 0; id--) {
- if (!(overlay_id_bits[BIT_WORD(id)] & BIT_MASK(id)))
- continue;
+static void of_unittest_remove_tracked_overlays(void)
+{
+ int ret, ovcs_id, overlay_nr, save_ovcs_id;
+ const char *overlay_name;
- ovcs_id = id + overlay_first_id;
- ret = of_overlay_remove(&ovcs_id);
- if (ret == -ENODEV) {
- pr_warn("%s: no overlay to destroy for #%d\n",
- __func__, id + overlay_first_id);
- continue;
- }
- if (ret != 0) {
- defers++;
- pr_warn("%s: overlay destroy failed for #%d\n",
- __func__, id + overlay_first_id);
- continue;
- }
+ while (track_ovcs_id_cnt > 0) {
- overlay_id_bits[BIT_WORD(id)] &= ~BIT_MASK(id);
+ ovcs_id = track_ovcs_id[track_ovcs_id_cnt - 1];
+ overlay_nr = track_ovcs_id_overlay_nr[track_ovcs_id_cnt - 1];
+ save_ovcs_id = ovcs_id;
+ ret = of_overlay_remove(&ovcs_id);
+ if (ret == -ENODEV) {
+ overlay_name = overlay_name_from_nr(overlay_nr);
+ pr_warn("%s: of_overlay_remove() for overlay \"%s\" failed, ret = %d\n",
+ __func__, overlay_name, ret);
}
- } while (defers > 0);
+ of_unittest_untrack_overlay(save_ovcs_id);
+ }
+
}
-static int __init of_unittest_apply_overlay(int overlay_nr, int *overlay_id)
+static int __init of_unittest_apply_overlay(int overlay_nr, int *ovcs_id)
{
+ /*
+ * The overlay will be tracked, thus it will be removed
+ * by of_unittest_remove_tracked_overlays().
+ */
+
const char *overlay_name;
overlay_name = overlay_name_from_nr(overlay_nr);
- if (!overlay_data_apply(overlay_name, overlay_id)) {
- unittest(0, "could not apply overlay \"%s\"\n",
- overlay_name);
+ if (!overlay_data_apply(overlay_name, ovcs_id)) {
+ unittest(0, "could not apply overlay \"%s\"\n", overlay_name);
return -EFAULT;
}
- of_unittest_track_overlay(*overlay_id);
+ of_unittest_track_overlay(*ovcs_id, overlay_nr);
return 0;
}
-/* apply an overlay while checking before and after states */
-static int __init of_unittest_apply_overlay_check(int overlay_nr,
+static int __init __of_unittest_apply_overlay_check(int overlay_nr,
int unittest_nr, int before, int after,
enum overlay_type ovtype)
{
int ret, ovcs_id;
- /* unittest device must not be in before state */
+ /* unittest device must be in before state */
if (of_unittest_device_exists(unittest_nr, ovtype) != before) {
unittest(0, "%s with device @\"%s\" %s\n",
overlay_name_from_nr(overlay_nr),
@@ -1503,6 +2526,7 @@ static int __init of_unittest_apply_overlay_check(int overlay_nr,
return -EINVAL;
}
+ /* apply the overlay */
ovcs_id = 0;
ret = of_unittest_apply_overlay(overlay_nr, &ovcs_id);
if (ret != 0) {
@@ -1510,15 +2534,28 @@ static int __init of_unittest_apply_overlay_check(int overlay_nr,
return ret;
}
- /* unittest device must be to set to after state */
+ /* unittest device must be in after state */
if (of_unittest_device_exists(unittest_nr, ovtype) != after) {
- unittest(0, "%s failed to create @\"%s\" %s\n",
+ unittest(0, "%s with device @\"%s\" %s\n",
overlay_name_from_nr(overlay_nr),
unittest_path(unittest_nr, ovtype),
!after ? "enabled" : "disabled");
return -EINVAL;
}
+ return ovcs_id;
+}
+
+/* apply an overlay while checking before and after states */
+static int __init of_unittest_apply_overlay_check(int overlay_nr,
+ int unittest_nr, int before, int after,
+ enum overlay_type ovtype)
+{
+ int ovcs_id = __of_unittest_apply_overlay_check(overlay_nr,
+ unittest_nr, before, after, ovtype);
+ if (ovcs_id < 0)
+ return ovcs_id;
+
return 0;
}
@@ -1527,34 +2564,15 @@ static int __init of_unittest_apply_revert_overlay_check(int overlay_nr,
int unittest_nr, int before, int after,
enum overlay_type ovtype)
{
- int ret, ovcs_id;
-
- /* unittest device must be in before state */
- if (of_unittest_device_exists(unittest_nr, ovtype) != before) {
- unittest(0, "%s with device @\"%s\" %s\n",
- overlay_name_from_nr(overlay_nr),
- unittest_path(unittest_nr, ovtype),
- !before ? "enabled" : "disabled");
- return -EINVAL;
- }
+ int ret, ovcs_id, save_ovcs_id;
- /* apply the overlay */
- ovcs_id = 0;
- ret = of_unittest_apply_overlay(overlay_nr, &ovcs_id);
- if (ret != 0) {
- /* of_unittest_apply_overlay already called unittest() */
- return ret;
- }
-
- /* unittest device must be in after state */
- if (of_unittest_device_exists(unittest_nr, ovtype) != after) {
- unittest(0, "%s failed to create @\"%s\" %s\n",
- overlay_name_from_nr(overlay_nr),
- unittest_path(unittest_nr, ovtype),
- !after ? "enabled" : "disabled");
- return -EINVAL;
- }
+ ovcs_id = __of_unittest_apply_overlay_check(overlay_nr, unittest_nr,
+ before, after, ovtype);
+ if (ovcs_id < 0)
+ return ovcs_id;
+ /* remove the overlay */
+ save_ovcs_id = ovcs_id;
ret = of_overlay_remove(&ovcs_id);
if (ret != 0) {
unittest(0, "%s failed to be destroyed @\"%s\"\n",
@@ -1562,9 +2580,10 @@ static int __init of_unittest_apply_revert_overlay_check(int overlay_nr,
unittest_path(unittest_nr, ovtype));
return ret;
}
+ of_unittest_untrack_overlay(save_ovcs_id);
/* unittest device must be again in before state */
- if (of_unittest_device_exists(unittest_nr, PDEV_OVERLAY) != before) {
+ if (of_unittest_device_exists(unittest_nr, ovtype) != before) {
unittest(0, "%s with device @\"%s\" %s\n",
overlay_name_from_nr(overlay_nr),
unittest_path(unittest_nr, ovtype),
@@ -1578,8 +2597,18 @@ static int __init of_unittest_apply_revert_overlay_check(int overlay_nr,
/* test activation of device */
static void __init of_unittest_overlay_0(void)
{
+ int ret;
+
+ EXPECT_BEGIN(KERN_INFO,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data/overlay-node/test-bus/test-unittest0/status");
+
/* device should enable */
- if (of_unittest_apply_overlay_check(0, 0, 0, 1, PDEV_OVERLAY))
+ ret = of_unittest_apply_overlay_check(0, 0, 0, 1, PDEV_OVERLAY);
+
+ EXPECT_END(KERN_INFO,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data/overlay-node/test-bus/test-unittest0/status");
+
+ if (ret)
return;
unittest(1, "overlay test %d passed\n", 0);
@@ -1588,28 +2617,58 @@ static void __init of_unittest_overlay_0(void)
/* test deactivation of device */
static void __init of_unittest_overlay_1(void)
{
+ int ret;
+
+ EXPECT_BEGIN(KERN_INFO,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data/overlay-node/test-bus/test-unittest1/status");
+
/* device should disable */
- if (of_unittest_apply_overlay_check(1, 1, 1, 0, PDEV_OVERLAY))
+ ret = of_unittest_apply_overlay_check(1, 1, 1, 0, PDEV_OVERLAY);
+
+ EXPECT_END(KERN_INFO,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data/overlay-node/test-bus/test-unittest1/status");
+
+ if (ret)
return;
unittest(1, "overlay test %d passed\n", 1);
+
}
/* test activation of device */
static void __init of_unittest_overlay_2(void)
{
+ int ret;
+
+ EXPECT_BEGIN(KERN_INFO,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data/overlay-node/test-bus/test-unittest2/status");
+
/* device should enable */
- if (of_unittest_apply_overlay_check(2, 2, 0, 1, PDEV_OVERLAY))
- return;
+ ret = of_unittest_apply_overlay_check(2, 2, 0, 1, PDEV_OVERLAY);
+
+ EXPECT_END(KERN_INFO,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data/overlay-node/test-bus/test-unittest2/status");
+ if (ret)
+ return;
unittest(1, "overlay test %d passed\n", 2);
}
/* test deactivation of device */
static void __init of_unittest_overlay_3(void)
{
+ int ret;
+
+ EXPECT_BEGIN(KERN_INFO,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data/overlay-node/test-bus/test-unittest3/status");
+
/* device should disable */
- if (of_unittest_apply_overlay_check(3, 3, 1, 0, PDEV_OVERLAY))
+ ret = of_unittest_apply_overlay_check(3, 3, 1, 0, PDEV_OVERLAY);
+
+ EXPECT_END(KERN_INFO,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data/overlay-node/test-bus/test-unittest3/status");
+
+ if (ret)
return;
unittest(1, "overlay test %d passed\n", 3);
@@ -1628,8 +2687,18 @@ static void __init of_unittest_overlay_4(void)
/* test overlay apply/revert sequence */
static void __init of_unittest_overlay_5(void)
{
+ int ret;
+
+ EXPECT_BEGIN(KERN_INFO,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data/overlay-node/test-bus/test-unittest5/status");
+
/* device should disable */
- if (of_unittest_apply_revert_overlay_check(5, 5, 0, 1, PDEV_OVERLAY))
+ ret = of_unittest_apply_revert_overlay_check(5, 5, 0, 1, PDEV_OVERLAY);
+
+ EXPECT_END(KERN_INFO,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data/overlay-node/test-bus/test-unittest5/status");
+
+ if (ret)
return;
unittest(1, "overlay test %d passed\n", 5);
@@ -1638,11 +2707,13 @@ static void __init of_unittest_overlay_5(void)
/* test overlay application in sequence */
static void __init of_unittest_overlay_6(void)
{
- int i, ov_id[2], ovcs_id;
+ int i, save_ovcs_id[2], ovcs_id;
int overlay_nr = 6, unittest_nr = 6;
int before = 0, after = 1;
const char *overlay_name;
+ int ret;
+
/* unittest device must be in before state */
for (i = 0; i < 2; i++) {
if (of_unittest_device_exists(unittest_nr + i, PDEV_OVERLAY)
@@ -1657,18 +2728,41 @@ static void __init of_unittest_overlay_6(void)
}
/* apply the overlays */
- for (i = 0; i < 2; i++) {
- overlay_name = overlay_name_from_nr(overlay_nr + i);
+ EXPECT_BEGIN(KERN_INFO,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data/overlay-node/test-bus/test-unittest6/status");
- if (!overlay_data_apply(overlay_name, &ovcs_id)) {
- unittest(0, "could not apply overlay \"%s\"\n",
- overlay_name);
+ overlay_name = overlay_name_from_nr(overlay_nr + 0);
+
+ ret = overlay_data_apply(overlay_name, &ovcs_id);
+
+ if (!ret) {
+ unittest(0, "could not apply overlay \"%s\"\n", overlay_name);
+ return;
+ }
+ save_ovcs_id[0] = ovcs_id;
+ of_unittest_track_overlay(ovcs_id, overlay_nr + 0);
+
+ EXPECT_END(KERN_INFO,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data/overlay-node/test-bus/test-unittest6/status");
+
+ EXPECT_BEGIN(KERN_INFO,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data/overlay-node/test-bus/test-unittest7/status");
+
+ overlay_name = overlay_name_from_nr(overlay_nr + 1);
+
+ ret = overlay_data_apply(overlay_name, &ovcs_id);
+
+ if (!ret) {
+ unittest(0, "could not apply overlay \"%s\"\n", overlay_name);
return;
- }
- ov_id[i] = ovcs_id;
- of_unittest_track_overlay(ov_id[i]);
}
+ save_ovcs_id[1] = ovcs_id;
+ of_unittest_track_overlay(ovcs_id, overlay_nr + 1);
+
+ EXPECT_END(KERN_INFO,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data/overlay-node/test-bus/test-unittest7/status");
+
for (i = 0; i < 2; i++) {
/* unittest device must be in after state */
@@ -1684,7 +2778,7 @@ static void __init of_unittest_overlay_6(void)
}
for (i = 1; i >= 0; i--) {
- ovcs_id = ov_id[i];
+ ovcs_id = save_ovcs_id[i];
if (of_overlay_remove(&ovcs_id)) {
unittest(0, "%s failed destroy @\"%s\"\n",
overlay_name_from_nr(overlay_nr + i),
@@ -1692,7 +2786,7 @@ static void __init of_unittest_overlay_6(void)
PDEV_OVERLAY));
return;
}
- of_unittest_untrack_overlay(ov_id[i]);
+ of_unittest_untrack_overlay(save_ovcs_id[i]);
}
for (i = 0; i < 2; i++) {
@@ -1709,34 +2803,78 @@ static void __init of_unittest_overlay_6(void)
}
unittest(1, "overlay test %d passed\n", 6);
+
}
/* test overlay application in sequence */
static void __init of_unittest_overlay_8(void)
{
- int i, ov_id[2], ovcs_id;
+ int i, save_ovcs_id[2], ovcs_id;
int overlay_nr = 8, unittest_nr = 8;
const char *overlay_name;
+ int ret;
/* we don't care about device state in this test */
+ EXPECT_BEGIN(KERN_INFO,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data/overlay-node/test-bus/test-unittest8/status");
+
+ overlay_name = overlay_name_from_nr(overlay_nr + 0);
+
+ ret = overlay_data_apply(overlay_name, &ovcs_id);
+ if (!ret)
+ unittest(0, "could not apply overlay \"%s\"\n", overlay_name);
+
+ EXPECT_END(KERN_INFO,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data/overlay-node/test-bus/test-unittest8/status");
+
+ if (!ret)
+ return;
+
+ save_ovcs_id[0] = ovcs_id;
+ of_unittest_track_overlay(ovcs_id, overlay_nr + 0);
+
+ overlay_name = overlay_name_from_nr(overlay_nr + 1);
+
+ EXPECT_BEGIN(KERN_INFO,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data/overlay-node/test-bus/test-unittest8/property-foo");
+
/* apply the overlays */
- for (i = 0; i < 2; i++) {
+ ret = overlay_data_apply(overlay_name, &ovcs_id);
- overlay_name = overlay_name_from_nr(overlay_nr + i);
+ EXPECT_END(KERN_INFO,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data/overlay-node/test-bus/test-unittest8/property-foo");
- if (!overlay_data_apply(overlay_name, &ovcs_id)) {
- unittest(0, "could not apply overlay \"%s\"\n",
- overlay_name);
- return;
- }
- ov_id[i] = ovcs_id;
- of_unittest_track_overlay(ov_id[i]);
+ if (!ret) {
+ unittest(0, "could not apply overlay \"%s\"\n", overlay_name);
+ return;
}
+ save_ovcs_id[1] = ovcs_id;
+ of_unittest_track_overlay(ovcs_id, overlay_nr + 1);
+
/* now try to remove first overlay (it should fail) */
- ovcs_id = ov_id[0];
- if (!of_overlay_remove(&ovcs_id)) {
+ ovcs_id = save_ovcs_id[0];
+
+ EXPECT_BEGIN(KERN_INFO,
+ "OF: overlay: node_overlaps_later_cs: #6 overlaps with #7 @/testcase-data/overlay-node/test-bus/test-unittest8");
+
+ EXPECT_BEGIN(KERN_INFO,
+ "OF: overlay: overlay #6 is not topmost");
+
+ ret = of_overlay_remove(&ovcs_id);
+
+ EXPECT_END(KERN_INFO,
+ "OF: overlay: overlay #6 is not topmost");
+
+ EXPECT_END(KERN_INFO,
+ "OF: overlay: node_overlaps_later_cs: #6 overlaps with #7 @/testcase-data/overlay-node/test-bus/test-unittest8");
+
+ if (!ret) {
+ /*
+ * Should never get here. If we do, expect a lot of
+ * subsequent tracking and overlay removal related errors.
+ */
unittest(0, "%s was destroyed @\"%s\"\n",
overlay_name_from_nr(overlay_nr + 0),
unittest_path(unittest_nr,
@@ -1746,7 +2884,7 @@ static void __init of_unittest_overlay_8(void)
/* removing them in order should work */
for (i = 1; i >= 0; i--) {
- ovcs_id = ov_id[i];
+ ovcs_id = save_ovcs_id[i];
if (of_overlay_remove(&ovcs_id)) {
unittest(0, "%s not destroyed @\"%s\"\n",
overlay_name_from_nr(overlay_nr + i),
@@ -1754,7 +2892,7 @@ static void __init of_unittest_overlay_8(void)
PDEV_OVERLAY));
return;
}
- of_unittest_untrack_overlay(ov_id[i]);
+ of_unittest_untrack_overlay(save_ovcs_id[i]);
}
unittest(1, "overlay test %d passed\n", 8);
@@ -1768,6 +2906,7 @@ static void __init of_unittest_overlay_10(void)
/* device should disable */
ret = of_unittest_apply_overlay_check(10, 10, 0, 1, PDEV_OVERLAY);
+
if (unittest(ret == 0,
"overlay test %d failed; overlay application\n", 10))
return;
@@ -1791,6 +2930,7 @@ static void __init of_unittest_overlay_11(void)
/* device should disable */
ret = of_unittest_apply_revert_overlay_check(11, 11, 0, 1,
PDEV_OVERLAY);
+
unittest(ret == 0, "overlay test %d failed; overlay apply\n", 11);
}
@@ -1838,10 +2978,8 @@ static int unittest_i2c_bus_probe(struct platform_device *pdev)
dev_dbg(dev, "%s for node @%pOF\n", __func__, np);
std = devm_kzalloc(dev, sizeof(*std), GFP_KERNEL);
- if (!std) {
- dev_err(dev, "Failed to allocate unittest i2c data\n");
+ if (!std)
return -ENOMEM;
- }
/* link them together */
std->pdev = pdev;
@@ -1850,7 +2988,7 @@ static int unittest_i2c_bus_probe(struct platform_device *pdev)
adap = &std->adap;
i2c_set_adapdata(adap, std);
adap->nr = -1;
- strlcpy(adap->name, pdev->name, sizeof(adap->name));
+ strscpy(adap->name, pdev->name, sizeof(adap->name));
adap->class = I2C_CLASS_DEPRECATED;
adap->algo = &unittest_i2c_algo;
adap->dev.parent = dev;
@@ -1867,7 +3005,7 @@ static int unittest_i2c_bus_probe(struct platform_device *pdev)
return 0;
}
-static int unittest_i2c_bus_remove(struct platform_device *pdev)
+static void unittest_i2c_bus_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
@@ -1875,8 +3013,6 @@ static int unittest_i2c_bus_remove(struct platform_device *pdev)
dev_dbg(dev, "%s for node @%pOF\n", __func__, np);
i2c_del_adapter(&std->adap);
-
- return 0;
}
static const struct of_device_id unittest_i2c_bus_match[] = {
@@ -1889,12 +3025,11 @@ static struct platform_driver unittest_i2c_bus_driver = {
.remove = unittest_i2c_bus_remove,
.driver = {
.name = "unittest-i2c-bus",
- .of_match_table = of_match_ptr(unittest_i2c_bus_match),
+ .of_match_table = unittest_i2c_bus_match,
},
};
-static int unittest_i2c_dev_probe(struct i2c_client *client,
- const struct i2c_device_id *id)
+static int unittest_i2c_dev_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct device_node *np = client->dev.of_node;
@@ -1909,13 +3044,12 @@ static int unittest_i2c_dev_probe(struct i2c_client *client,
return 0;
};
-static int unittest_i2c_dev_remove(struct i2c_client *client)
+static void unittest_i2c_dev_remove(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct device_node *np = client->dev.of_node;
dev_dbg(dev, "%s for node @%pOF\n", __func__, np);
- return 0;
}
static const struct i2c_device_id unittest_i2c_dev_id[] = {
@@ -1939,12 +3073,11 @@ static int unittest_i2c_mux_select_chan(struct i2c_mux_core *muxc, u32 chan)
return 0;
}
-static int unittest_i2c_mux_probe(struct i2c_client *client,
- const struct i2c_device_id *id)
+static int unittest_i2c_mux_probe(struct i2c_client *client)
{
int i, nchans;
struct device *dev = &client->dev;
- struct i2c_adapter *adap = to_i2c_adapter(dev->parent);
+ struct i2c_adapter *adap = client->adapter;
struct device_node *np = client->dev.of_node, *child;
struct i2c_mux_core *muxc;
u32 reg, max_reg;
@@ -1974,7 +3107,7 @@ static int unittest_i2c_mux_probe(struct i2c_client *client,
if (!muxc)
return -ENOMEM;
for (i = 0; i < nchans; i++) {
- if (i2c_mux_add_adapter(muxc, 0, i, 0)) {
+ if (i2c_mux_add_adapter(muxc, 0, i)) {
dev_err(dev, "Failed to register mux #%d\n", i);
i2c_mux_del_adapters(muxc);
return -ENODEV;
@@ -1986,7 +3119,7 @@ static int unittest_i2c_mux_probe(struct i2c_client *client,
return 0;
};
-static int unittest_i2c_mux_remove(struct i2c_client *client)
+static void unittest_i2c_mux_remove(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct device_node *np = client->dev.of_node;
@@ -1994,7 +3127,6 @@ static int unittest_i2c_mux_remove(struct i2c_client *client)
dev_dbg(dev, "%s for node @%pOF\n", __func__, np);
i2c_mux_del_adapters(muxc);
- return 0;
}
static const struct i2c_device_id unittest_i2c_mux_id[] = {
@@ -2023,12 +3155,21 @@ static int of_unittest_overlay_i2c_init(void)
return ret;
ret = platform_driver_register(&unittest_i2c_bus_driver);
+
if (unittest(ret == 0,
"could not register unittest i2c bus driver\n"))
return ret;
#if IS_BUILTIN(CONFIG_I2C_MUX)
+
+ EXPECT_BEGIN(KERN_INFO,
+ "i2c i2c-1: Added multiplexed i2c bus 2");
+
ret = i2c_add_driver(&unittest_i2c_mux_driver);
+
+ EXPECT_END(KERN_INFO,
+ "i2c i2c-1: Added multiplexed i2c bus 2");
+
if (unittest(ret == 0,
"could not register unittest i2c mux driver\n"))
return ret;
@@ -2048,8 +3189,18 @@ static void of_unittest_overlay_i2c_cleanup(void)
static void __init of_unittest_overlay_i2c_12(void)
{
+ int ret;
+
/* device should enable */
- if (of_unittest_apply_overlay_check(12, 12, 0, 1, I2C_OVERLAY))
+ EXPECT_BEGIN(KERN_INFO,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data/overlay-node/test-bus/i2c-test-bus/test-unittest12/status");
+
+ ret = of_unittest_apply_overlay_check(12, 12, 0, 1, I2C_OVERLAY);
+
+ EXPECT_END(KERN_INFO,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data/overlay-node/test-bus/i2c-test-bus/test-unittest12/status");
+
+ if (ret)
return;
unittest(1, "overlay test %d passed\n", 12);
@@ -2058,8 +3209,18 @@ static void __init of_unittest_overlay_i2c_12(void)
/* test deactivation of device */
static void __init of_unittest_overlay_i2c_13(void)
{
+ int ret;
+
+ EXPECT_BEGIN(KERN_INFO,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data/overlay-node/test-bus/i2c-test-bus/test-unittest13/status");
+
/* device should disable */
- if (of_unittest_apply_overlay_check(13, 13, 1, 0, I2C_OVERLAY))
+ ret = of_unittest_apply_overlay_check(13, 13, 1, 0, I2C_OVERLAY);
+
+ EXPECT_END(KERN_INFO,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data/overlay-node/test-bus/i2c-test-bus/test-unittest13/status");
+
+ if (ret)
return;
unittest(1, "overlay test %d passed\n", 13);
@@ -2072,8 +3233,18 @@ static void of_unittest_overlay_i2c_14(void)
static void __init of_unittest_overlay_i2c_15(void)
{
+ int ret;
+
/* device should enable */
- if (of_unittest_apply_overlay_check(15, 15, 0, 1, I2C_OVERLAY))
+ EXPECT_BEGIN(KERN_INFO,
+ "i2c i2c-1: Added multiplexed i2c bus 3");
+
+ ret = of_unittest_apply_overlay_check(15, 15, 0, 1, I2C_OVERLAY);
+
+ EXPECT_END(KERN_INFO,
+ "i2c i2c-1: Added multiplexed i2c bus 3");
+
+ if (ret)
return;
unittest(1, "overlay test %d passed\n", 15);
@@ -2086,9 +3257,193 @@ static inline void of_unittest_overlay_i2c_15(void) { }
#endif
+static int of_notify(struct notifier_block *nb, unsigned long action,
+ void *arg)
+{
+ struct of_overlay_notify_data *nd = arg;
+ struct device_node *found;
+ int ret;
+
+ /*
+ * For overlay_16 .. overlay_19, check that returning an error
+ * works for each of the actions by setting an arbitrary return
+ * error number that matches the test number. e.g. for unittest16,
+ * ret = -EBUSY which is -16.
+ *
+ * OVERLAY_INFO() for the overlays is declared to expect the same
+ * error number, so overlay_data_apply() will return no error.
+ *
+ * overlay_20 will return NOTIFY_DONE
+ */
+
+ ret = 0;
+ of_node_get(nd->overlay);
+
+ switch (action) {
+
+ case OF_OVERLAY_PRE_APPLY:
+ found = of_find_node_by_name(nd->overlay, "test-unittest16");
+ if (found) {
+ of_node_put(found);
+ ret = -EBUSY;
+ }
+ break;
+
+ case OF_OVERLAY_POST_APPLY:
+ found = of_find_node_by_name(nd->overlay, "test-unittest17");
+ if (found) {
+ of_node_put(found);
+ ret = -EEXIST;
+ }
+ break;
+
+ case OF_OVERLAY_PRE_REMOVE:
+ found = of_find_node_by_name(nd->overlay, "test-unittest18");
+ if (found) {
+ of_node_put(found);
+ ret = -EXDEV;
+ }
+ break;
+
+ case OF_OVERLAY_POST_REMOVE:
+ found = of_find_node_by_name(nd->overlay, "test-unittest19");
+ if (found) {
+ of_node_put(found);
+ ret = -ENODEV;
+ }
+ break;
+
+ default: /* should not happen */
+ of_node_put(nd->overlay);
+ ret = -EINVAL;
+ break;
+ }
+
+ if (ret)
+ return notifier_from_errno(ret);
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block of_nb = {
+ .notifier_call = of_notify,
+};
+
+static void __init of_unittest_overlay_notify(void)
+{
+ int ovcs_id;
+ int ret;
+
+ ret = of_overlay_notifier_register(&of_nb);
+ unittest(!ret,
+ "of_overlay_notifier_register() failed, ret = %d\n", ret);
+ if (ret)
+ return;
+
+ /*
+ * The overlays are applied by overlay_data_apply()
+ * instead of of_unittest_apply_overlay() so that they
+ * will not be tracked. Thus they will not be removed
+ * by of_unittest_remove_tracked_overlays().
+ *
+ * Applying overlays 16 - 19 will each trigger an error for a
+ * different action in of_notify().
+ *
+ * Applying overlay 20 will not trigger any error in of_notify().
+ */
+
+ /* --- overlay 16 --- */
+
+ EXPECT_BEGIN(KERN_INFO, "OF: overlay: overlay changeset pre-apply notifier error -16, target: /testcase-data/overlay-node/test-bus");
+
+ unittest(overlay_data_apply("overlay_16", &ovcs_id),
+ "test OF_OVERLAY_PRE_APPLY notify injected error\n");
+
+ EXPECT_END(KERN_INFO, "OF: overlay: overlay changeset pre-apply notifier error -16, target: /testcase-data/overlay-node/test-bus");
+
+ unittest(ovcs_id, "ovcs_id not created for overlay_16\n");
+
+ /* --- overlay 17 --- */
+
+ EXPECT_BEGIN(KERN_INFO, "OF: overlay: overlay changeset post-apply notifier error -17, target: /testcase-data/overlay-node/test-bus");
+
+ unittest(overlay_data_apply("overlay_17", &ovcs_id),
+ "test OF_OVERLAY_POST_APPLY notify injected error\n");
+
+ EXPECT_END(KERN_INFO, "OF: overlay: overlay changeset post-apply notifier error -17, target: /testcase-data/overlay-node/test-bus");
+
+ unittest(ovcs_id, "ovcs_id not created for overlay_17\n");
+
+ /* --- overlay 18 --- */
+
+ unittest(overlay_data_apply("overlay_18", &ovcs_id),
+ "OF_OVERLAY_PRE_REMOVE notify injected error\n");
+
+ unittest(ovcs_id, "ovcs_id not created for overlay_18\n");
+
+ if (ovcs_id) {
+ EXPECT_BEGIN(KERN_INFO, "OF: overlay: overlay changeset pre-remove notifier error -18, target: /testcase-data/overlay-node/test-bus");
+
+ ret = of_overlay_remove(&ovcs_id);
+ EXPECT_END(KERN_INFO, "OF: overlay: overlay changeset pre-remove notifier error -18, target: /testcase-data/overlay-node/test-bus");
+ if (ret == -EXDEV) {
+ /*
+ * change set ovcs_id should still exist
+ */
+ unittest(1, "overlay_18 of_overlay_remove() injected error for OF_OVERLAY_PRE_REMOVE\n");
+ } else {
+ unittest(0, "overlay_18 of_overlay_remove() injected error for OF_OVERLAY_PRE_REMOVE not returned\n");
+ }
+ } else {
+ unittest(1, "ovcs_id not created for overlay_18\n");
+ }
+
+ unittest(ovcs_id, "ovcs_id removed for overlay_18\n");
+
+ /* --- overlay 19 --- */
+
+ unittest(overlay_data_apply("overlay_19", &ovcs_id),
+ "OF_OVERLAY_POST_REMOVE notify injected error\n");
+
+ unittest(ovcs_id, "ovcs_id not created for overlay_19\n");
+
+ if (ovcs_id) {
+ EXPECT_BEGIN(KERN_INFO, "OF: overlay: overlay changeset post-remove notifier error -19, target: /testcase-data/overlay-node/test-bus");
+ ret = of_overlay_remove(&ovcs_id);
+ EXPECT_END(KERN_INFO, "OF: overlay: overlay changeset post-remove notifier error -19, target: /testcase-data/overlay-node/test-bus");
+ if (ret == -ENODEV)
+ unittest(1, "overlay_19 of_overlay_remove() injected error for OF_OVERLAY_POST_REMOVE\n");
+ else
+ unittest(0, "overlay_19 of_overlay_remove() injected error for OF_OVERLAY_POST_REMOVE not returned\n");
+ } else {
+ unittest(1, "ovcs_id removed for overlay_19\n");
+ }
+
+ unittest(!ovcs_id, "changeset ovcs_id = %d not removed for overlay_19\n",
+ ovcs_id);
+
+ /* --- overlay 20 --- */
+
+ unittest(overlay_data_apply("overlay_20", &ovcs_id),
+ "overlay notify no injected error\n");
+
+ if (ovcs_id) {
+ ret = of_overlay_remove(&ovcs_id);
+ if (ret)
+ unittest(1, "overlay_20 failed to be destroyed, ret = %d\n",
+ ret);
+ } else {
+ unittest(1, "ovcs_id not created for overlay_20\n");
+ }
+
+ unittest(!of_overlay_notifier_unregister(&of_nb),
+ "of_overlay_notifier_unregister() failed, ret = %d\n", ret);
+}
+
static void __init of_unittest_overlay(void)
{
struct device_node *bus_np = NULL;
+ unsigned int i;
if (platform_driver_register(&unittest_driver)) {
unittest(0, "could not register unittest driver\n");
@@ -2126,7 +3481,8 @@ static void __init of_unittest_overlay(void)
of_unittest_overlay_2();
of_unittest_overlay_3();
of_unittest_overlay_4();
- of_unittest_overlay_5();
+ for (i = 0; i < 3; i++)
+ of_unittest_overlay_5();
of_unittest_overlay_6();
of_unittest_overlay_8();
@@ -2145,7 +3501,11 @@ static void __init of_unittest_overlay(void)
of_unittest_overlay_i2c_cleanup();
#endif
- of_unittest_destroy_tracked_overlays();
+ of_unittest_overlay_gpio();
+
+ of_unittest_remove_tracked_overlays();
+
+ of_unittest_overlay_notify();
out:
of_node_put(bus_np);
@@ -2155,29 +3515,168 @@ out:
static inline void __init of_unittest_overlay(void) { }
#endif
+static void __init of_unittest_lifecycle(void)
+{
+#ifdef CONFIG_OF_DYNAMIC
+ unsigned int refcount;
+ int found_refcount_one = 0;
+ int put_count = 0;
+ struct device_node *np;
+ struct device_node *prev_sibling, *next_sibling;
+ const char *refcount_path = "/testcase-data/refcount-node";
+ const char *refcount_parent_path = "/testcase-data";
+
+ /*
+ * Node lifecycle tests, non-dynamic node:
+ *
+ * - Decrementing refcount to zero via of_node_put() should cause the
+ * attempt to free the node memory by of_node_release() to fail
+ * because the node is not a dynamic node.
+ *
+ * - Decrementing refcount past zero should result in additional
+ * errors reported.
+ */
+
+ np = of_find_node_by_path(refcount_path);
+ unittest(np, "find refcount_path \"%s\"\n", refcount_path);
+ if (np == NULL)
+ goto out_skip_tests;
+
+ while (!found_refcount_one) {
+
+ if (put_count++ > 10) {
+ unittest(0, "guardrail to avoid infinite loop\n");
+ goto out_skip_tests;
+ }
+
+ refcount = kref_read(&np->kobj.kref);
+ if (refcount == 1)
+ found_refcount_one = 1;
+ else
+ of_node_put(np);
+ }
+
+ EXPECT_BEGIN(KERN_INFO, "OF: ERROR: of_node_release() detected bad of_node_put() on /testcase-data/refcount-node");
+
+ /*
+ * refcount is now one, decrementing to zero will result in a call to
+ * of_node_release() to free the node's memory, which should result
+ * in an error
+ */
+ unittest(1, "/testcase-data/refcount-node is one");
+ of_node_put(np);
+
+ EXPECT_END(KERN_INFO, "OF: ERROR: of_node_release() detected bad of_node_put() on /testcase-data/refcount-node");
+
+
+ /*
+ * expect stack trace for subsequent of_node_put():
+ * __refcount_sub_and_test() calls:
+ * refcount_warn_saturate(r, REFCOUNT_SUB_UAF)
+ *
+ * Not capturing entire WARN_ONCE() trace with EXPECT_*(), just
+ * the first three lines, and the last line.
+ */
+ EXPECT_BEGIN(KERN_INFO, "------------[ cut here ]------------");
+ EXPECT_BEGIN(KERN_INFO, "WARNING: <<all>>");
+ EXPECT_BEGIN(KERN_INFO, "refcount_t: underflow; use-after-free.");
+ EXPECT_BEGIN(KERN_INFO, "---[ end trace <<int>> ]---");
+
+ /* refcount is now zero, this should fail */
+ unittest(1, "/testcase-data/refcount-node is zero");
+ of_node_put(np);
+
+ EXPECT_END(KERN_INFO, "---[ end trace <<int>> ]---");
+ EXPECT_END(KERN_INFO, "refcount_t: underflow; use-after-free.");
+ EXPECT_END(KERN_INFO, "WARNING: <<all>>");
+ EXPECT_END(KERN_INFO, "------------[ cut here ]------------");
+
+ /*
+ * Q. do we expect to get yet another warning?
+ * A. no, the WARNING is from WARN_ONCE()
+ */
+ EXPECT_NOT_BEGIN(KERN_INFO, "------------[ cut here ]------------");
+ EXPECT_NOT_BEGIN(KERN_INFO, "WARNING: <<all>>");
+ EXPECT_NOT_BEGIN(KERN_INFO, "refcount_t: underflow; use-after-free.");
+ EXPECT_NOT_BEGIN(KERN_INFO, "---[ end trace <<int>> ]---");
+
+ unittest(1, "/testcase-data/refcount-node is zero, second time");
+ of_node_put(np);
+
+ EXPECT_NOT_END(KERN_INFO, "---[ end trace <<int>> ]---");
+ EXPECT_NOT_END(KERN_INFO, "refcount_t: underflow; use-after-free.");
+ EXPECT_NOT_END(KERN_INFO, "WARNING: <<all>>");
+ EXPECT_NOT_END(KERN_INFO, "------------[ cut here ]------------");
+
+ /*
+ * refcount of zero will trigger stack traces from any further
+ * attempt to of_node_get() node "refcount-node". One example of
+ * this is where of_unittest_check_node_linkage() will recursively
+ * scan the tree, with 'for_each_child_of_node()' doing an
+ * of_node_get() of the children of a node.
+ *
+ * Prevent the stack trace by removing node "refcount-node" from
+ * its parent's child list.
+ *
+ * WARNING: EVIL, EVIL, EVIL:
+ *
+ * Directly manipulate the child list of node /testcase-data to
+ * remove child refcount-node. This is ignoring all proper methods
+ * of removing a child and will leak a small amount of memory.
+ */
+
+ np = of_find_node_by_path(refcount_parent_path);
+ unittest(np, "find refcount_parent_path \"%s\"\n", refcount_parent_path);
+ unittest(np, "ERROR: devicetree live tree left in a 'bad state' if test fail\n");
+ if (np == NULL)
+ return;
+
+ prev_sibling = np->child;
+ next_sibling = prev_sibling->sibling;
+ if (!strcmp(prev_sibling->full_name, "refcount-node")) {
+ np->child = next_sibling;
+ next_sibling = next_sibling->sibling;
+ }
+ while (next_sibling) {
+ if (!strcmp(next_sibling->full_name, "refcount-node"))
+ prev_sibling->sibling = next_sibling->sibling;
+ prev_sibling = next_sibling;
+ next_sibling = next_sibling->sibling;
+ }
+ of_node_put(np);
+
+ return;
+
+out_skip_tests:
+#endif
+ unittest(0, "One or more lifecycle tests skipped\n");
+}
+
#ifdef CONFIG_OF_OVERLAY
/*
- * __dtb_ot_begin[] and __dtb_ot_end[] are created by cmd_dt_S_dtb
- * in scripts/Makefile.lib
+ * __dtbo_##overlay_name##_begin[] and __dtbo_##overlay_name##_end[] are
+ * created by cmd_wrap_S_dtbo in scripts/Makefile.dtbs
*/
-#define OVERLAY_INFO_EXTERN(name) \
- extern uint8_t __dtb_##name##_begin[]; \
- extern uint8_t __dtb_##name##_end[]
+#define OVERLAY_INFO_EXTERN(overlay_name) \
+ extern uint8_t __dtbo_##overlay_name##_begin[]; \
+ extern uint8_t __dtbo_##overlay_name##_end[]
-#define OVERLAY_INFO(overlay_name, expected) \
-{ .dtb_begin = __dtb_##overlay_name##_begin, \
- .dtb_end = __dtb_##overlay_name##_end, \
- .expected_result = expected, \
- .name = #overlay_name, \
+#define OVERLAY_INFO(overlay_name, expected, expected_remove) \
+{ .dtbo_begin = __dtbo_##overlay_name##_begin, \
+ .dtbo_end = __dtbo_##overlay_name##_end, \
+ .expected_result = expected, \
+ .expected_result_remove = expected_remove, \
+ .name = #overlay_name, \
}
struct overlay_info {
- uint8_t *dtb_begin;
- uint8_t *dtb_end;
+ uint8_t *dtbo_begin;
+ uint8_t *dtbo_end;
int expected_result;
- int overlay_id;
+ int expected_result_remove; /* if apply failed */
+ int ovcs_id;
char *name;
};
@@ -2198,43 +3697,69 @@ OVERLAY_INFO_EXTERN(overlay_11);
OVERLAY_INFO_EXTERN(overlay_12);
OVERLAY_INFO_EXTERN(overlay_13);
OVERLAY_INFO_EXTERN(overlay_15);
+OVERLAY_INFO_EXTERN(overlay_16);
+OVERLAY_INFO_EXTERN(overlay_17);
+OVERLAY_INFO_EXTERN(overlay_18);
+OVERLAY_INFO_EXTERN(overlay_19);
+OVERLAY_INFO_EXTERN(overlay_20);
+OVERLAY_INFO_EXTERN(overlay_gpio_01);
+OVERLAY_INFO_EXTERN(overlay_gpio_02a);
+OVERLAY_INFO_EXTERN(overlay_gpio_02b);
+OVERLAY_INFO_EXTERN(overlay_gpio_03);
+OVERLAY_INFO_EXTERN(overlay_gpio_04a);
+OVERLAY_INFO_EXTERN(overlay_gpio_04b);
+OVERLAY_INFO_EXTERN(overlay_pci_node);
OVERLAY_INFO_EXTERN(overlay_bad_add_dup_node);
OVERLAY_INFO_EXTERN(overlay_bad_add_dup_prop);
OVERLAY_INFO_EXTERN(overlay_bad_phandle);
OVERLAY_INFO_EXTERN(overlay_bad_symbol);
+OVERLAY_INFO_EXTERN(overlay_bad_unresolved);
/* entries found by name */
static struct overlay_info overlays[] = {
- OVERLAY_INFO(overlay_base, -9999),
- OVERLAY_INFO(overlay, 0),
- OVERLAY_INFO(overlay_0, 0),
- OVERLAY_INFO(overlay_1, 0),
- OVERLAY_INFO(overlay_2, 0),
- OVERLAY_INFO(overlay_3, 0),
- OVERLAY_INFO(overlay_4, 0),
- OVERLAY_INFO(overlay_5, 0),
- OVERLAY_INFO(overlay_6, 0),
- OVERLAY_INFO(overlay_7, 0),
- OVERLAY_INFO(overlay_8, 0),
- OVERLAY_INFO(overlay_9, 0),
- OVERLAY_INFO(overlay_10, 0),
- OVERLAY_INFO(overlay_11, 0),
- OVERLAY_INFO(overlay_12, 0),
- OVERLAY_INFO(overlay_13, 0),
- OVERLAY_INFO(overlay_15, 0),
- OVERLAY_INFO(overlay_bad_add_dup_node, -EINVAL),
- OVERLAY_INFO(overlay_bad_add_dup_prop, -EINVAL),
- OVERLAY_INFO(overlay_bad_phandle, -EINVAL),
- OVERLAY_INFO(overlay_bad_symbol, -EINVAL),
+ OVERLAY_INFO(overlay_base, -9999, 0),
+ OVERLAY_INFO(overlay, 0, 0),
+ OVERLAY_INFO(overlay_0, 0, 0),
+ OVERLAY_INFO(overlay_1, 0, 0),
+ OVERLAY_INFO(overlay_2, 0, 0),
+ OVERLAY_INFO(overlay_3, 0, 0),
+ OVERLAY_INFO(overlay_4, 0, 0),
+ OVERLAY_INFO(overlay_5, 0, 0),
+ OVERLAY_INFO(overlay_6, 0, 0),
+ OVERLAY_INFO(overlay_7, 0, 0),
+ OVERLAY_INFO(overlay_8, 0, 0),
+ OVERLAY_INFO(overlay_9, 0, 0),
+ OVERLAY_INFO(overlay_10, 0, 0),
+ OVERLAY_INFO(overlay_11, 0, 0),
+ OVERLAY_INFO(overlay_12, 0, 0),
+ OVERLAY_INFO(overlay_13, 0, 0),
+ OVERLAY_INFO(overlay_15, 0, 0),
+ OVERLAY_INFO(overlay_16, -EBUSY, 0),
+ OVERLAY_INFO(overlay_17, -EEXIST, 0),
+ OVERLAY_INFO(overlay_18, 0, 0),
+ OVERLAY_INFO(overlay_19, 0, 0),
+ OVERLAY_INFO(overlay_20, 0, 0),
+ OVERLAY_INFO(overlay_gpio_01, 0, 0),
+ OVERLAY_INFO(overlay_gpio_02a, 0, 0),
+ OVERLAY_INFO(overlay_gpio_02b, 0, 0),
+ OVERLAY_INFO(overlay_gpio_03, 0, 0),
+ OVERLAY_INFO(overlay_gpio_04a, 0, 0),
+ OVERLAY_INFO(overlay_gpio_04b, 0, 0),
+ OVERLAY_INFO(overlay_pci_node, 0, 0),
+ OVERLAY_INFO(overlay_bad_add_dup_node, -EINVAL, -ENODEV),
+ OVERLAY_INFO(overlay_bad_add_dup_prop, -EINVAL, -ENODEV),
+ OVERLAY_INFO(overlay_bad_phandle, -EINVAL, 0),
+ OVERLAY_INFO(overlay_bad_symbol, -EINVAL, -ENODEV),
+ OVERLAY_INFO(overlay_bad_unresolved, -EINVAL, 0),
/* end marker */
- {.dtb_begin = NULL, .dtb_end = NULL, .expected_result = 0, .name = NULL}
+ { }
};
static struct device_node *overlay_base_root;
static void * __init dt_alloc_memory(u64 size, u64 align)
{
- return memblock_alloc(size, align);
+ return memblock_alloc_or_panic(size, align);
}
/*
@@ -2278,13 +3803,13 @@ void __init unittest_unflatten_overlay_base(void)
return;
}
- data_size = info->dtb_end - info->dtb_begin;
+ data_size = info->dtbo_end - info->dtbo_begin;
if (!data_size) {
pr_err("No dtb 'overlay_base' to attach\n");
return;
}
- size = fdt_totalsize(info->dtb_begin);
+ size = fdt_totalsize(info->dtbo_begin);
if (size != data_size) {
pr_err("dtb 'overlay_base' header totalsize != actual size");
return;
@@ -2296,7 +3821,7 @@ void __init unittest_unflatten_overlay_base(void)
return;
}
- memcpy(new_fdt, info->dtb_begin, size);
+ memcpy(new_fdt, info->dtbo_begin, size);
__unflatten_device_tree(new_fdt, NULL, &overlay_base_root,
dt_alloc_memory, true);
@@ -2313,11 +3838,12 @@ void __init unittest_unflatten_overlay_base(void)
*
* Return 0 on unexpected error.
*/
-static int __init overlay_data_apply(const char *overlay_name, int *overlay_id)
+static int __init overlay_data_apply(const char *overlay_name, int *ovcs_id)
{
struct overlay_info *info;
+ int passed = 1;
int found = 0;
- int ret;
+ int ret, ret2;
u32 size;
for (info = overlays; info && info->name; info++) {
@@ -2331,24 +3857,38 @@ static int __init overlay_data_apply(const char *overlay_name, int *overlay_id)
return 0;
}
- size = info->dtb_end - info->dtb_begin;
+ size = info->dtbo_end - info->dtbo_begin;
if (!size)
pr_err("no overlay data for %s\n", overlay_name);
- ret = of_overlay_fdt_apply(info->dtb_begin, size, &info->overlay_id);
- if (overlay_id)
- *overlay_id = info->overlay_id;
+ ret = of_overlay_fdt_apply(info->dtbo_begin, size, &info->ovcs_id,
+ NULL);
+ if (ovcs_id)
+ *ovcs_id = info->ovcs_id;
if (ret < 0)
goto out;
pr_debug("%s applied\n", overlay_name);
out:
- if (ret != info->expected_result)
+ if (ret != info->expected_result) {
pr_err("of_overlay_fdt_apply() expected %d, ret=%d, %s\n",
info->expected_result, ret, overlay_name);
+ passed = 0;
+ }
+
+ if (ret < 0) {
+ /* changeset may be partially applied */
+ ret2 = of_overlay_remove(&info->ovcs_id);
+ if (ret2 != info->expected_result_remove) {
+ pr_err("of_overlay_remove() expected %d, ret=%d, %s\n",
+ info->expected_result_remove, ret2,
+ overlay_name);
+ passed = 0;
+ }
+ }
- return (ret == info->expected_result);
+ return passed;
}
/*
@@ -2367,6 +3907,7 @@ static __init void of_unittest_overlay_high_level(void)
struct device_node *overlay_base_symbols;
struct device_node **pprev;
struct property *prop;
+ int ret;
if (!overlay_base_root) {
unittest(0, "overlay_base_root not initialized\n");
@@ -2421,6 +3962,8 @@ static __init void of_unittest_overlay_high_level(void)
if (!strcmp(np->full_name, base_child->full_name)) {
unittest(0, "illegal node name in overlay_base %pOFn",
np);
+ of_node_put(np);
+ of_node_put(base_child);
return;
}
}
@@ -2461,8 +4004,9 @@ static __init void of_unittest_overlay_high_level(void)
goto err_unlock;
}
if (__of_add_property(of_symbols, new_prop)) {
+ __of_prop_free(new_prop);
/* "name" auto-generated by unflatten */
- if (!strcmp(new_prop->name, "name"))
+ if (!strcmp(prop->name, "name"))
continue;
unittest(0, "duplicate property '%s' in overlay_base node __symbols__",
prop->name);
@@ -2481,30 +4025,336 @@ static __init void of_unittest_overlay_high_level(void)
/* now do the normal overlay usage test */
- unittest(overlay_data_apply("overlay", NULL),
- "Adding overlay 'overlay' failed\n");
+ /* --- overlay --- */
+
+ EXPECT_BEGIN(KERN_ERR,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data-2/substation@100/status");
+ EXPECT_BEGIN(KERN_ERR,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data-2/fairway-1/status");
+ EXPECT_BEGIN(KERN_ERR,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data-2/fairway-1/ride@100/track@30/incline-up");
+ EXPECT_BEGIN(KERN_ERR,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data-2/fairway-1/ride@100/track@40/incline-up");
+ EXPECT_BEGIN(KERN_ERR,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data-2/lights@40000/status");
+ EXPECT_BEGIN(KERN_ERR,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data-2/lights@40000/color");
+ EXPECT_BEGIN(KERN_ERR,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data-2/lights@40000/rate");
+ EXPECT_BEGIN(KERN_ERR,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /__symbols__/hvac_2");
+ EXPECT_BEGIN(KERN_ERR,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /__symbols__/ride_200");
+ EXPECT_BEGIN(KERN_ERR,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /__symbols__/ride_200_left");
+ EXPECT_BEGIN(KERN_ERR,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /__symbols__/ride_200_right");
+
+ ret = overlay_data_apply("overlay", NULL);
+
+ EXPECT_END(KERN_ERR,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /__symbols__/ride_200_right");
+ EXPECT_END(KERN_ERR,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /__symbols__/ride_200_left");
+ EXPECT_END(KERN_ERR,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /__symbols__/ride_200");
+ EXPECT_END(KERN_ERR,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /__symbols__/hvac_2");
+ EXPECT_END(KERN_ERR,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data-2/lights@40000/rate");
+ EXPECT_END(KERN_ERR,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data-2/lights@40000/color");
+ EXPECT_END(KERN_ERR,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data-2/lights@40000/status");
+ EXPECT_END(KERN_ERR,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data-2/fairway-1/ride@100/track@40/incline-up");
+ EXPECT_END(KERN_ERR,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data-2/fairway-1/ride@100/track@30/incline-up");
+ EXPECT_END(KERN_ERR,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data-2/fairway-1/status");
+ EXPECT_END(KERN_ERR,
+ "OF: overlay: WARNING: memory leak will occur if overlay removed, property: /testcase-data-2/substation@100/status");
+
+ unittest(ret, "Adding overlay 'overlay' failed\n");
+
+ /* --- overlay_bad_add_dup_node --- */
+
+ EXPECT_BEGIN(KERN_ERR,
+ "OF: overlay: ERROR: multiple fragments add and/or delete node /testcase-data-2/substation@100/motor-1/controller");
+ EXPECT_BEGIN(KERN_ERR,
+ "OF: overlay: ERROR: multiple fragments add, update, and/or delete property /testcase-data-2/substation@100/motor-1/controller/name");
+ EXPECT_BEGIN(KERN_ERR,
+ "OF: changeset: apply failed: REMOVE_PROPERTY /testcase-data-2/substation@100/motor-1/controller:name");
+ EXPECT_BEGIN(KERN_ERR,
+ "OF: Error reverting changeset (-19)");
unittest(overlay_data_apply("overlay_bad_add_dup_node", NULL),
"Adding overlay 'overlay_bad_add_dup_node' failed\n");
+ EXPECT_END(KERN_ERR,
+ "OF: Error reverting changeset (-19)");
+ EXPECT_END(KERN_ERR,
+ "OF: changeset: apply failed: REMOVE_PROPERTY /testcase-data-2/substation@100/motor-1/controller:name");
+ EXPECT_END(KERN_ERR,
+ "OF: overlay: ERROR: multiple fragments add, update, and/or delete property /testcase-data-2/substation@100/motor-1/controller/name");
+ EXPECT_END(KERN_ERR,
+ "OF: overlay: ERROR: multiple fragments add and/or delete node /testcase-data-2/substation@100/motor-1/controller");
+
+ /* --- overlay_bad_add_dup_prop --- */
+
+ EXPECT_BEGIN(KERN_ERR,
+ "OF: overlay: ERROR: multiple fragments add and/or delete node /testcase-data-2/substation@100/motor-1/electric");
+ EXPECT_BEGIN(KERN_ERR,
+ "OF: overlay: ERROR: multiple fragments add, update, and/or delete property /testcase-data-2/substation@100/motor-1/electric/rpm_avail");
+ EXPECT_BEGIN(KERN_ERR,
+ "OF: overlay: ERROR: multiple fragments add, update, and/or delete property /testcase-data-2/substation@100/motor-1/electric/name");
+ EXPECT_BEGIN(KERN_ERR,
+ "OF: changeset: apply failed: REMOVE_PROPERTY /testcase-data-2/substation@100/motor-1/electric:name");
+ EXPECT_BEGIN(KERN_ERR,
+ "OF: Error reverting changeset (-19)");
+
unittest(overlay_data_apply("overlay_bad_add_dup_prop", NULL),
"Adding overlay 'overlay_bad_add_dup_prop' failed\n");
+ EXPECT_END(KERN_ERR,
+ "OF: Error reverting changeset (-19)");
+ EXPECT_END(KERN_ERR,
+ "OF: changeset: apply failed: REMOVE_PROPERTY /testcase-data-2/substation@100/motor-1/electric:name");
+ EXPECT_END(KERN_ERR,
+ "OF: overlay: ERROR: multiple fragments add, update, and/or delete property /testcase-data-2/substation@100/motor-1/electric/name");
+ EXPECT_END(KERN_ERR,
+ "OF: overlay: ERROR: multiple fragments add, update, and/or delete property /testcase-data-2/substation@100/motor-1/electric/rpm_avail");
+ EXPECT_END(KERN_ERR,
+ "OF: overlay: ERROR: multiple fragments add and/or delete node /testcase-data-2/substation@100/motor-1/electric");
+
+ /* --- overlay_bad_phandle --- */
+
unittest(overlay_data_apply("overlay_bad_phandle", NULL),
"Adding overlay 'overlay_bad_phandle' failed\n");
+ /* --- overlay_bad_symbol --- */
+
+ EXPECT_BEGIN(KERN_ERR,
+ "OF: changeset: apply failed: REMOVE_PROPERTY /testcase-data-2/substation@100/hvac-medium-2:name");
+ EXPECT_BEGIN(KERN_ERR,
+ "OF: Error reverting changeset (-19)");
+
unittest(overlay_data_apply("overlay_bad_symbol", NULL),
"Adding overlay 'overlay_bad_symbol' failed\n");
+ EXPECT_END(KERN_ERR,
+ "OF: Error reverting changeset (-19)");
+ EXPECT_END(KERN_ERR,
+ "OF: changeset: apply failed: REMOVE_PROPERTY /testcase-data-2/substation@100/hvac-medium-2:name");
+
+ /* --- overlay_bad_unresolved --- */
+
+ EXPECT_BEGIN(KERN_ERR,
+ "OF: resolver: node label 'this_label_does_not_exist' not found in live devicetree symbols table");
+ EXPECT_BEGIN(KERN_ERR,
+ "OF: resolver: overlay phandle fixup failed: -22");
+
+ unittest(overlay_data_apply("overlay_bad_unresolved", NULL),
+ "Adding overlay 'overlay_bad_unresolved' failed\n");
+
+ EXPECT_END(KERN_ERR,
+ "OF: resolver: overlay phandle fixup failed: -22");
+ EXPECT_END(KERN_ERR,
+ "OF: resolver: node label 'this_label_does_not_exist' not found in live devicetree symbols table");
+
return;
err_unlock:
mutex_unlock(&of_mutex);
}
+static int of_unittest_pci_dev_num;
+static int of_unittest_pci_child_num;
+
+/*
+ * PCI device tree node test driver
+ */
+static const struct pci_device_id testdrv_pci_ids[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_REDHAT, 0x5), }, /* PCI_VENDOR_ID_REDHAT */
+ { 0, }
+};
+
+static int testdrv_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ struct overlay_info *info;
+ struct device_node *dn;
+ int ret, ovcs_id;
+ u32 size;
+
+ dn = pdev->dev.of_node;
+ if (!dn) {
+ dev_err(&pdev->dev, "does not find bus endpoint");
+ return -EINVAL;
+ }
+
+ for (info = overlays; info && info->name; info++) {
+ if (!strcmp(info->name, "overlay_pci_node"))
+ break;
+ }
+ if (!info || !info->name) {
+ dev_err(&pdev->dev, "no overlay data for overlay_pci_node");
+ return -ENODEV;
+ }
+
+ size = info->dtbo_end - info->dtbo_begin;
+ ret = of_overlay_fdt_apply(info->dtbo_begin, size, &ovcs_id, dn);
+ of_node_put(dn);
+ if (ret)
+ return ret;
+
+ of_platform_default_populate(dn, NULL, &pdev->dev);
+ pci_set_drvdata(pdev, (void *)(uintptr_t)ovcs_id);
+
+ return 0;
+}
+
+static void testdrv_remove(struct pci_dev *pdev)
+{
+ int ovcs_id = (int)(uintptr_t)pci_get_drvdata(pdev);
+
+ of_platform_depopulate(&pdev->dev);
+ of_overlay_remove(&ovcs_id);
+}
+
+static struct pci_driver testdrv_driver = {
+ .name = "pci_dt_testdrv",
+ .id_table = testdrv_pci_ids,
+ .probe = testdrv_probe,
+ .remove = testdrv_remove,
+};
+
+static int unittest_pci_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ struct device *dev;
+ u64 exp_addr;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENODEV;
+
+ dev = &pdev->dev;
+ while (dev && !dev_is_pci(dev))
+ dev = dev->parent;
+ if (!dev) {
+ pr_err("unable to find parent device\n");
+ return -ENODEV;
+ }
+
+ exp_addr = pci_resource_start(to_pci_dev(dev), 0) + 0x100;
+ unittest(res->start == exp_addr, "Incorrect translated address %llx, expected %llx\n",
+ (u64)res->start, exp_addr);
+
+ of_unittest_pci_child_num++;
+
+ return 0;
+}
+
+static const struct of_device_id unittest_pci_of_match[] = {
+ { .compatible = "unittest-pci" },
+ { }
+};
+
+static struct platform_driver unittest_pci_driver = {
+ .probe = unittest_pci_probe,
+ .driver = {
+ .name = "unittest-pci",
+ .of_match_table = unittest_pci_of_match,
+ },
+};
+
+static int of_unittest_pci_node_verify(struct pci_dev *pdev, bool add)
+{
+ struct device_node *pnp, *np = NULL;
+ struct device *child_dev;
+ char *path = NULL;
+ const __be32 *reg;
+ int rc = 0;
+
+ pnp = pdev->dev.of_node;
+ unittest(pnp, "Failed creating PCI dt node\n");
+ if (!pnp)
+ return -ENODEV;
+
+ if (add) {
+ path = kasprintf(GFP_KERNEL, "%pOF/pci-ep-bus@0/unittest-pci@100", pnp);
+ np = of_find_node_by_path(path);
+ unittest(np, "Failed to get unittest-pci node under PCI node\n");
+ if (!np) {
+ rc = -ENODEV;
+ goto failed;
+ }
+
+ reg = of_get_property(np, "reg", NULL);
+ unittest(reg, "Failed to get reg property\n");
+ if (!reg)
+ rc = -ENODEV;
+ } else {
+ path = kasprintf(GFP_KERNEL, "%pOF/pci-ep-bus@0", pnp);
+ np = of_find_node_by_path(path);
+ unittest(!np, "Child device tree node is not removed\n");
+ child_dev = device_find_any_child(&pdev->dev);
+ unittest(!child_dev, "Child device is not removed\n");
+ put_device(child_dev);
+ }
+
+failed:
+ kfree(path);
+ if (np)
+ of_node_put(np);
+
+ return rc;
+}
+
+static void __init of_unittest_pci_node(void)
+{
+ struct pci_dev *pdev = NULL;
+ int rc;
+
+ if (!IS_ENABLED(CONFIG_PCI_DYNAMIC_OF_NODES))
+ return;
+
+ rc = pci_register_driver(&testdrv_driver);
+ unittest(!rc, "Failed to register pci test driver; rc = %d\n", rc);
+ if (rc)
+ return;
+
+ rc = platform_driver_register(&unittest_pci_driver);
+ if (unittest(!rc, "Failed to register unittest pci driver\n")) {
+ pci_unregister_driver(&testdrv_driver);
+ return;
+ }
+
+ while ((pdev = pci_get_device(PCI_VENDOR_ID_REDHAT, 0x5, pdev)) != NULL) {
+ of_unittest_pci_node_verify(pdev, true);
+ of_unittest_pci_dev_num++;
+ }
+ if (pdev)
+ pci_dev_put(pdev);
+
+ unittest(of_unittest_pci_dev_num,
+ "No test PCI device been found. Please run QEMU with '-device pci-testdev'\n");
+ unittest(of_unittest_pci_dev_num == of_unittest_pci_child_num,
+ "Child device number %d is not expected %d", of_unittest_pci_child_num,
+ of_unittest_pci_dev_num);
+
+ platform_driver_unregister(&unittest_pci_driver);
+ pci_unregister_driver(&testdrv_driver);
+
+ while ((pdev = pci_get_device(PCI_VENDOR_ID_REDHAT, 0x5, pdev)) != NULL)
+ of_unittest_pci_node_verify(pdev, false);
+ if (pdev)
+ pci_dev_put(pdev);
+}
#else
static inline __init void of_unittest_overlay_high_level(void) {}
+static inline __init void of_unittest_pci_node(void) { }
#endif
@@ -2513,6 +4363,11 @@ static int __init of_unittest(void)
struct device_node *np;
int res;
+ pr_info("start of unittest - you will see error messages\n");
+
+ /* Taint the kernel so we know we've run tests. */
+ add_taint(TAINT_TEST, LOCKDEP_STILL_OK);
+
/* adding data for unittest */
res = unittest_data_add();
if (res)
@@ -2527,7 +4382,6 @@ static int __init of_unittest(void)
}
of_node_put(np);
- pr_info("start of unittest - you will see error messages\n");
of_unittest_check_tree_linkage();
of_unittest_check_phandles();
of_unittest_find_node_by_name();
@@ -2538,11 +4392,23 @@ static int __init of_unittest(void)
of_unittest_property_string();
of_unittest_property_copy();
of_unittest_changeset();
+ of_unittest_changeset_prop();
of_unittest_parse_interrupts();
of_unittest_parse_interrupts_extended();
+ of_unittest_irq_refcount();
+ of_unittest_dma_get_max_cpu_address();
+ of_unittest_parse_dma_ranges();
+ of_unittest_pci_dma_ranges();
+ of_unittest_pci_empty_dma_ranges();
+ of_unittest_bus_ranges();
+ of_unittest_bus_3cell_ranges();
+ of_unittest_reg();
+ of_unittest_translate_addr();
of_unittest_match_node();
of_unittest_platform_populate();
of_unittest_overlay();
+ of_unittest_lifecycle();
+ of_unittest_pci_node();
/* Double check linkage after removing testcase data */
of_unittest_check_tree_linkage();