summaryrefslogtreecommitdiffstats
path: root/drivers/of
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2014-08-14 09:53:39 -0600
committerLinus Torvalds <torvalds@linux-foundation.org>2014-08-14 09:53:39 -0600
commitae36e95cf81c98b111b84317adeb358aaffa80e2 (patch)
tree87ef144d3392cca199448117a737c657b7c4663a /drivers/of
parentcc8a44c671fd3a2c792e9e1f59ea1df52697cc8b (diff)
parent663d3f7c2e5e1b018a4c53277ccfde40329d98ca (diff)
Merge tag 'devicetree-for-linus' of git://git.secretlab.ca/git/linux
Pull device tree updates from Grant Likely: "The branch contains the following device tree changes the v3.17 merge window: Group changes to the device tree. In preparation for adding device tree overlay support, OF_DYNAMIC is reworked so that a set of device tree changes can be prepared and applied to the tree all at once. OF_RECONFIG notifiers see the most significant change here so that users always get a consistent view of the tree. Notifiers generation is moved from before a change to after it, and notifiers for a group of changes are emitted after the entire block of changes have been applied Automatic console selection from DT. Console drivers can now use of_console_check() to see if the device node is specified as a console device. If so then it gets added as a preferred console. UART devices get this support automatically when uart_add_one_port() is called. DT unit tests no longer depend on pre-loaded data in the device tree. Data is loaded dynamically at the start of unit tests, and then unloaded again when the tests have completed. Also contains a few bugfixes for reserved regions and early memory setup" * tag 'devicetree-for-linus' of git://git.secretlab.ca/git/linux: (21 commits) of: Fixing OF Selftest build error drivers: of: add automated assignment of reserved regions to client devices of: Use proper types for checking memory overflow of: typo fix in __of_prop_dup() Adding selftest testdata dynamically into live tree of: Add todo tasklist for Devicetree of: Transactional DT support. of: Reorder device tree changes and notifiers of: Move dynamic node fixups out of powerpc and into common code of: Make sure attached nodes don't carry along extra children of: Make devicetree sysfs update functions consistent. of: Create unlocked versions of node and property add/remove functions OF: Utility helper functions for dynamic nodes of: Move CONFIG_OF_DYNAMIC code into a separate file of: rename of_aliases_mutex to just of_mutex of/platform: Fix of_platform_device_destroy iteration of devices of: Migrate of_find_node_by_name() users to for_each_node_by_name() tty: Update hypervisor tty drivers to use core stdout parsing code. arm/versatile: Add the uart as the stdout device. of: Enable console on serial ports specified by /chosen/stdout-path ...
Diffstat (limited to 'drivers/of')
-rw-r--r--drivers/of/Kconfig3
-rw-r--r--drivers/of/Makefile4
-rw-r--r--drivers/of/base.c451
-rw-r--r--drivers/of/device.c4
-rw-r--r--drivers/of/dynamic.c660
-rw-r--r--drivers/of/fdt.c22
-rw-r--r--drivers/of/of_private.h59
-rw-r--r--drivers/of/of_reserved_mem.c70
-rw-r--r--drivers/of/platform.c32
-rw-r--r--drivers/of/selftest.c235
-rw-r--r--drivers/of/testcase-data/testcases.dts15
-rw-r--r--drivers/of/testcase-data/testcases.dtsi4
12 files changed, 1188 insertions, 371 deletions
diff --git a/drivers/of/Kconfig b/drivers/of/Kconfig
index 2dcb0541012..5160c4eb73c 100644
--- a/drivers/of/Kconfig
+++ b/drivers/of/Kconfig
@@ -9,7 +9,8 @@ menu "Device Tree and Open Firmware support"
config OF_SELFTEST
bool "Device Tree Runtime self tests"
- depends on OF_IRQ
+ depends on OF_IRQ && OF_EARLY_FLATTREE
+ select OF_DYNAMIC
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
diff --git a/drivers/of/Makefile b/drivers/of/Makefile
index 099b1fb00af..2b6a7b129d1 100644
--- a/drivers/of/Makefile
+++ b/drivers/of/Makefile
@@ -1,11 +1,13 @@
obj-y = base.o device.o platform.o
+obj-$(CONFIG_OF_DYNAMIC) += dynamic.o
obj-$(CONFIG_OF_FLATTREE) += fdt.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_SELFTEST) += selftest.o
+obj-$(CONFIG_OF_SELFTEST) += of_selftest.o
+of_selftest-objs := selftest.o testcase-data/testcases.dtb.o
obj-$(CONFIG_OF_MDIO) += of_mdio.o
obj-$(CONFIG_OF_PCI) += of_pci.o
obj-$(CONFIG_OF_PCI_IRQ) += of_pci_irq.o
diff --git a/drivers/of/base.c b/drivers/of/base.c
index b9864806e9b..d8574adf0d6 100644
--- a/drivers/of/base.c
+++ b/drivers/of/base.c
@@ -17,6 +17,7 @@
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
+#include <linux/console.h>
#include <linux/ctype.h>
#include <linux/cpu.h>
#include <linux/module.h>
@@ -35,15 +36,17 @@ struct device_node *of_allnodes;
EXPORT_SYMBOL(of_allnodes);
struct device_node *of_chosen;
struct device_node *of_aliases;
-static struct device_node *of_stdout;
+struct device_node *of_stdout;
-static struct kset *of_kset;
+struct kset *of_kset;
/*
- * Used to protect the of_aliases; but also overloaded to hold off addition of
- * nodes to sysfs
+ * Used to protect the of_aliases, to hold off addition of nodes to sysfs.
+ * This mutex must be held whenever modifications are being made to the
+ * device tree. The of_{attach,detach}_node() and
+ * of_{add,remove,update}_property() helpers make sure this happens.
*/
-DEFINE_MUTEX(of_aliases_mutex);
+DEFINE_MUTEX(of_mutex);
/* use when traversing tree through the allnext, child, sibling,
* or parent members of struct device_node.
@@ -89,79 +92,7 @@ int __weak of_node_to_nid(struct device_node *np)
}
#endif
-#if defined(CONFIG_OF_DYNAMIC)
-/**
- * of_node_get - Increment refcount of a node
- * @node: Node to inc refcount, NULL is supported to
- * simplify writing of callers
- *
- * Returns node.
- */
-struct device_node *of_node_get(struct device_node *node)
-{
- if (node)
- kobject_get(&node->kobj);
- return node;
-}
-EXPORT_SYMBOL(of_node_get);
-
-static inline struct device_node *kobj_to_device_node(struct kobject *kobj)
-{
- return container_of(kobj, struct device_node, kobj);
-}
-
-/**
- * of_node_release - release a dynamically allocated node
- * @kref: kref element of the node to be released
- *
- * In of_node_put() this function is passed to kref_put()
- * as the destructor.
- */
-static void of_node_release(struct kobject *kobj)
-{
- struct device_node *node = kobj_to_device_node(kobj);
- struct property *prop = node->properties;
-
- /* 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 %s\n", node->full_name);
- dump_stack();
- return;
- }
-
- if (!of_node_check_flag(node, OF_DYNAMIC))
- return;
-
- while (prop) {
- struct property *next = prop->next;
- kfree(prop->name);
- kfree(prop->value);
- kfree(prop);
- prop = next;
-
- if (!prop) {
- prop = node->deadprops;
- node->deadprops = NULL;
- }
- }
- kfree(node->full_name);
- kfree(node->data);
- kfree(node);
-}
-
-/**
- * of_node_put - Decrement refcount of a node
- * @node: Node to dec refcount, NULL is supported to
- * simplify writing of callers
- *
- */
-void of_node_put(struct device_node *node)
-{
- if (node)
- kobject_put(&node->kobj);
-}
-EXPORT_SYMBOL(of_node_put);
-#else
+#ifndef CONFIG_OF_DYNAMIC
static void of_node_release(struct kobject *kobj)
{
/* Without CONFIG_OF_DYNAMIC, no nodes gets freed */
@@ -200,13 +131,16 @@ static const char *safe_name(struct kobject *kobj, const char *orig_name)
return name;
}
-static int __of_add_property_sysfs(struct device_node *np, struct property *pp)
+int __of_add_property_sysfs(struct device_node *np, struct property *pp)
{
int rc;
/* Important: Don't leak passwords */
bool secure = strncmp(pp->name, "security-", 9) == 0;
+ if (!of_kset || !of_node_is_attached(np))
+ return 0;
+
sysfs_bin_attr_init(&pp->attr);
pp->attr.attr.name = safe_name(&np->kobj, pp->name);
pp->attr.attr.mode = secure ? S_IRUSR : S_IRUGO;
@@ -218,12 +152,15 @@ static int __of_add_property_sysfs(struct device_node *np, struct property *pp)
return rc;
}
-static int __of_node_add(struct device_node *np)
+int __of_attach_node_sysfs(struct device_node *np)
{
const char *name;
struct property *pp;
int rc;
+ if (!of_kset)
+ return 0;
+
np->kobj.kset = of_kset;
if (!np->parent) {
/* Nodes without parents are new top level trees */
@@ -245,59 +182,20 @@ static int __of_node_add(struct device_node *np)
return 0;
}
-int of_node_add(struct device_node *np)
-{
- int rc = 0;
-
- BUG_ON(!of_node_is_initialized(np));
-
- /*
- * Grab the mutex here so that in a race condition between of_init() and
- * of_node_add(), node addition will still be consistent.
- */
- mutex_lock(&of_aliases_mutex);
- if (of_kset)
- rc = __of_node_add(np);
- else
- /* This scenario may be perfectly valid, but report it anyway */
- pr_info("of_node_add(%s) before of_init()\n", np->full_name);
- mutex_unlock(&of_aliases_mutex);
- return rc;
-}
-
-#if defined(CONFIG_OF_DYNAMIC)
-static void of_node_remove(struct device_node *np)
-{
- struct property *pp;
-
- BUG_ON(!of_node_is_initialized(np));
-
- /* only remove properties if on sysfs */
- if (of_node_is_attached(np)) {
- for_each_property_of_node(np, pp)
- sysfs_remove_bin_file(&np->kobj, &pp->attr);
- kobject_del(&np->kobj);
- }
-
- /* finally remove the kobj_init ref */
- of_node_put(np);
-}
-#endif
-
static int __init of_init(void)
{
struct device_node *np;
/* Create the kset, and register existing nodes */
- mutex_lock(&of_aliases_mutex);
+ mutex_lock(&of_mutex);
of_kset = kset_create_and_add("devicetree", NULL, firmware_kobj);
if (!of_kset) {
- mutex_unlock(&of_aliases_mutex);
+ mutex_unlock(&of_mutex);
return -ENOMEM;
}
for_each_of_allnodes(np)
- __of_node_add(np);
- mutex_unlock(&of_aliases_mutex);
+ __of_attach_node_sysfs(np);
+ mutex_unlock(&of_mutex);
/* Symlink in /proc as required by userspace ABI */
if (of_allnodes)
@@ -369,8 +267,8 @@ EXPORT_SYMBOL(of_find_all_nodes);
* Find a property with a given name for a given node
* and return the value.
*/
-static const void *__of_get_property(const struct device_node *np,
- const char *name, int *lenp)
+const void *__of_get_property(const struct device_node *np,
+ const char *name, int *lenp)
{
struct property *pp = __of_find_property(np, name, lenp);
@@ -1748,32 +1646,10 @@ int of_count_phandle_with_args(const struct device_node *np, const char *list_na
}
EXPORT_SYMBOL(of_count_phandle_with_args);
-#if defined(CONFIG_OF_DYNAMIC)
-static int of_property_notify(int action, struct device_node *np,
- struct property *prop)
-{
- struct of_prop_reconfig pr;
-
- /* only call notifiers if the node is attached */
- if (!of_node_is_attached(np))
- return 0;
-
- pr.dn = np;
- pr.prop = prop;
- return of_reconfig_notify(action, &pr);
-}
-#else
-static int of_property_notify(int action, struct device_node *np,
- struct property *prop)
-{
- return 0;
-}
-#endif
-
/**
* __of_add_property - Add a property to a node without lock operations
*/
-static int __of_add_property(struct device_node *np, struct property *prop)
+int __of_add_property(struct device_node *np, struct property *prop)
{
struct property **next;
@@ -1799,22 +1675,49 @@ int of_add_property(struct device_node *np, struct property *prop)
unsigned long flags;
int rc;
- rc = of_property_notify(OF_RECONFIG_ADD_PROPERTY, np, prop);
- if (rc)
- return 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)
- return rc;
- if (of_node_is_attached(np))
+ if (!rc)
__of_add_property_sysfs(np, prop);
+ mutex_unlock(&of_mutex);
+
+ if (!rc)
+ of_property_notify(OF_RECONFIG_ADD_PROPERTY, np, prop, NULL);
+
return rc;
}
+int __of_remove_property(struct device_node *np, struct property *prop)
+{
+ struct property **next;
+
+ for (next = &np->properties; *next; next = &(*next)->next) {
+ if (*next == prop)
+ break;
+ }
+ if (*next == NULL)
+ return -ENODEV;
+
+ /* found the node */
+ *next = prop->next;
+ prop->next = np->deadprops;
+ np->deadprops = prop;
+
+ return 0;
+}
+
+void __of_remove_property_sysfs(struct device_node *np, struct property *prop)
+{
+ /* at early boot, bail here and defer setup to of_init() */
+ if (of_kset && of_node_is_attached(np))
+ sysfs_remove_bin_file(&np->kobj, &prop->attr);
+}
+
/**
* of_remove_property - Remove a property from a node.
*
@@ -1825,211 +1728,98 @@ int of_add_property(struct device_node *np, struct property *prop)
*/
int of_remove_property(struct device_node *np, struct property *prop)
{
- struct property **next;
unsigned long flags;
- int found = 0;
int rc;
- rc = of_property_notify(OF_RECONFIG_REMOVE_PROPERTY, np, prop);
- if (rc)
- return rc;
+ mutex_lock(&of_mutex);
raw_spin_lock_irqsave(&devtree_lock, flags);
- next = &np->properties;
- while (*next) {
- if (*next == prop) {
- /* found the node */
- *next = prop->next;
- prop->next = np->deadprops;
- np->deadprops = prop;
- found = 1;
- break;
- }
- next = &(*next)->next;
- }
+ rc = __of_remove_property(np, prop);
raw_spin_unlock_irqrestore(&devtree_lock, flags);
- if (!found)
- return -ENODEV;
+ if (!rc)
+ __of_remove_property_sysfs(np, prop);
- /* at early boot, bail hear and defer setup to of_init() */
- if (!of_kset)
- return 0;
+ mutex_unlock(&of_mutex);
- sysfs_remove_bin_file(&np->kobj, &prop->attr);
+ if (!rc)
+ of_property_notify(OF_RECONFIG_REMOVE_PROPERTY, np, prop, NULL);
- return 0;
+ return rc;
}
-/*
- * of_update_property - Update a property in a node, if the property does
- * not exist, add it.
- *
- * Note that we don't actually remove it, since we have given out
- * who-knows-how-many pointers to the data using get-property.
- * Instead we just move the property to the "dead properties" list,
- * and add the new property to the property list
- */
-int of_update_property(struct device_node *np, struct property *newprop)
+int __of_update_property(struct device_node *np, struct property *newprop,
+ struct property **oldpropp)
{
struct property **next, *oldprop;
- unsigned long flags;
- int rc;
-
- rc = of_property_notify(OF_RECONFIG_UPDATE_PROPERTY, np, newprop);
- if (rc)
- return rc;
- if (!newprop->name)
- return -EINVAL;
+ for (next = &np->properties; *next; next = &(*next)->next) {
+ if (of_prop_cmp((*next)->name, newprop->name) == 0)
+ break;
+ }
+ *oldpropp = oldprop = *next;
- raw_spin_lock_irqsave(&devtree_lock, flags);
- next = &np->properties;
- oldprop = __of_find_property(np, newprop->name, NULL);
- if (!oldprop) {
- /* add the new node */
- rc = __of_add_property(np, newprop);
- } else while (*next) {
+ if (oldprop) {
/* replace the node */
- if (*next == oldprop) {
- newprop->next = oldprop->next;
- *next = newprop;
- oldprop->next = np->deadprops;
- np->deadprops = oldprop;
- break;
- }
- next = &(*next)->next;
+ newprop->next = oldprop->next;
+ *next = newprop;
+ oldprop->next = np->deadprops;
+ np->deadprops = oldprop;
+ } else {
+ /* new node */
+ newprop->next = NULL;
+ *next = newprop;
}
- raw_spin_unlock_irqrestore(&devtree_lock, flags);
- if (rc)
- return rc;
+ return 0;
+}
+
+void __of_update_property_sysfs(struct device_node *np, struct property *newprop,
+ struct property *oldprop)
+{
/* At early boot, bail out and defer setup to of_init() */
if (!of_kset)
- return 0;
+ return;
- /* Update the sysfs attribute */
if (oldprop)
sysfs_remove_bin_file(&np->kobj, &oldprop->attr);
__of_add_property_sysfs(np, newprop);
-
- return 0;
}
-#if defined(CONFIG_OF_DYNAMIC)
/*
- * Support for dynamic device trees.
+ * of_update_property - Update a property in a node, if the property does
+ * not exist, add it.
*
- * On some platforms, the device tree can be manipulated at runtime.
- * The routines in this section support adding, removing and changing
- * device tree nodes.
- */
-
-static BLOCKING_NOTIFIER_HEAD(of_reconfig_chain);
-
-int of_reconfig_notifier_register(struct notifier_block *nb)
-{
- return blocking_notifier_chain_register(&of_reconfig_chain, nb);
-}
-EXPORT_SYMBOL_GPL(of_reconfig_notifier_register);
-
-int of_reconfig_notifier_unregister(struct notifier_block *nb)
-{
- return blocking_notifier_chain_unregister(&of_reconfig_chain, nb);
-}
-EXPORT_SYMBOL_GPL(of_reconfig_notifier_unregister);
-
-int of_reconfig_notify(unsigned long action, void *p)
-{
- int rc;
-
- rc = blocking_notifier_call_chain(&of_reconfig_chain, action, p);
- return notifier_to_errno(rc);
-}
-
-/**
- * of_attach_node - Plug a device node into the tree and global list.
+ * Note that we don't actually remove it, since we have given out
+ * who-knows-how-many pointers to the data using get-property.
+ * Instead we just move the property to the "dead properties" list,
+ * and add the new property to the property list
*/
-int of_attach_node(struct device_node *np)
+int of_update_property(struct device_node *np, struct property *newprop)
{
+ struct property *oldprop;
unsigned long flags;
int rc;
- rc = of_reconfig_notify(OF_RECONFIG_ATTACH_NODE, np);
- if (rc)
- return rc;
-
- raw_spin_lock_irqsave(&devtree_lock, flags);
- np->sibling = np->parent->child;
- np->allnext = np->parent->allnext;
- np->parent->allnext = np;
- np->parent->child = np;
- of_node_clear_flag(np, OF_DETACHED);
- raw_spin_unlock_irqrestore(&devtree_lock, flags);
-
- of_node_add(np);
- return 0;
-}
-
-/**
- * of_detach_node - "Unplug" a node from the device tree.
- *
- * The caller must hold a reference to the node. The memory associated with
- * the node is not freed until its refcount goes to zero.
- */
-int of_detach_node(struct device_node *np)
-{
- struct device_node *parent;
- unsigned long flags;
- int rc = 0;
+ if (!newprop->name)
+ return -EINVAL;
- rc = of_reconfig_notify(OF_RECONFIG_DETACH_NODE, np);
- if (rc)
- return rc;
+ 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 (of_node_check_flag(np, OF_DETACHED)) {
- /* someone already detached it */
- raw_spin_unlock_irqrestore(&devtree_lock, flags);
- return rc;
- }
-
- parent = np->parent;
- if (!parent) {
- raw_spin_unlock_irqrestore(&devtree_lock, flags);
- return rc;
- }
+ if (!rc)
+ __of_update_property_sysfs(np, newprop, oldprop);
- if (of_allnodes == np)
- of_allnodes = np->allnext;
- else {
- struct device_node *prev;
- for (prev = of_allnodes;
- prev->allnext != np;
- prev = prev->allnext)
- ;
- prev->allnext = np->allnext;
- }
+ mutex_unlock(&of_mutex);
- if (parent->child == np)
- parent->child = np->sibling;
- else {
- struct device_node *prevsib;
- for (prevsib = np->parent->child;
- prevsib->sibling != np;
- prevsib = prevsib->sibling)
- ;
- prevsib->sibling = np->sibling;
- }
-
- of_node_set_flag(np, OF_DETACHED);
- raw_spin_unlock_irqrestore(&devtree_lock, flags);
+ if (!rc)
+ of_property_notify(OF_RECONFIG_UPDATE_PROPERTY, np, newprop, oldprop);
- of_node_remove(np);
return rc;
}
-#endif /* defined(CONFIG_OF_DYNAMIC) */
static void of_alias_add(struct alias_prop *ap, struct device_node *np,
int id, const char *stem, int stem_len)
@@ -2062,9 +1852,12 @@ void of_alias_scan(void * (*dt_alloc)(u64 size, u64 align))
of_chosen = of_find_node_by_path("/chosen@0");
if (of_chosen) {
+ /* linux,stdout-path and /aliases/stdout are for legacy compatibility */
const char *name = of_get_property(of_chosen, "stdout-path", NULL);
if (!name)
name = of_get_property(of_chosen, "linux,stdout-path", NULL);
+ if (IS_ENABLED(CONFIG_PPC) && !name)
+ name = of_get_property(of_aliases, "stdout", NULL);
if (name)
of_stdout = of_find_node_by_path(name);
}
@@ -2122,7 +1915,7 @@ int of_alias_get_id(struct device_node *np, const char *stem)
struct alias_prop *app;
int id = -ENODEV;
- mutex_lock(&of_aliases_mutex);
+ mutex_lock(&of_mutex);
list_for_each_entry(app, &aliases_lookup, link) {
if (strcmp(app->stem, stem) != 0)
continue;
@@ -2132,7 +1925,7 @@ int of_alias_get_id(struct device_node *np, const char *stem)
break;
}
}
- mutex_unlock(&of_aliases_mutex);
+ mutex_unlock(&of_mutex);
return id;
}
@@ -2180,20 +1973,22 @@ const char *of_prop_next_string(struct property *prop, const char *cur)
EXPORT_SYMBOL_GPL(of_prop_next_string);
/**
- * of_device_is_stdout_path - check if a device node matches the
- * linux,stdout-path property
- *
- * Check if this device node matches the linux,stdout-path property
- * in the chosen node. return true if yes, false otherwise.
+ * 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.
+ *
+ * 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.
*/
-int of_device_is_stdout_path(struct device_node *dn)
+bool of_console_check(struct device_node *dn, char *name, int index)
{
- if (!of_stdout)
+ if (!dn || dn != of_stdout || console_set_on_cmdline)
return false;
-
- return of_stdout == dn;
+ return add_preferred_console(name, index, NULL);
}
-EXPORT_SYMBOL_GPL(of_device_is_stdout_path);
+EXPORT_SYMBOL_GPL(of_console_check);
/**
* of_find_next_cache_node - Find a node's subsidiary cache
diff --git a/drivers/of/device.c b/drivers/of/device.c
index dafb9736ab9..46d6c75c140 100644
--- a/drivers/of/device.c
+++ b/drivers/of/device.c
@@ -160,7 +160,7 @@ void of_device_uevent(struct device *dev, struct kobj_uevent_env *env)
add_uevent_var(env, "OF_COMPATIBLE_N=%d", seen);
seen = 0;
- mutex_lock(&of_aliases_mutex);
+ mutex_lock(&of_mutex);
list_for_each_entry(app, &aliases_lookup, link) {
if (dev->of_node == app->np) {
add_uevent_var(env, "OF_ALIAS_%d=%s", seen,
@@ -168,7 +168,7 @@ void of_device_uevent(struct device *dev, struct kobj_uevent_env *env)
seen++;
}
}
- mutex_unlock(&of_aliases_mutex);
+ mutex_unlock(&of_mutex);
}
int of_device_uevent_modalias(struct device *dev, struct kobj_uevent_env *env)
diff --git a/drivers/of/dynamic.c b/drivers/of/dynamic.c
new file mode 100644
index 00000000000..54fecc49a1f
--- /dev/null
+++ b/drivers/of/dynamic.c
@@ -0,0 +1,660 @@
+/*
+ * Support for dynamic device trees.
+ *
+ * On some platforms, the device tree can be manipulated at runtime.
+ * The routines in this section support adding, removing and changing
+ * device tree nodes.
+ */
+
+#include <linux/of.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/proc_fs.h>
+
+#include "of_private.h"
+
+/**
+ * of_node_get() - Increment refcount of a node
+ * @node: Node to inc refcount, NULL is supported to simplify writing of
+ * callers
+ *
+ * Returns node.
+ */
+struct device_node *of_node_get(struct device_node *node)
+{
+ if (node)
+ kobject_get(&node->kobj);
+ return node;
+}
+EXPORT_SYMBOL(of_node_get);
+
+/**
+ * of_node_put() - Decrement refcount of a node
+ * @node: Node to dec refcount, NULL is supported to simplify writing of
+ * callers
+ */
+void of_node_put(struct device_node *node)
+{
+ if (node)
+ kobject_put(&node->kobj);
+}
+EXPORT_SYMBOL(of_node_put);
+
+void __of_detach_node_sysfs(struct device_node *np)
+{
+ struct property *pp;
+
+ BUG_ON(!of_node_is_initialized(np));
+ if (!of_kset)
+ return;
+
+ /* only remove properties if on sysfs */
+ if (of_node_is_attached(np)) {
+ for_each_property_of_node(np, pp)
+ sysfs_remove_bin_file(&np->kobj, &pp->attr);
+ kobject_del(&np->kobj);
+ }
+
+ /* finally remove the kobj_init ref */
+ of_node_put(np);
+}
+
+static BLOCKING_NOTIFIER_HEAD(of_reconfig_chain);
+
+int of_reconfig_notifier_register(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_register(&of_reconfig_chain, nb);
+}
+EXPORT_SYMBOL_GPL(of_reconfig_notifier_register);
+
+int of_reconfig_notifier_unregister(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_unregister(&of_reconfig_chain, nb);
+}
+EXPORT_SYMBOL_GPL(of_reconfig_notifier_unregister);
+
+int of_reconfig_notify(unsigned long action, void *p)
+{
+ int rc;
+
+ rc = blocking_notifier_call_chain(&of_reconfig_chain, action, p);
+ return notifier_to_errno(rc);
+}
+
+int of_property_notify(int action, struct device_node *np,
+ struct property *prop, struct property *oldprop)
+{
+ struct of_prop_reconfig pr;
+
+ /* only call notifiers if the node is attached */
+ if (!of_node_is_attached(np))
+ return 0;
+
+ pr.dn = np;
+ pr.prop = prop;
+ pr.old_prop = oldprop;
+ return of_reconfig_notify(action, &pr);
+}
+
+void __of_attach_node(struct device_node *np)
+{
+ const __be32 *phandle;
+ int sz;
+
+ np->name = __of_get_property(np, "name", NULL) ? : "<NULL>";
+ np->type = __of_get_property(np, "device_type", NULL) ? : "<NULL>";
+
+ phandle = __of_get_property(np, "phandle", &sz);
+ if (!phandle)
+ phandle = __of_get_property(np, "linux,phandle", &sz);
+ if (IS_ENABLED(PPC_PSERIES) && !phandle)
+ phandle = __of_get_property(np, "ibm,phandle", &sz);
+ np->phandle = (phandle && (sz >= 4)) ? be32_to_cpup(phandle) : 0;
+
+ np->child = NULL;
+ np->sibling = np->parent->child;
+ np->allnext = np->parent->allnext;
+ np->parent->allnext = np;
+ np->parent->child = np;
+ of_node_clear_flag(np, OF_DETACHED);
+}
+
+/**
+ * of_attach_node() - Plug a device node into the tree and global list.
+ */
+int of_attach_node(struct device_node *np)
+{
+ unsigned long flags;
+
+ 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, np);
+
+ return 0;
+}
+
+void __of_detach_node(struct device_node *np)
+{
+ struct device_node *parent;
+
+ if (WARN_ON(of_node_check_flag(np, OF_DETACHED)))
+ return;
+
+ parent = np->parent;
+ if (WARN_ON(!parent))
+ return;
+
+ if (of_allnodes == np)
+ of_allnodes = np->allnext;
+ else {
+ struct device_node *prev;
+ for (prev = of_allnodes;
+ prev->allnext != np;
+ prev = prev->allnext)
+ ;
+ prev->allnext = np->allnext;
+ }
+
+ if (parent->child == np)
+ parent->child = np->sibling;
+ else {
+ struct device_node *prevsib;
+ for (prevsib = np->parent->child;
+ prevsib->sibling != np;
+ prevsib = prevsib->sibling)
+ ;
+ prevsib->sibling = np->sibling;
+ }
+
+ of_node_set_flag(np, OF_DETACHED);
+}
+
+/**
+ * of_detach_node() - "Unplug" a node from the device tree.
+ *
+ * The caller must hold a reference to the node. The memory associated with
+ * the node is not freed until its refcount goes to zero.
+ */
+int of_detach_node(struct device_node *np)
+{
+ unsigned long flags;
+ int rc = 0;
+
+ 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, np);
+
+ return rc;
+}
+
+/**
+ * of_node_release() - release a dynamically allocated node
+ * @kref: kref element of the node to be released
+ *
+ * In of_node_put() this function is passed to kref_put() as the destructor.
+ */
+void of_node_release(struct kobject *kobj)
+{
+ struct device_node *node = kobj_to_device_node(kobj);
+ struct property *prop = node->properties;
+
+ /* 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 %s\n", node->full_name);
+ dump_stack();
+ return;
+ }
+
+ if (!of_node_check_flag(node, OF_DYNAMIC))
+ return;
+
+ while (prop) {
+ struct property *next = prop->next;
+ kfree(prop->name);
+ kfree(prop->value);
+ kfree(prop);
+ prop = next;
+
+ if (!prop) {
+ prop = node->deadprops;
+ node->deadprops = NULL;
+ }
+ }
+ kfree(node->full_name);
+ kfree(node->data);
+ kfree(node);
+}
+
+/**
+ * __of_prop_dup - Copy a property dynamically.
+ * @prop: Property to copy
+ * @allocflags: Allocation flags (typically pass GFP_KERNEL)
+ *
+ * Copy a property by dynamically allocating the memory of both the
+ * property stucture 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.
+ */
+struct property *__of_prop_dup(const struct property *prop, gfp_t allocflags)
+{
+ struct property *new;
+
+ new = kzalloc(sizeof(*new), allocflags);
+ if (!new)
+ return NULL;
+
+ /*
+ * NOTE: There is no check for zero length value.
+ * In case of a boolean property, this will allocate a value
+ * of zero bytes. We do this to work around the use
+ * of of_get_property() calls on boolean values.
+ */
+ new->name = kstrdup(prop->name, allocflags);
+ new->value = kmemdup(prop->value, prop->length, allocflags);
+ new->length = prop->length;
+ if (!new->name || !new->value)
+ goto err_free;
+
+ /* mark the property as dynamic */
+ of_property_set_flag(new, OF_DYNAMIC);
+
+ return new;
+
+ err_free:
+ kfree(new->name);
+ kfree(new->value);
+ kfree(new);
+ return NULL;
+}
+
+/**
+ * __of_node_alloc() - Create an empty device node dynamically.
+ * @full_name: Full name of the new device node
+ * @allocflags: Allocation flags (typically pass GFP_KERNEL)
+ *
+ * Create an empty device tree node, suitable for further modification.
+ * 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.
+ */
+struct device_node *__of_node_alloc(const char *full_name, gfp_t allocflags)
+{
+ struct device_node *node;
+
+ node = kzalloc(sizeof(*node), allocflags);
+ if (!node)
+ return NULL;
+
+ node->full_name = kstrdup(full_name, allocflags);
+ of_node_set_flag(node, OF_DYNAMIC);
+ of_node_set_flag(node, OF_DETACHED);
+ if (!node->full_name)
+ goto err_free;
+
+ of_node_init(node);
+
+ return node;
+
+ err_free:
+ kfree(node->full_name);
+ kfree(node);
+ return NULL;
+}
+
+static void __of_changeset_entry_destroy(struct of_changeset_entry *ce)
+{
+ of_node_put(ce->np);
+ list_del(&ce->node);
+ kfree(ce);
+}
+
+#ifdef DEBUG
+static void __of_changeset_entry_dump(struct of_changeset_entry *ce)
+{
+ switch (ce->action) {
+ case OF_RECONFIG_ADD_PROPERTY:
+ pr_debug("%p: %s %s/%s\n",
+ ce, "ADD_PROPERTY ", ce->np->full_name,
+ ce->prop->name);
+ break;
+ case OF_RECONFIG_REMOVE_PROPERTY:
+ pr_debug("%p: %s %s/%s\n",
+ ce, "REMOVE_PROPERTY", ce->np->full_name,
+ ce->prop->name);
+ break;
+ case OF_RECONFIG_UPDATE_PROPERTY:
+ pr_debug("%p: %s %s/%s\n",
+ ce, "UPDATE_PROPERTY", ce->np->full_name,
+ ce->prop->name);
+ break;
+ case OF_RECONFIG_ATTACH_NODE:
+ pr_debug("%p: %s %s\n",
+ ce, "ATTACH_NODE ", ce->np->full_name);
+ break;
+ case OF_RECONFIG_DETACH_NODE:
+ pr_debug("%p: %s %s\n",
+ ce, "DETACH_NODE ", ce->np->full_name);
+ 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,
+ struct of_changeset_entry *rce)
+{
+ memcpy(rce, ce, sizeof(*rce));
+
+ switch (ce->action) {
+ case OF_RECONFIG_ATTACH_NODE:
+ rce->action = OF_RECONFIG_DETACH_NODE;
+ break;
+ case OF_RECONFIG_DETACH_NODE:
+ rce->action = OF_RECONFIG_ATTACH_NODE;
+ break;
+ case OF_RECONFIG_ADD_PROPERTY:
+ rce->action = OF_RECONFIG_REMOVE_PROPERTY;
+ break;
+ case OF_RECONFIG_REMOVE_PROPERTY:
+ rce->action = OF_RECONFIG_ADD_PROPERTY;
+ break;
+ case OF_RECONFIG_UPDATE_PROPERTY:
+ rce->old_prop = ce->prop;
+ rce->prop = ce->old_prop;
+ break;
+ }
+}
+
+static void __of_changeset_entry_notify(struct of_changeset_entry *ce, bool revert)
+{
+ struct of_changeset_entry ce_inverted;
+ int ret;
+
+ if (revert) {
+ __of_changeset_entry_invert(ce, &ce_inverted);
+ ce = &ce_inverted;
+ }
+
+ switch (ce->action) {
+ case OF_RECONFIG_ATTACH_NODE:
+ case OF_RECONFIG_DETACH_NODE:
+ ret = of_reconfig_notify(ce->action, ce->np);
+ break;
+ case OF_RECONFIG_ADD_PROPERTY:
+ case OF_RECONFIG_REMOVE_PROPERTY:
+ case OF_RECONFIG_UPDATE_PROPERTY:
+ ret = of_property_notify(ce->action, ce->np, ce->prop, ce->old_prop);
+ break;
+ default:
+ pr_err("%s: invalid devicetree changeset action: %i\n", __func__,
+ (int)ce->action);
+ return;
+ }
+
+ if (ret)
+ pr_err("%s: notifier error @%s\n", __func__, ce->np->full_name);
+}
+
+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);
+
+ raw_spin_lock_irqsave(&devtree_lock, flags);
+ switch (ce->action) {
+ case OF_RECONFIG_ATTACH_NODE:
+ __of_attach_node(ce->np);
+ break;
+ case OF_RECONFIG_DETACH_NODE:
+ __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("%s: add_property failed @%s/%s\n",
+ __func__, ce->np->full_name,
+ ce->prop->name);
+ break;
+ }
+ break;
+ case OF_RECONFIG_REMOVE_PROPERTY:
+ ret = __of_remove_property(ce->np, ce->prop);
+ if (ret) {
+ pr_err("%s: remove_property failed @%s/%s\n",
+ __func__, ce->np->full_name,
+ 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("%s: update_property failed @%s/%s\n",
+ __func__, ce->np->full_name,
+ ce->prop->name);
+ break;
+ }
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ raw_spin_unlock_irqrestore(&devtree_lock, flags);
+
+ if (ret)
+ 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)
+{
+ struct of_changeset_entry ce_inverted;
+
+ __of_changeset_entry_invert(ce, &ce_inverted);
+ return __of_changeset_entry_apply(&ce_inverted);
+}
+
+/**
+ * of_changeset_init - Initialize a changeset for use
+ *
+ * @ocs: changeset pointer
+ *
+ * Initialize a changeset structure
+ */
+void of_changeset_init(struct of_changeset *ocs)
+{
+ memset(ocs, 0, sizeof(*ocs));
+ INIT_LIST_HEAD(&ocs->entries);
+}
+
+/**
+ * of_changeset_destroy - Destroy a changeset
+ *
+ * @ocs: changeset pointer
+ *
+ * Destroys a changeset. Note that if a changeset is applied,
+ * its changes to the tree cannot be reverted.
+ */
+void of_changeset_destroy(struct of_changeset *ocs)
+{
+ struct of_changeset_entry *ce, *cen;
+
+ list_for_each_entry_safe_reverse(ce, cen, &ocs->entries, node)
+ __of_changeset_entry_destroy(ce);
+}
+
+/**
+ * of_changeset_apply - Applies a changeset
+ *
+ * @ocs: changeset pointer
+ *
+ * Applies a changeset to the live tree.
+ * Any side-effects of live tree state changes are applied here on
+ * sucess, 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.
+ * On error the partially applied effects are reverted.
+ */
+int of_changeset_apply(struct of_changeset *ocs)
+{
+ struct of_changeset_entry *ce;
+ int ret;
+
+ /* perform the rest of the work */
+ pr_debug("of_changeset: applying...\n");
+ list_for_each_entry(ce, &ocs->entries, node) {
+ ret = __of_changeset_entry_apply(ce);
+ if (ret) {
+ pr_err("%s: Error applying changeset (%d)\n", __func__, ret);
+ list_for_each_entry_continue_reverse(ce, &ocs->entries, node)
+ __of_changeset_entry_revert(ce);
+ return ret;
+ }
+ }
+ pr_debug("of_changeset: applied, emitting notifiers.\n");
+
+ /* drop the global lock while emitting notifiers */
+ mutex_unlock(&of_mutex);
+ list_for_each_entry(ce, &ocs->entries, node)
+ __of_changeset_entry_notify(ce, 0);
+ mutex_lock(&of_mutex);
+ pr_debug("of_changeset: notifiers sent.\n");
+
+ return 0;
+}
+
+/**
+ * of_changeset_revert - Reverts an applied changeset
+ *
+ * @ocs: changeset pointer
+ *
+ * Reverts a changeset returning the state of the tree to what it
+ * 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.
+ */
+int of_changeset_revert(struct of_changeset *ocs)
+{
+ struct of_changeset_entry *ce;
+ int ret;
+
+ pr_debug("of_changeset: reverting...\n");
+ list_for_each_entry_reverse(ce, &ocs->entries, node) {
+ ret = __of_changeset_entry_revert(ce);
+ if (ret) {
+ pr_err("%s: Error reverting changeset (%d)\n", __func__, ret);
+ list_for_each_entry_continue(ce, &ocs->entries, node)
+ __of_changeset_entry_apply(ce);
+ return ret;
+ }
+ }
+ pr_debug("of_changeset: reverted, emitting notifiers.\n");
+
+ /* drop the global lock while emitting notifiers */
+ mutex_unlock(&of_mutex);
+ list_for_each_entry_reverse(ce, &ocs->entries, node)
+ __of_changeset_entry_notify(ce, 1);
+ mutex_lock(&of_mutex);
+ pr_debug("of_changeset: notifiers sent.\n");
+
+ return 0;
+}
+
+/**
+ * of_changeset_action - Perform a changeset action
+ *
+ * @ocs: changeset pointer
+ * @action: action to perform
+ * @np: Pointer to device node
+ * @prop: Pointer to property
+ *
+ * On action being one of:
+ * + OF_RECONFIG_ATTACH_NODE
+ * + OF_RECONFIG_DETACH_NODE,
+ * + 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.
+ */
+int of_changeset_action(struct of_changeset *ocs, unsigned long action,
+ struct device_node *np, struct property *prop)
+{
+ struct of_changeset_entry *ce;
+
+ ce = kzalloc(sizeof(*ce), GFP_KERNEL);
+ if (!ce) {
+ pr_err("%s: Failed to allocate\n", __func__);
+ return -ENOMEM;
+ }
+ /* get a reference to the node */
+ ce->action = 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;
+}
diff --git a/drivers/of/fdt.c b/drivers/of/fdt.c
index 9aa012e6ea0..f46a24ffa3f 100644
--- a/drivers/of/fdt.c
+++ b/drivers/of/fdt.c
@@ -923,24 +923,24 @@ int __init early_init_dt_scan_chosen(unsigned long node, const char *uname,
}
#ifdef CONFIG_HAVE_MEMBLOCK
+#define MAX_PHYS_ADDR ((phys_addr_t)~0)
+
void __init __weak early_init_dt_add_memory_arch(u64 base, u64 size)
{
const u64 phys_offset = __pa(PAGE_OFFSET);
base &= PAGE_MASK;
size &= PAGE_MASK;
- if (sizeof(phys_addr_t) < sizeof(u64)) {
- if (base > ULONG_MAX) {
- pr_warning("Ignoring memory block 0x%llx - 0x%llx\n",
- base, base + size);
- return;
- }
+ if (base > MAX_PHYS_ADDR) {
+ pr_warning("Ignoring memory block 0x%llx - 0x%llx\n",
+ base, base + size);
+ return;
+ }
- if (base + size > ULONG_MAX) {
- pr_warning("Ignoring memory range 0x%lx - 0x%llx\n",
- ULONG_MAX, base + size);
- size = ULONG_MAX - base;
- }
+ if (base + size > MAX_PHYS_ADDR) {
+ pr_warning("Ignoring memory range 0x%lx - 0x%llx\n",
+ ULONG_MAX, base + size);
+ size = MAX_PHYS_ADDR - base;
}
if (base + size < phys_offset) {
diff --git a/drivers/of/of_private.h b/drivers/of/of_private.h
index ff350c8fa7a..858e0a5d9a1 100644
--- a/drivers/of/of_private.h
+++ b/drivers/of/of_private.h
@@ -31,6 +31,63 @@ struct alias_prop {
char stem[0];
};
-extern struct mutex of_aliases_mutex;
+extern struct mutex of_mutex;
extern struct list_head aliases_lookup;
+extern struct kset *of_kset;
+
+
+static inline struct device_node *kobj_to_device_node(struct kobject *kobj)
+{
+ return container_of(kobj, struct device_node, kobj);
+}
+
+#if defined(CONFIG_OF_DYNAMIC)
+extern int of_property_notify(int action, struct device_node *np,
+ struct property *prop, struct property *old_prop);
+extern void of_node_release(struct kobject *kobj);
+#else /* CONFIG_OF_DYNAMIC */
+static inline int of_property_notify(int action, struct device_node *np,
+ struct property *prop, struct property *old_prop)
+{
+ return 0;
+}
+#endif /* CONFIG_OF_DYNAMIC */
+
+/**
+ * General utilities for working with live trees.
+ *
+ * All functions with two leading underscores operate
+ * without taking node references, so you either have to
+ * own the devtree lock or work on detached trees only.
+ */
+struct property *__of_prop_dup(const struct property *prop, gfp_t allocflags);
+struct device_node *__of_node_alloc(const char *full_name, gfp_t allocflags);
+
+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 void __of_attach_node(struct device_node *np);
+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);
+
+/* iterators for transactions, used for overlays */
+/* forward iterator */
+#define for_each_transaction_entry(_oft, _te) \
+ list_for_each_entry(_te, &(_oft)->te_list, node)
+
+/* reverse iterator */
+#define for_each_transaction_entry_reverse(_oft, _te) \
+ list_for_each_entry_reverse(_te, &(_oft)->te_list, node)
+
#endif /* _LINUX_OF_PRIVATE_H */
diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c
index 632aae86137..59fb12e84e6 100644
--- a/drivers/of/of_reserved_mem.c
+++ b/drivers/of/of_reserved_mem.c
@@ -206,8 +206,16 @@ void __init fdt_init_reserved_mem(void)
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;
+ 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);
+
if (rmem->size == 0)
err = __reserved_mem_alloc_size(node, rmem->name,
&rmem->base, &rmem->size);
@@ -215,3 +223,65 @@ void __init fdt_init_reserved_mem(void)
__reserved_mem_init_node(rmem);
}
}
+
+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;
+}
+
+/**
+ * of_reserved_mem_device_init() - assign reserved memory region to given device
+ *
+ * This function assign memory region pointed by "memory-region" device tree
+ * property to the given device.
+ */
+void of_reserved_mem_device_init(struct device *dev)
+{
+ struct reserved_mem *rmem;
+ struct device_node *np;
+
+ np = of_parse_phandle(dev->of_node, "memory-region", 0);
+ if (!np)
+ return;
+
+ rmem = __find_rmem(np);
+ of_node_put(np);
+
+ if (!rmem || !rmem->ops || !rmem->ops->device_init)
+ return;
+
+ rmem->ops->device_init(rmem, dev);
+ dev_info(dev, "assigned reserved memory node %s\n", rmem->name);
+}
+
+/**
+ * of_reserved_mem_device_release() - release reserved memory device structures
+ *
+ * This function releases structures allocated for memory region handling for
+ * the given device.
+ */
+void of_reserved_mem_device_release(struct device *dev)
+{
+ struct reserved_mem *rmem;
+ struct device_node *np;
+
+ np = of_parse_phandle(dev->of_node, "memory-region", 0);
+ if (!np)
+ return;
+
+ rmem = __find_rmem(np);
+ of_node_put(np);
+
+ if (!rmem || !rmem->ops || !rmem->ops->device_release)
+ return;
+
+ rmem->ops->device_release(rmem, dev);
+}
diff --git a/drivers/of/platform.c b/drivers/of/platform.c
index 500436f9be7..0197725e033 100644
--- a/drivers/of/platform.c
+++ b/drivers/of/platform.c
@@ -422,6 +422,7 @@ static int of_platform_bus_create(struct device_node *bus,
break;
}
}
+ of_node_set_flag(bus, OF_POPULATED_BUS);
return rc;
}
@@ -508,19 +509,13 @@ EXPORT_SYMBOL_GPL(of_platform_populate);
static int of_platform_device_destroy(struct device *dev, void *data)
{
- bool *children_left = data;
-
/* Do not touch devices not populated from the device tree */
- if (!dev->of_node || !of_node_check_flag(dev->of_node, OF_POPULATED)) {
- *children_left = true;
+ if (!dev->of_node || !of_node_check_flag(dev->of_node, OF_POPULATED))
return 0;
- }
- /* Recurse, but don't touch this device if it has any children left */
- if (of_platform_depopulate(dev) != 0) {
- *children_left = true;
- return 0;
- }
+ /* Recurse for any nodes that were treated as busses */
+ if (of_node_check_flag(dev->of_node, OF_POPULATED_BUS))
+ device_for_each_child(dev, NULL, of_platform_device_destroy);
if (dev->bus == &platform_bus_type)
platform_device_unregister(to_platform_device(dev));
@@ -528,19 +523,15 @@ static int of_platform_device_destroy(struct device *dev, void *data)
else if (dev->bus == &amba_bustype)
amba_device_unregister(to_amba_device(dev));
#endif
- else {
- *children_left = true;
- return 0;
- }
of_node_clear_flag(dev->of_node, OF_POPULATED);
-
+ of_node_clear_flag(dev->of_node, OF_POPULATED_BUS);
return 0;
}
/**
* of_platform_depopulate() - Remove devices populated from device tree
- * @parent: device which childred will be removed
+ * @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
@@ -550,14 +541,9 @@ static int of_platform_device_destroy(struct device *dev, void *data)
* Returns 0 when all children devices have been removed or
* -EBUSY when some children remained.
*/
-int of_platform_depopulate(struct device *parent)
+void of_platform_depopulate(struct device *parent)
{
- bool children_left = false;
-
- device_for_each_child(parent, &children_left,
- of_platform_device_destroy);
-
- return children_left ? -EBUSY : 0;
+ device_for_each_child(parent, NULL, of_platform_device_destroy);
}
EXPORT_SYMBOL_GPL(of_platform_depopulate);
diff --git a/drivers/of/selftest.c b/drivers/of/selftest.c
index 077314eebb9..d4100266783 100644
--- a/drivers/of/selftest.c
+++ b/drivers/of/selftest.c
@@ -9,6 +9,7 @@
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/of.h>
+#include <linux/of_fdt.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/list.h>
@@ -16,11 +17,17 @@
#include <linux/slab.h>
#include <linux/device.h>
+#include "of_private.h"
+
static struct selftest_results {
int passed;
int failed;
} selftest_results;
+#define NO_OF_NODES 2
+static struct device_node *nodes[NO_OF_NODES];
+static int last_node_index;
+
#define selftest(result, fmt, ...) { \
if (!(result)) { \
selftest_results.failed++; \
@@ -266,6 +273,81 @@ static void __init of_selftest_property_match_string(void)
selftest(rc == -EILSEQ, "unterminated string; rc=%i", rc);
}
+#define propcmp(p1, p2) (((p1)->length == (p2)->length) && \
+ (p1)->value && (p2)->value && \
+ !memcmp((p1)->value, (p2)->value, (p1)->length) && \
+ !strcmp((p1)->name, (p2)->name))
+static void __init of_selftest_property_copy(void)
+{
+#ifdef CONFIG_OF_DYNAMIC
+ struct property p1 = { .name = "p1", .length = 0, .value = "" };
+ struct property p2 = { .name = "p2", .length = 5, .value = "abcd" };
+ struct property *new;
+
+ new = __of_prop_dup(&p1, GFP_KERNEL);
+ selftest(new && propcmp(&p1, new), "empty property didn't copy correctly\n");
+ kfree(new->value);
+ kfree(new->name);
+ kfree(new);
+
+ new = __of_prop_dup(&p2, GFP_KERNEL);
+ selftest(new && propcmp(&p2, new), "non-empty property didn't copy correctly\n");
+ kfree(new->value);
+ kfree(new->name);
+ kfree(new);
+#endif
+}
+
+static void __init of_selftest_changeset(void)
+{
+#ifdef CONFIG_OF_DYNAMIC
+ struct property *ppadd, padd = { .name = "prop-add", .length = 0, .value = "" };
+ struct property *ppupdate, pupdate = { .name = "prop-update", .length = 5, .value = "abcd" };
+ struct property *ppremove;
+ struct device_node *n1, *n2, *n21, *nremove, *parent;
+ struct of_changeset chgset;
+
+ of_changeset_init(&chgset);
+ n1 = __of_node_alloc("/testcase-data/changeset/n1", GFP_KERNEL);
+ selftest(n1, "testcase setup failure\n");
+ n2 = __of_node_alloc("/testcase-data/changeset/n2", GFP_KERNEL);
+ selftest(n2, "testcase setup failure\n");
+ n21 = __of_node_alloc("/testcase-data/changeset/n2/n21", GFP_KERNEL);
+ selftest(n21, "testcase setup failure %p\n", n21);
+ nremove = of_find_node_by_path("/testcase-data/changeset/node-remove");
+ selftest(nremove, "testcase setup failure\n");
+ ppadd = __of_prop_dup(&padd, GFP_KERNEL);
+ selftest(ppadd, "testcase setup failure\n");
+ ppupdate = __of_prop_dup(&pupdate, GFP_KERNEL);
+ selftest(ppupdate, "testcase setup failure\n");
+ parent = nremove->parent;
+ n1->parent = parent;
+ n2->parent = parent;
+ n21->parent = n2;
+ n2->child = n21;
+ ppremove = of_find_property(parent, "prop-remove", NULL);
+ selftest(ppremove, "failed to find removal prop");
+
+ of_changeset_init(&chgset);
+ selftest(!of_changeset_attach_node(&chgset, n1), "fail attach n1\n");
+ selftest(!of_changeset_attach_node(&chgset, n2), "fail attach n2\n");
+ selftest(!of_changeset_detach_node(&chgset, nremove), "fail remove node\n");
+ selftest(!of_changeset_attach_node(&chgset, n21), "fail attach n21\n");
+ selftest(!of_changeset_add_property(&chgset, parent, ppadd), "fail add prop\n");
+ selftest(!of_changeset_update_property(&chgset, parent, ppupdate), "fail update prop\n");
+ selftest(!of_changeset_remove_property(&chgset, parent, ppremove), "fail remove prop\n");
+ mutex_lock(&of_mutex);
+ selftest(!of_changeset_apply(&chgset), "apply failed\n");
+ mutex_unlock(&of_mutex);
+
+ mutex_lock(&of_mutex);
+ selftest(!of_changeset_revert(&chgset), "revert failed\n");
+ mutex_unlock(&of_mutex);
+
+ of_changeset_destroy(&chgset);
+#endif
+}
+
static void __init of_selftest_parse_interrupts(void)
{
struct device_node *np;
@@ -517,9 +599,156 @@ static void __init of_selftest_platform_populate(void)
}
}
+/**
+ * update_node_properties - adds the properties
+ * of np into dup node (present in live tree) and
+ * updates parent of children of np to dup.
+ *
+ * @np: node already present in live tree
+ * @dup: node present in live tree to be updated
+ */
+static void update_node_properties(struct device_node *np,
+ struct device_node *dup)
+{
+ struct property *prop;
+ struct device_node *child;
+
+ for_each_property_of_node(np, prop)
+ of_add_property(dup, prop);
+
+ for_each_child_of_node(np, child)
+ child->parent = dup;
+}
+
+/**
+ * attach_node_and_children - attaches nodes
+ * and its children to live tree
+ *
+ * @np: Node to attach to live tree
+ */
+static int attach_node_and_children(struct device_node *np)
+{
+ struct device_node *next, *root = np, *dup;
+
+ if (!np) {
+ pr_warn("%s: No tree to attach; not running tests\n",
+ __func__);
+ return -ENODATA;
+ }
+
+
+ /* skip root node */
+ np = np->child;
+ /* storing a copy in temporary node */
+ dup = np;
+
+ while (dup) {
+ nodes[last_node_index++] = dup;
+ dup = dup->sibling;
+ }
+ dup = NULL;
+
+ while (np) {
+ next = np->allnext;
+ dup = of_find_node_by_path(np->full_name);
+ if (dup)
+ update_node_properties(np, dup);
+ else {
+ np->child = NULL;
+ if (np->parent == root)
+ np->parent = of_allnodes;
+ of_attach_node(np);
+ }
+ np = next;
+ }
+
+ return 0;
+}
+
+/**
+ * selftest_data_add - Reads, copies data from
+ * linked tree and attaches it to the live tree
+ */
+static int __init selftest_data_add(void)
+{
+ void *selftest_data;
+ struct device_node *selftest_data_node;
+ extern uint8_t __dtb_testcases_begin[];
+ extern uint8_t __dtb_testcases_end[];
+ const int size = __dtb_testcases_end - __dtb_testcases_begin;
+
+ if (!size || !of_allnodes) {
+ pr_warn("%s: No testcase data to attach; not running tests\n",
+ __func__);
+ return -ENODATA;
+ }
+
+ /* creating copy */
+ selftest_data = kmemdup(__dtb_testcases_begin, size, GFP_KERNEL);
+
+ if (!selftest_data) {
+ pr_warn("%s: Failed to allocate memory for selftest_data; "
+ "not running tests\n", __func__);
+ return -ENOMEM;
+ }
+ of_fdt_unflatten_tree(selftest_data, &selftest_data_node);
+
+ /* attach the sub-tree to live tree */
+ return attach_node_and_children(selftest_data_node);
+}
+
+/**
+ * detach_node_and_children - detaches node
+ * and its children from live tree
+ *
+ * @np: Node to detach from live tree
+ */
+static void detach_node_and_children(struct device_node *np)
+{
+ while (np->child)
+ detach_node_and_children(np->child);
+
+ while (np->sibling)
+ detach_node_and_children(np->sibling);
+
+ of_detach_node(np);
+}
+
+/**
+ * selftest_data_remove - removes the selftest data
+ * nodes from the live tree
+ */
+static void selftest_data_remove(void)
+{
+ struct device_node *np;
+ struct property *prop;
+
+ while (last_node_index >= 0) {
+ if (nodes[last_node_index]) {
+ np = of_find_node_by_path(nodes[last_node_index]->full_name);
+ if (strcmp(np->full_name, "/aliases") != 0) {
+ detach_node_and_children(np->child);
+ of_detach_node(np);
+ } else {
+ for_each_property_of_node(np, prop) {
+ if (strcmp(prop->name, "testcase-alias") == 0)
+ of_remove_property(np, prop);
+ }
+ }
+ }
+ last_node_index--;
+ }
+}
+
static int __init of_selftest(void)
{
struct device_node *np;
+ int res;
+
+ /* adding data for selftest */
+ res = selftest_data_add();
+ if (res)
+ return res;
np = of_find_node_by_path("/testcase-data/phandle-tests/consumer-a");
if (!np) {
@@ -533,12 +762,18 @@ static int __init of_selftest(void)
of_selftest_dynamic();
of_selftest_parse_phandle_with_args();
of_selftest_property_match_string();
+ of_selftest_property_copy();
+ of_selftest_changeset();
of_selftest_parse_interrupts();
of_selftest_parse_interrupts_extended();
of_selftest_match_node();
of_selftest_platform_populate();
pr_info("end of selftest - %i passed, %i failed\n",
selftest_results.passed, selftest_results.failed);
+
+ /* removing selftest data from live tree */
+ selftest_data_remove();
+
return 0;
}
late_initcall(of_selftest);
diff --git a/drivers/of/testcase-data/testcases.dts b/drivers/of/testcase-data/testcases.dts
new file mode 100644
index 00000000000..219ef9324e9
--- /dev/null
+++ b/drivers/of/testcase-data/testcases.dts
@@ -0,0 +1,15 @@
+/dts-v1/;
+/ {
+ 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-platform.dtsi"
diff --git a/drivers/of/testcase-data/testcases.dtsi b/drivers/of/testcase-data/testcases.dtsi
deleted file mode 100644
index 6d8d980ac85..00000000000
--- a/drivers/of/testcase-data/testcases.dtsi
+++ /dev/null
@@ -1,4 +0,0 @@
-#include "tests-phandle.dtsi"
-#include "tests-interrupts.dtsi"
-#include "tests-match.dtsi"
-#include "tests-platform.dtsi"