summaryrefslogtreecommitdiff
path: root/drivers/of
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/of')
-rw-r--r--drivers/of/Kconfig14
-rw-r--r--drivers/of/address.c62
-rw-r--r--drivers/of/base.c3
-rw-r--r--drivers/of/device.c10
-rw-r--r--drivers/of/dynamic.c31
-rw-r--r--drivers/of/fdt.c66
-rw-r--r--drivers/of/irq.c12
-rw-r--r--drivers/of/kobj.c2
-rw-r--r--drivers/of/of_reserved_mem.c13
-rw-r--r--drivers/of/overlay.c2
-rw-r--r--drivers/of/platform.c19
-rw-r--r--drivers/of/property.c94
-rw-r--r--drivers/of/unittest-data/testcases_common.dtsi1
-rw-r--r--drivers/of/unittest-data/tests-lifecycle.dtsi8
-rw-r--r--drivers/of/unittest.c150
15 files changed, 347 insertions, 140 deletions
diff --git a/drivers/of/Kconfig b/drivers/of/Kconfig
index 80b5fd44ab1c..644386833a7b 100644
--- a/drivers/of/Kconfig
+++ b/drivers/of/Kconfig
@@ -23,7 +23,19 @@ config OF_UNITTEST
that are executed once at boot time, and the results dumped to the
console.
- If unsure, say N here, but this option is safe to enable.
+ 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_ALL_DTBS
bool "Build all Device Tree Blobs"
diff --git a/drivers/of/address.c b/drivers/of/address.c
index c34ac33b7338..4c0b169ef9bf 100644
--- a/drivers/of/address.c
+++ b/drivers/of/address.c
@@ -626,6 +626,47 @@ u64 of_translate_dma_address(struct device_node *dev, const __be32 *in_addr)
}
EXPORT_SYMBOL(of_translate_dma_address);
+/**
+ * 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;
+ u64 address, size;
+ int na, ns;
+
+ parent = __of_get_dma_parent(dev);
+ if (!parent)
+ return NULL;
+
+ na = of_bus_n_addr_cells(parent);
+ ns = of_bus_n_size_cells(parent);
+
+ of_node_put(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)
{
@@ -965,8 +1006,19 @@ int of_dma_get_range(struct device_node *np, const struct bus_dma_region **map)
}
of_dma_range_parser_init(&parser, node);
- for_each_of_range(&parser, &range)
+ 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) {
+ ret = -EINVAL;
+ goto out;
+ }
r = kcalloc(num_ranges + 1, sizeof(*r), GFP_KERNEL);
if (!r) {
@@ -975,18 +1027,16 @@ int of_dma_get_range(struct device_node *np, const struct bus_dma_region **map)
}
/*
- * Record all info in the generic DMA ranges array for struct device.
+ * 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) {
- pr_err("translation of DMA address(%llx) to CPU address failed node(%pOF)\n",
- range.bus_addr, node);
+ if (range.cpu_addr == OF_BAD_ADDR)
continue;
- }
r->cpu_start = range.cpu_addr;
r->dma_start = range.bus_addr;
r->size = range.size;
diff --git a/drivers/of/base.c b/drivers/of/base.c
index d5a5c35eba72..ac6fde53342f 100644
--- a/drivers/of/base.c
+++ b/drivers/of/base.c
@@ -1884,8 +1884,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);
diff --git a/drivers/of/device.c b/drivers/of/device.c
index c674a13c3055..955bfb3d1a83 100644
--- a/drivers/of/device.c
+++ b/drivers/of/device.c
@@ -248,7 +248,7 @@ 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)
+static ssize_t of_device_get_modalias(const struct device *dev, char *str, ssize_t len)
{
const char *compat;
char *c;
@@ -256,7 +256,7 @@ static ssize_t of_device_get_modalias(struct device *dev, char *str, ssize_t len
ssize_t csize;
ssize_t tsize;
- if ((!dev) || (!dev->of_node))
+ if ((!dev) || (!dev->of_node) || dev->of_node_reused)
return -ENODEV;
/* Name & Type */
@@ -372,11 +372,11 @@ void of_device_uevent(const struct device *dev, struct kobj_uevent_env *env)
mutex_unlock(&of_mutex);
}
-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 */
@@ -385,6 +385,8 @@ int of_device_uevent_modalias(struct device *dev, struct kobj_uevent_env *env)
sl = of_device_get_modalias(dev, &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;
diff --git a/drivers/of/dynamic.c b/drivers/of/dynamic.c
index cd3821a6444f..07d93753b12f 100644
--- a/drivers/of/dynamic.c
+++ b/drivers/of/dynamic.c
@@ -329,10 +329,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))
@@ -357,6 +377,10 @@ 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));
@@ -419,7 +443,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.
*
- * Return: 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)
diff --git a/drivers/of/fdt.c b/drivers/of/fdt.c
index b2272bccf85c..d1a68b6d03b3 100644
--- a/drivers/of/fdt.c
+++ b/drivers/of/fdt.c
@@ -26,7 +26,6 @@
#include <linux/serial_core.h>
#include <linux/sysfs.h>
#include <linux/random.h>
-#include <linux/kmemleak.h>
#include <asm/setup.h> /* for COMMAND_LINE_SIZE */
#include <asm/page.h>
@@ -525,12 +524,9 @@ static int __init __reserved_mem_reserve_reg(unsigned long node,
size = dt_mem_next_cell(dt_root_size_cells, &prop);
if (size &&
- early_init_dt_reserve_memory(base, size, nomap) == 0) {
+ early_init_dt_reserve_memory(base, size, nomap) == 0)
pr_debug("Reserved memory: reserved region for node '%s': base %pa, size %lu MiB\n",
uname, &base, (unsigned long)(size / SZ_1M));
- if (!nomap)
- kmemleak_alloc_phys(base, size, 0);
- }
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));
@@ -1099,7 +1095,7 @@ u64 __init dt_mem_next_cell(int s, const __be32 **cellp)
*/
int __init early_init_dt_scan_memory(void)
{
- int node;
+ int node, found_memory = 0;
const void *fdt = initial_boot_params;
fdt_for_each_subnode(node, fdt, 0) {
@@ -1139,6 +1135,8 @@ int __init early_init_dt_scan_memory(void)
early_init_dt_add_memory_arch(base, size);
+ found_memory = 1;
+
if (!hotpluggable)
continue;
@@ -1147,7 +1145,7 @@ int __init early_init_dt_scan_memory(void)
base, base + size);
}
}
- return 0;
+ return found_memory;
}
int __init early_init_dt_scan_chosen(char *cmdline)
@@ -1161,18 +1159,14 @@ int __init early_init_dt_scan_chosen(char *cmdline)
if (node < 0)
node = fdt_path_offset(fdt, "/chosen@0");
if (node < 0)
- return -ENOENT;
+ /* Handle the cmdline config options even if no /chosen node */
+ goto handle_cmdline;
chosen_node_offset = node;
early_init_dt_check_for_initrd(node);
early_init_dt_check_for_elfcorehdr(node);
- /* Retrieve command line */
- p = of_get_flat_dt_prop(node, "bootargs", &l);
- if (p != NULL && l > 0)
- strscpy(cmdline, p, min(l, COMMAND_LINE_SIZE));
-
rng_seed = of_get_flat_dt_prop(node, "rng-seed", &l);
if (rng_seed && l > 0) {
add_bootloader_randomness(rng_seed, l);
@@ -1185,6 +1179,32 @@ int __init early_init_dt_scan_chosen(char *cmdline)
fdt_totalsize(initial_boot_params));
}
+ /* Retrieve command line */
+ p = of_get_flat_dt_prop(node, "bootargs", &l);
+ if (p != NULL && l > 0)
+ 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
+ * is set in which case we override whatever was found earlier.
+ */
+#ifdef CONFIG_CMDLINE
+#if defined(CONFIG_CMDLINE_EXTEND)
+ strlcat(cmdline, " ", COMMAND_LINE_SIZE);
+ strlcat(cmdline, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
+#elif defined(CONFIG_CMDLINE_FORCE)
+ strscpy(cmdline, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
+#else
+ /* No arguments from boot loader, use kernel's cmdl*/
+ if (!((char *)cmdline)[0])
+ strscpy(cmdline, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
+#endif
+#endif /* CONFIG_CMDLINE */
+
+ pr_debug("Command line is: %s\n", (char *)cmdline);
+
return 0;
}
@@ -1277,26 +1297,6 @@ void __init early_init_dt_scan_nodes(void)
if (rc)
pr_warn("No chosen node found, continuing without\n");
- /*
- * CONFIG_CMDLINE is meant to be a default in case nothing else
- * managed to set the command line, unless CONFIG_CMDLINE_FORCE
- * is set in which case we override whatever was found earlier.
- */
-#ifdef CONFIG_CMDLINE
-#if defined(CONFIG_CMDLINE_EXTEND)
- strlcat(boot_command_line, " ", COMMAND_LINE_SIZE);
- strlcat(boot_command_line, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
-#elif defined(CONFIG_CMDLINE_FORCE)
- strscpy(boot_command_line, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
-#else
- /* No arguments from boot loader, use kernel's cmdl */
- if (!boot_command_line[0])
- strscpy(boot_command_line, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
-#endif
-#endif /* CONFIG_CMDLINE */
-
- pr_debug("Command line is: %s\n", boot_command_line);
-
/* Setup memory, calling early_init_dt_add_memory_arch */
early_init_dt_scan_memory();
diff --git a/drivers/of/irq.c b/drivers/of/irq.c
index e9bf5236ed89..174900072c18 100644
--- a/drivers/of/irq.c
+++ b/drivers/of/irq.c
@@ -438,10 +438,16 @@ 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;
+ }
- return irq_create_of_mapping(&oirq);
+ rc = irq_create_of_mapping(&oirq);
+out:
+ of_node_put(oirq.np);
+
+ return rc;
}
EXPORT_SYMBOL_GPL(of_irq_get);
diff --git a/drivers/of/kobj.c b/drivers/of/kobj.c
index 7d3853a5a09a..3dbce1e6f184 100644
--- a/drivers/of/kobj.c
+++ b/drivers/of/kobj.c
@@ -24,7 +24,7 @@ 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,
};
diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c
index 65f3b02a0e4e..948efa9f99e3 100644
--- a/drivers/of/of_reserved_mem.c
+++ b/drivers/of/of_reserved_mem.c
@@ -48,9 +48,10 @@ static int __init early_init_dt_alloc_reserved_memory_arch(phys_addr_t size,
err = memblock_mark_nomap(base, size);
if (err)
memblock_phys_free(base, size);
- kmemleak_ignore_phys(base);
}
+ kmemleak_ignore_phys(base);
+
return err;
}
@@ -284,6 +285,16 @@ void __init fdt_init_reserved_mem(void)
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");
}
}
}
diff --git a/drivers/of/overlay.c b/drivers/of/overlay.c
index ed4e6c144a68..2e01960f1aeb 100644
--- a/drivers/of/overlay.c
+++ b/drivers/of/overlay.c
@@ -1121,7 +1121,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)
{
diff --git a/drivers/of/platform.c b/drivers/of/platform.c
index 81c8c227ab6b..b2bd2e783445 100644
--- a/drivers/of/platform.c
+++ b/drivers/of/platform.c
@@ -222,7 +222,6 @@ static struct amba_device *of_amba_device_create(struct device_node *node,
struct device *parent)
{
struct amba_device *dev;
- const void *prop;
int ret;
pr_debug("Creating amba device %pOF\n", node);
@@ -250,9 +249,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);
+ of_property_read_u32(node, "arm,primecell-periphid", &dev->periphid);
ret = of_address_to_resource(node, 0, &dev->res);
if (ret) {
@@ -525,10 +522,11 @@ static int __init of_platform_default_populate_init(void)
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_get_property(of_chosen, "linux,bootx-noscreen", NULL)) {
+ 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
@@ -555,16 +553,23 @@ static int __init of_platform_default_populate_init(void)
if (!of_get_property(node, "linux,opened", NULL) ||
!of_get_property(node, "linux,boot-display", NULL))
continue;
- dev = of_platform_device_create(node, "of-display", NULL);
+ dev = of_platform_device_create(node, "of-display.0", NULL);
+ of_node_put(node);
if (WARN_ON(!dev))
return -ENOMEM;
boot_display = node;
+ display_number++;
break;
}
for_each_node_by_type(node, "display") {
+ char buf[14];
+ const char *of_display_format = "of-display.%d";
+
if (!of_get_property(node, "linux,opened", NULL) || node == boot_display)
continue;
- of_platform_device_create(node, "of-display", NULL);
+ ret = snprintf(buf, sizeof(buf), of_display_format, display_number++);
+ if (ret < sizeof(buf))
+ of_platform_device_create(node, buf, NULL);
}
} else {
diff --git a/drivers/of/property.c b/drivers/of/property.c
index 134cfc980b70..ddc75cd50825 100644
--- a/drivers/of/property.c
+++ b/drivers/of/property.c
@@ -1062,20 +1062,6 @@ of_fwnode_device_get_match_data(const struct fwnode_handle *fwnode,
return of_device_get_match_data(dev);
}
-static bool of_is_ancestor_of(struct device_node *test_ancestor,
- struct device_node *child)
-{
- of_node_get(child);
- while (child) {
- if (child == test_ancestor) {
- of_node_put(child);
- return true;
- }
- child = of_get_next_parent(child);
- }
- return false;
-}
-
static struct device_node *of_get_compat_node(struct device_node *np)
{
of_node_get(np);
@@ -1086,7 +1072,7 @@ static struct device_node *of_get_compat_node(struct device_node *np)
np = NULL;
}
- if (of_find_property(np, "compatible", NULL))
+ if (of_property_present(np, "compatible"))
break;
np = of_get_next_parent(np);
@@ -1106,71 +1092,27 @@ static struct device_node *of_get_compat_node_parent(struct device_node *np)
return node;
}
-/**
- * of_link_to_phandle - Add fwnode link to supplier from supplier phandle
- * @con_np: consumer device tree node
- * @sup_np: supplier device tree node
- *
- * Given a phandle to a supplier device tree node (@sup_np), this function
- * finds the device that owns the supplier device tree node and creates a
- * device link from @dev consumer device to the supplier device. This function
- * doesn't create device links for invalid scenarios such as trying to create a
- * link with a parent device as the consumer of its child device. In such
- * cases, it returns an error.
- *
- * Returns:
- * - 0 if fwnode link successfully created to supplier
- * - -EINVAL if the supplier link is invalid and should not be created
- * - -ENODEV if struct device will never be create for supplier
- */
-static int of_link_to_phandle(struct device_node *con_np,
+static void of_link_to_phandle(struct device_node *con_np,
struct device_node *sup_np)
{
- struct device *sup_dev;
- struct device_node *tmp_np = sup_np;
+ struct device_node *tmp_np = of_node_get(sup_np);
- /*
- * Find the device node that contains the supplier phandle. It may be
- * @sup_np or it may be an ancestor of @sup_np.
- */
- sup_np = of_get_compat_node(sup_np);
- if (!sup_np) {
- pr_debug("Not linking %pOFP to %pOFP - No device\n",
- con_np, tmp_np);
- return -ENODEV;
- }
+ /* Check that sup_np and its ancestors are available. */
+ while (tmp_np) {
+ if (of_fwnode_handle(tmp_np)->dev) {
+ of_node_put(tmp_np);
+ break;
+ }
- /*
- * Don't allow linking a device node as a consumer of one of its
- * descendant nodes. By definition, a child node can't be a functional
- * dependency for the parent node.
- */
- if (of_is_ancestor_of(con_np, sup_np)) {
- pr_debug("Not linking %pOFP to %pOFP - is descendant\n",
- con_np, sup_np);
- of_node_put(sup_np);
- return -EINVAL;
- }
+ if (!of_device_is_available(tmp_np)) {
+ of_node_put(tmp_np);
+ return;
+ }
- /*
- * Don't create links to "early devices" that won't have struct devices
- * created for them.
- */
- sup_dev = get_dev_from_fwnode(&sup_np->fwnode);
- if (!sup_dev &&
- (of_node_check_flag(sup_np, OF_POPULATED) ||
- sup_np->fwnode.flags & FWNODE_FLAG_NOT_DEVICE)) {
- pr_debug("Not linking %pOFP to %pOFP - No struct device\n",
- con_np, sup_np);
- of_node_put(sup_np);
- return -ENODEV;
+ tmp_np = of_get_next_parent(tmp_np);
}
- put_device(sup_dev);
fwnode_link_add(of_fwnode_handle(con_np), of_fwnode_handle(sup_np));
- of_node_put(sup_np);
-
- return 0;
}
/**
@@ -1202,8 +1144,8 @@ static struct device_node *parse_prop_cells(struct device_node *np,
if (strcmp(prop_name, list_name))
return NULL;
- if (of_parse_phandle_with_args(np, list_name, cells_name, index,
- &sup_args))
+ if (__of_parse_phandle_with_args(np, list_name, cells_name, 0, index,
+ &sup_args))
return NULL;
return sup_args.np;
@@ -1307,7 +1249,7 @@ 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", 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)
@@ -1358,7 +1300,7 @@ static struct device_node *parse_gpio_compat(struct device_node *np,
* Ignore node with gpio-hog property since its gpios are all provided
* by its parent.
*/
- if (of_find_property(np, "gpio-hog", NULL))
+ if (of_property_read_bool(np, "gpio-hog"))
return NULL;
if (of_parse_phandle_with_args(np, prop_name, "#gpio-cells", index,
diff --git a/drivers/of/unittest-data/testcases_common.dtsi b/drivers/of/unittest-data/testcases_common.dtsi
index 19292bbb4cbb..e7887f2301c1 100644
--- a/drivers/of/unittest-data/testcases_common.dtsi
+++ b/drivers/of/unittest-data/testcases_common.dtsi
@@ -17,3 +17,4 @@
#include "tests-address.dtsi"
#include "tests-platform.dtsi"
#include "tests-overlay.dtsi"
+#include "tests-lifecycle.dtsi"
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.c b/drivers/of/unittest.c
index bc0f1e50a4be..b5a7a31d8bd2 100644
--- a/drivers/of/unittest.c
+++ b/drivers/of/unittest.c
@@ -54,8 +54,9 @@ static struct unittest_results {
* Print the expected message only if the current loglevel will allow
* the actual message to print.
*
- * Do not use EXPECT_BEGIN() or EXPECT_END() for messages generated by
- * pr_debug().
+ * 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__)
@@ -63,6 +64,12 @@ static struct unittest_results {
#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;
@@ -1488,6 +1495,7 @@ static int __init unittest_data_add(void)
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;
}
@@ -2998,6 +3006,143 @@ 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
/*
@@ -3502,6 +3647,7 @@ static int __init of_unittest(void)
of_unittest_match_node();
of_unittest_platform_populate();
of_unittest_overlay();
+ of_unittest_lifecycle();
/* Double check linkage after removing testcase data */
of_unittest_check_tree_linkage();