summaryrefslogtreecommitdiffstats
path: root/drivers/of
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/of')
-rw-r--r--drivers/of/address.c13
-rw-r--r--drivers/of/base.c33
-rw-r--r--drivers/of/device.c7
-rw-r--r--drivers/of/irq.c84
-rw-r--r--drivers/of/of_private.h7
-rw-r--r--drivers/of/overlay.c10
-rw-r--r--drivers/of/platform.c8
-rw-r--r--drivers/of/property.c33
-rw-r--r--drivers/of/resolver.c41
-rw-r--r--drivers/of/unittest-data/tests-interrupts.dtsi13
-rw-r--r--drivers/of/unittest.c67
11 files changed, 232 insertions, 84 deletions
diff --git a/drivers/of/address.c b/drivers/of/address.c
index d177a2b9edaf..f0f8f0dd191c 100644
--- a/drivers/of/address.c
+++ b/drivers/of/address.c
@@ -1031,20 +1031,15 @@ EXPORT_SYMBOL_GPL(of_dma_is_coherent);
*
* Returns true if the "nonposted-mmio" property was found for
* the device's bus.
- *
- * This is currently only enabled on builds that support Apple ARM devices, as
- * an optimization.
*/
static bool of_mmio_is_nonposted(const struct device_node *np)
{
- if (!IS_ENABLED(CONFIG_ARCH_APPLE))
- return false;
-
struct device_node *parent __free(device_node) = of_get_parent(np);
- if (!parent)
- return false;
- return of_property_read_bool(parent, "nonposted-mmio");
+ 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,
diff --git a/drivers/of/base.c b/drivers/of/base.c
index af6c68bbb427..7043acd971a0 100644
--- a/drivers/of/base.c
+++ b/drivers/of/base.c
@@ -824,6 +824,33 @@ struct device_node *of_get_child_by_name(const struct device_node *node,
}
EXPORT_SYMBOL(of_get_child_by_name);
+/**
+ * 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)
{
@@ -1651,7 +1678,7 @@ int __of_add_property(struct device_node *np, struct property *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 */
rc = -EEXIST;
goto out_unlock;
@@ -1855,9 +1882,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);
diff --git a/drivers/of/device.c b/drivers/of/device.c
index edf3be197265..5053e5d532cc 100644
--- a/drivers/of/device.c
+++ b/drivers/of/device.c
@@ -99,6 +99,11 @@ int of_dma_configure_id(struct device *dev, struct device_node *np,
bool coherent, set_map = false;
int ret;
+ if (dev->dma_range_map) {
+ dev_dbg(dev, "dma_range_map already set\n");
+ goto skip_map;
+ }
+
if (np == dev->of_node)
bus_np = __of_get_dma_parent(np);
else
@@ -119,7 +124,7 @@ int of_dma_configure_id(struct device *dev, struct device_node *np,
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
diff --git a/drivers/of/irq.c b/drivers/of/irq.c
index 6c843d54ebb1..f8ad79b9b1c9 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>
@@ -38,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);
@@ -50,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
*
- * Return: 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)
{
@@ -165,6 +170,8 @@ const __be32 *of_irq_parse_imap_parent(const __be32 *imap, int len, struct of_ph
* 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)
{
@@ -310,6 +317,12 @@ int of_irq_parse_raw(const __be32 *addr, struct of_phandle_args *out_irq)
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;
}
@@ -339,10 +352,12 @@ EXPORT_SYMBOL_GPL(of_irq_parse_raw);
* 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, addr_len;
@@ -367,41 +382,33 @@ int of_irq_parse_one(struct device_node *device, int index, struct of_phandle_ar
/* 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_buf, 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_buf, 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);
@@ -505,8 +512,10 @@ 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;
}
@@ -623,6 +632,8 @@ void __init of_irq_init(const struct of_device_id *matches)
__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;
}
@@ -653,6 +664,7 @@ 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);
}
diff --git a/drivers/of/of_private.h b/drivers/of/of_private.h
index 1bdc7ceef3c5..df0bb00349e0 100644
--- a/drivers/of/of_private.h
+++ b/drivers/of/of_private.h
@@ -208,6 +208,13 @@ static void __maybe_unused of_dump_addr(const char *s, const __be32 *addr, int n
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
diff --git a/drivers/of/overlay.c b/drivers/of/overlay.c
index 434f6dd6a86c..1af6f52d0708 100644
--- a/drivers/of/overlay.c
+++ b/drivers/of/overlay.c
@@ -84,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
@@ -304,9 +310,7 @@ static int add_changeset_property(struct overlay_changeset *ovcs,
int ret = 0;
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)
diff --git a/drivers/of/platform.c b/drivers/of/platform.c
index c6d8afb284e8..f77cb19973a5 100644
--- a/drivers/of/platform.c
+++ b/drivers/of/platform.c
@@ -334,7 +334,7 @@ static int of_platform_bus_create(struct device_node *bus,
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;
@@ -536,8 +536,8 @@ static int __init of_platform_default_populate_init(void)
* ignore errors for the rest.
*/
for_each_node_by_type(node, "display") {
- if (!of_get_property(node, "linux,opened", NULL) ||
- !of_get_property(node, "linux,boot-display", NULL))
+ 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);
@@ -551,7 +551,7 @@ static int __init of_platform_default_populate_init(void)
char buf[14];
const char *of_display_format = "of-display.%d";
- if (!of_get_property(node, "linux,opened", NULL) || node == boot_display)
+ 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))
diff --git a/drivers/of/property.c b/drivers/of/property.c
index 208d922cc24c..c1feb631e383 100644
--- a/drivers/of/property.c
+++ b/drivers/of/property.c
@@ -148,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.
diff --git a/drivers/of/resolver.c b/drivers/of/resolver.c
index 779db058c42f..86424e145919 100644
--- a/drivers/of/resolver.c
+++ b/drivers/of/resolver.c
@@ -161,9 +161,7 @@ static int adjust_local_phandle_references(const struct device_node *local_fixup
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)
@@ -249,25 +247,22 @@ static int adjust_local_phandle_references(const struct device_node *local_fixup
*/
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;
@@ -279,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;
@@ -288,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) {
@@ -311,14 +303,12 @@ int of_resolve_phandles(struct device_node *overlay)
if (err) {
pr_err("node label '%s' not found in live devicetree symbols table\n",
prop->name);
- goto out;
+ 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);
@@ -328,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/tests-interrupts.dtsi b/drivers/of/unittest-data/tests-interrupts.dtsi
index 7c9f31cc131b..4ccb54f91c30 100644
--- a/drivers/of/unittest-data/tests-interrupts.dtsi
+++ b/drivers/of/unittest-data/tests-interrupts.dtsi
@@ -50,6 +50,13 @@
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>;
@@ -60,6 +67,12 @@
interrupts = <1>, <2>, <3>, <4>;
};
+ interrupts2 {
+ reg = <0x6000 0x100>;
+ interrupt-parent = <&test_intc_intmap0>;
+ interrupts = <1>;
+ };
+
interrupts-extended0 {
reg = <0x5000 0x100>;
/*
diff --git a/drivers/of/unittest.c b/drivers/of/unittest.c
index f88ddb1cf5d7..64d301893af7 100644
--- a/drivers/of/unittest.c
+++ b/drivers/of/unittest.c
@@ -1654,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 */
@@ -4324,6 +4390,7 @@ static int __init of_unittest(void)
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();