aboutsummaryrefslogtreecommitdiff
path: root/drivers/of
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/of')
-rw-r--r--drivers/of/Kconfig11
-rw-r--r--drivers/of/Makefile5
-rw-r--r--drivers/of/address.c23
-rw-r--r--drivers/of/base.c175
-rw-r--r--drivers/of/device.c84
-rw-r--r--drivers/of/dynamic.c220
-rw-r--r--drivers/of/fdt.c113
-rw-r--r--drivers/of/irq.c51
-rw-r--r--drivers/of/of_mdio.c19
-rw-r--r--drivers/of/of_pci.c32
-rw-r--r--drivers/of/of_private.h5
-rw-r--r--drivers/of/of_reserved_mem.c6
-rw-r--r--drivers/of/overlay.c552
-rw-r--r--drivers/of/pdt.c27
-rw-r--r--drivers/of/platform.c139
-rw-r--r--drivers/of/resolver.c128
-rw-r--r--drivers/of/testcase-data/testcases.dts50
-rw-r--r--drivers/of/unittest-data/testcases.dts79
-rw-r--r--drivers/of/unittest-data/tests-interrupts.dtsi (renamed from drivers/of/testcase-data/tests-interrupts.dtsi)0
-rw-r--r--drivers/of/unittest-data/tests-match.dtsi (renamed from drivers/of/testcase-data/tests-match.dtsi)0
-rw-r--r--drivers/of/unittest-data/tests-overlay.dtsi235
-rw-r--r--drivers/of/unittest-data/tests-phandle.dtsi (renamed from drivers/of/testcase-data/tests-phandle.dtsi)0
-rw-r--r--drivers/of/unittest-data/tests-platform.dtsi (renamed from drivers/of/testcase-data/tests-platform.dtsi)0
-rw-r--r--drivers/of/unittest.c (renamed from drivers/of/selftest.c)734
24 files changed, 2245 insertions, 443 deletions
diff --git a/drivers/of/Kconfig b/drivers/of/Kconfig
index 1a13f5b722c5..7bcaeec876c0 100644
--- a/drivers/of/Kconfig
+++ b/drivers/of/Kconfig
@@ -7,10 +7,9 @@ config OF
menu "Device Tree and Open Firmware support"
depends on OF
-config OF_SELFTEST
- bool "Device Tree Runtime self tests"
+config OF_UNITTEST
+ bool "Device Tree runtime unit tests"
depends on OF_IRQ && OF_EARLY_FLATTREE
- select OF_DYNAMIC
select OF_RESOLVE
help
This option builds in test cases for the device tree infrastructure
@@ -23,6 +22,7 @@ config OF_FLATTREE
bool
select DTC
select LIBFDT
+ select CRC32
config OF_EARLY_FLATTREE
bool
@@ -83,4 +83,9 @@ config OF_RESERVED_MEM
config OF_RESOLVE
bool
+config OF_OVERLAY
+ bool "Device Tree overlays"
+ select OF_DYNAMIC
+ select OF_RESOLVE
+
endmenu # OF
diff --git a/drivers/of/Makefile b/drivers/of/Makefile
index ca9209ce50cd..7563f36c71db 100644
--- a/drivers/of/Makefile
+++ b/drivers/of/Makefile
@@ -6,14 +6,15 @@ 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) += of_selftest.o
-of_selftest-objs := selftest.o testcase-data/testcases.dtb.o
+obj-$(CONFIG_OF_UNITTEST) += of_unittest.o
+of_unittest-objs := unittest.o unittest-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
obj-$(CONFIG_OF_MTD) += of_mtd.o
obj-$(CONFIG_OF_RESERVED_MEM) += of_reserved_mem.o
obj-$(CONFIG_OF_RESOLVE) += resolver.o
+obj-$(CONFIG_OF_OVERLAY) += overlay.o
CFLAGS_fdt.o = -I$(src)/../../scripts/dtc/libfdt
CFLAGS_fdt_address.o = -I$(src)/../../scripts/dtc/libfdt
diff --git a/drivers/of/address.c b/drivers/of/address.c
index 06af494184d6..d5f3b07177f9 100644
--- a/drivers/of/address.c
+++ b/drivers/of/address.c
@@ -450,12 +450,17 @@ static struct of_bus *of_match_bus(struct device_node *np)
return NULL;
}
-static int of_empty_ranges_quirk(void)
+static int of_empty_ranges_quirk(struct device_node *np)
{
if (IS_ENABLED(CONFIG_PPC)) {
- /* To save cycles, we cache the result */
+ /* To save cycles, we cache the result for global "Mac" setting */
static int quirk_state = -1;
+ /* PA-SEMI sdc DT bug */
+ if (of_device_is_compatible(np, "1682m-sdc"))
+ return true;
+
+ /* Make quirk cached */
if (quirk_state < 0)
quirk_state =
of_machine_is_compatible("Power Macintosh") ||
@@ -490,7 +495,7 @@ static int of_translate_one(struct device_node *parent, struct of_bus *bus,
* This code is only enabled on powerpc. --gcl
*/
ranges = of_get_property(parent, rprop, &rlen);
- if (ranges == NULL && !of_empty_ranges_quirk()) {
+ if (ranges == NULL && !of_empty_ranges_quirk(parent)) {
pr_err("OF: no ranges; cannot translate\n");
return 1;
}
@@ -707,7 +712,7 @@ int __weak pci_register_io_range(phys_addr_t addr, resource_size_t size)
}
/* add the range to the list */
- range = kzalloc(sizeof(*range), GFP_KERNEL);
+ range = kzalloc(sizeof(*range), GFP_ATOMIC);
if (!range) {
err = -ENOMEM;
goto end_register;
@@ -760,7 +765,7 @@ unsigned long __weak pci_address_to_pio(phys_addr_t address)
spin_lock(&io_range_lock);
list_for_each_entry(res, &io_range_list, list) {
if (address >= res->start && address < res->start + res->size) {
- addr = res->start - address + offset;
+ addr = address - res->start + offset;
break;
}
offset += res->size;
@@ -840,10 +845,10 @@ struct device_node *of_find_matching_node_by_address(struct device_node *from,
struct resource res;
while (dn) {
- if (of_address_to_resource(dn, 0, &res))
- continue;
- if (res.start == base_address)
+ if (!of_address_to_resource(dn, 0, &res) &&
+ res.start == base_address)
return dn;
+
dn = of_find_matching_node(dn, matches);
}
@@ -884,7 +889,7 @@ EXPORT_SYMBOL(of_iomap);
* return PTR_ERR(base);
*/
void __iomem *of_io_request_and_map(struct device_node *np, int index,
- char *name)
+ const char *name)
{
struct resource res;
void __iomem *mem;
diff --git a/drivers/of/base.c b/drivers/of/base.c
index 3823edf2d012..08e243924f9f 100644
--- a/drivers/of/base.c
+++ b/drivers/of/base.c
@@ -32,11 +32,12 @@
LIST_HEAD(aliases_lookup);
-struct device_node *of_allnodes;
-EXPORT_SYMBOL(of_allnodes);
+struct device_node *of_root;
+EXPORT_SYMBOL(of_root);
struct device_node *of_chosen;
struct device_node *of_aliases;
struct device_node *of_stdout;
+static const char *of_stdout_options;
struct kset *of_kset;
@@ -48,7 +49,7 @@ struct kset *of_kset;
*/
DEFINE_MUTEX(of_mutex);
-/* use when traversing tree through the allnext, child, sibling,
+/* use when traversing tree through the child, sibling,
* or parent members of struct device_node.
*/
DEFINE_RAW_SPINLOCK(devtree_lock);
@@ -88,7 +89,7 @@ EXPORT_SYMBOL(of_n_size_cells);
#ifdef CONFIG_NUMA
int __weak of_node_to_nid(struct device_node *np)
{
- return numa_node_id();
+ return NUMA_NO_NODE;
}
#endif
@@ -111,6 +112,7 @@ static ssize_t of_node_property_read(struct file *filp, struct kobject *kobj,
return memory_read_from_buffer(buf, count, &offset, pp->value, pp->length);
}
+/* always return newly allocated name, caller must free after use */
static const char *safe_name(struct kobject *kobj, const char *orig_name)
{
const char *name = orig_name;
@@ -125,9 +127,12 @@ static const char *safe_name(struct kobject *kobj, const char *orig_name)
name = kasprintf(GFP_KERNEL, "%s#%i", orig_name, ++i);
}
- if (name != orig_name)
+ if (name == orig_name) {
+ name = kstrdup(orig_name, GFP_KERNEL);
+ } else {
pr_warn("device-tree: Duplicate name in %s, renamed to \"%s\"\n",
kobject_name(kobj), name);
+ }
return name;
}
@@ -158,6 +163,7 @@ int __of_add_property_sysfs(struct device_node *np, struct property *pp)
int __of_attach_node_sysfs(struct device_node *np)
{
const char *name;
+ struct kobject *parent;
struct property *pp;
int rc;
@@ -170,15 +176,16 @@ int __of_attach_node_sysfs(struct device_node *np)
np->kobj.kset = of_kset;
if (!np->parent) {
/* Nodes without parents are new top level trees */
- rc = kobject_add(&np->kobj, NULL, "%s",
- safe_name(&of_kset->kobj, "base"));
+ name = safe_name(&of_kset->kobj, "base");
+ parent = NULL;
} else {
name = safe_name(&np->parent->kobj, kbasename(np->full_name));
- if (!name || !name[0])
- return -EINVAL;
-
- rc = kobject_add(&np->kobj, &np->parent->kobj, "%s", name);
+ parent = &np->parent->kobj;
}
+ if (!name)
+ return -ENOMEM;
+ rc = kobject_add(&np->kobj, parent, "%s", name);
+ kfree(name);
if (rc)
return rc;
@@ -204,7 +211,7 @@ static int __init of_init(void)
mutex_unlock(&of_mutex);
/* Symlink in /proc as required by userspace ABI */
- if (of_allnodes)
+ if (of_root)
proc_symlink("device-tree", NULL, "/sys/firmware/devicetree/base");
return 0;
@@ -245,6 +252,23 @@ struct property *of_find_property(const struct device_node *np,
}
EXPORT_SYMBOL(of_find_property);
+struct device_node *__of_find_all_nodes(struct device_node *prev)
+{
+ struct device_node *np;
+ if (!prev) {
+ np = of_root;
+ } else if (prev->child) {
+ np = prev->child;
+ } else {
+ /* Walk back up looking for a sibling, or the end of the structure */
+ np = prev;
+ while (np->parent && !np->sibling)
+ np = np->parent;
+ np = np->sibling; /* Might be null at the end of the tree */
+ }
+ return np;
+}
+
/**
* of_find_all_nodes - Get next node in global list
* @prev: Previous node or NULL to start iteration
@@ -259,10 +283,8 @@ struct device_node *of_find_all_nodes(struct device_node *prev)
unsigned long flags;
raw_spin_lock_irqsave(&devtree_lock, flags);
- np = prev ? prev->allnext : of_allnodes;
- for (; np != NULL; np = np->allnext)
- if (of_node_get(np))
- break;
+ np = __of_find_all_nodes(prev);
+ of_node_get(np);
of_node_put(prev);
raw_spin_unlock_irqrestore(&devtree_lock, flags);
return np;
@@ -507,27 +529,27 @@ EXPORT_SYMBOL(of_machine_is_compatible);
*
* @device: Node to check for availability, with locks already held
*
- * Returns 1 if the status property is absent or set to "okay" or "ok",
- * 0 otherwise
+ * Returns true if the status property is absent or set to "okay" or "ok",
+ * false otherwise
*/
-static int __of_device_is_available(const struct device_node *device)
+static bool __of_device_is_available(const struct device_node *device)
{
const char *status;
int statlen;
if (!device)
- return 0;
+ return false;
status = __of_get_property(device, "status", &statlen);
if (status == NULL)
- return 1;
+ return true;
if (statlen > 0) {
if (!strcmp(status, "okay") || !strcmp(status, "ok"))
- return 1;
+ return true;
}
- return 0;
+ return false;
}
/**
@@ -535,13 +557,13 @@ static int __of_device_is_available(const struct device_node *device)
*
* @device: Node to check for availability
*
- * Returns 1 if the status property is absent or set to "okay" or "ok",
- * 0 otherwise
+ * Returns true if the status property is absent or set to "okay" or "ok",
+ * false otherwise
*/
-int of_device_is_available(const struct device_node *device)
+bool of_device_is_available(const struct device_node *device)
{
unsigned long flags;
- int res;
+ bool res;
raw_spin_lock_irqsave(&devtree_lock, flags);
res = __of_device_is_available(device);
@@ -698,8 +720,9 @@ static struct device_node *__of_find_node_by_path(struct device_node *parent,
const char *path)
{
struct device_node *child;
- int len = strchrnul(path, '/') - path;
+ int len;
+ len = strcspn(path, "/:");
if (!len)
return NULL;
@@ -715,11 +738,14 @@ static struct device_node *__of_find_node_by_path(struct device_node *parent,
}
/**
- * of_find_node_by_path - Find a node matching a full OF path
+ * of_find_node_opts_by_path - Find a node matching a full OF path
* @path: Either the full path to match, or if the path does not
* start with '/', the name of a property of the /aliases
* node (an alias). In the case of an alias, the node
* matching the alias' value will be returned.
+ * @opts: Address of a pointer into which to store the start of
+ * an options string appended to the end of the path with
+ * a ':' separator.
*
* Valid paths:
* /foo/bar Full path
@@ -729,19 +755,27 @@ static struct device_node *__of_find_node_by_path(struct device_node *parent,
* Returns a node pointer with refcount incremented, use
* of_node_put() on it when done.
*/
-struct device_node *of_find_node_by_path(const char *path)
+struct device_node *of_find_node_opts_by_path(const char *path, const char **opts)
{
struct device_node *np = NULL;
struct property *pp;
unsigned long flags;
+ const char *separator = strchr(path, ':');
+
+ if (opts)
+ *opts = separator ? separator + 1 : NULL;
if (strcmp(path, "/") == 0)
- return of_node_get(of_allnodes);
+ return of_node_get(of_root);
/* The path could begin with an alias */
if (*path != '/') {
- char *p = strchrnul(path, '/');
- int len = p - path;
+ int len;
+ const char *p = separator;
+
+ if (!p)
+ p = strchrnul(path, '/');
+ len = p - path;
/* of_aliases must not be NULL */
if (!of_aliases)
@@ -761,16 +795,18 @@ struct device_node *of_find_node_by_path(const char *path)
/* Step down the tree matching path components */
raw_spin_lock_irqsave(&devtree_lock, flags);
if (!np)
- np = of_node_get(of_allnodes);
+ np = of_node_get(of_root);
while (np && *path == '/') {
path++; /* Increment past '/' delimiter */
np = __of_find_node_by_path(np, path);
path = strchrnul(path, '/');
+ if (separator && separator < path)
+ break;
}
raw_spin_unlock_irqrestore(&devtree_lock, flags);
return np;
}
-EXPORT_SYMBOL(of_find_node_by_path);
+EXPORT_SYMBOL(of_find_node_opts_by_path);
/**
* of_find_node_by_name - Find a node by its "name" property
@@ -790,8 +826,7 @@ struct device_node *of_find_node_by_name(struct device_node *from,
unsigned long flags;
raw_spin_lock_irqsave(&devtree_lock, flags);
- np = from ? from->allnext : of_allnodes;
- for (; np; np = np->allnext)
+ for_each_of_allnodes_from(from, np)
if (np->name && (of_node_cmp(np->name, name) == 0)
&& of_node_get(np))
break;
@@ -820,8 +855,7 @@ struct device_node *of_find_node_by_type(struct device_node *from,
unsigned long flags;
raw_spin_lock_irqsave(&devtree_lock, flags);
- np = from ? from->allnext : of_allnodes;
- for (; np; np = np->allnext)
+ for_each_of_allnodes_from(from, np)
if (np->type && (of_node_cmp(np->type, type) == 0)
&& of_node_get(np))
break;
@@ -852,12 +886,10 @@ struct device_node *of_find_compatible_node(struct device_node *from,
unsigned long flags;
raw_spin_lock_irqsave(&devtree_lock, flags);
- np = from ? from->allnext : of_allnodes;
- for (; np; np = np->allnext) {
+ for_each_of_allnodes_from(from, np)
if (__of_device_is_compatible(np, compatible, type, NULL) &&
of_node_get(np))
break;
- }
of_node_put(from);
raw_spin_unlock_irqrestore(&devtree_lock, flags);
return np;
@@ -884,8 +916,7 @@ struct device_node *of_find_node_with_property(struct device_node *from,
unsigned long flags;
raw_spin_lock_irqsave(&devtree_lock, flags);
- np = from ? from->allnext : of_allnodes;
- for (; np; np = np->allnext) {
+ for_each_of_allnodes_from(from, np) {
for (pp = np->properties; pp; pp = pp->next) {
if (of_prop_cmp(pp->name, prop_name) == 0) {
of_node_get(np);
@@ -967,8 +998,7 @@ struct device_node *of_find_matching_node_and_match(struct device_node *from,
*match = NULL;
raw_spin_lock_irqsave(&devtree_lock, flags);
- np = from ? from->allnext : of_allnodes;
- for (; np; np = np->allnext) {
+ for_each_of_allnodes_from(from, np) {
m = __of_match_node(matches, np);
if (m && of_node_get(np)) {
if (match)
@@ -1025,7 +1055,7 @@ struct device_node *of_find_node_by_phandle(phandle handle)
return NULL;
raw_spin_lock_irqsave(&devtree_lock, flags);
- for (np = of_allnodes; np; np = np->allnext)
+ for_each_of_allnodes(np)
if (np->phandle == handle)
break;
of_node_get(np);
@@ -1250,6 +1280,40 @@ int of_property_read_u64(const struct device_node *np, const char *propname,
EXPORT_SYMBOL_GPL(of_property_read_u64);
/**
+ * of_property_read_u64_array - Find and read an array of 64 bit integers
+ * from a property.
+ *
+ * @np: device node from which the property value is to be read.
+ * @propname: name of the property to be searched.
+ * @out_values: pointer to return value, modified only if return value is 0.
+ * @sz: number of array elements to read
+ *
+ * Search for a property in a device node and read 64-bit value(s) from
+ * it. Returns 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_values is modified only if a valid u64 value can be decoded.
+ */
+int of_property_read_u64_array(const struct device_node *np,
+ const char *propname, u64 *out_values,
+ size_t sz)
+{
+ const __be32 *val = of_find_property_value_of_size(np, propname,
+ (sz * sizeof(*out_values)));
+
+ if (IS_ERR(val))
+ return PTR_ERR(val);
+
+ while (sz--) {
+ *out_values++ = of_read_number(val, 2);
+ val += 2;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(of_property_read_u64_array);
+
+/**
* of_property_read_string - Find and read a string from a property
* @np: device node from which the property value is to be read.
* @propname: name of the property to be searched.
@@ -1317,7 +1381,7 @@ int of_property_match_string(struct device_node *np, const char *propname,
EXPORT_SYMBOL_GPL(of_property_match_string);
/**
- * of_property_read_string_util() - Utility helper for parsing string properties
+ * of_property_read_string_helper() - Utility helper for parsing string properties
* @np: device node from which the property value is to be read.
* @propname: name of the property to be searched.
* @out_strs: output array of string pointers.
@@ -1676,6 +1740,12 @@ int __of_remove_property(struct device_node *np, struct property *prop)
return 0;
}
+void __of_sysfs_remove_bin_file(struct device_node *np, struct property *prop)
+{
+ sysfs_remove_bin_file(&np->kobj, &prop->attr);
+ kfree(prop->attr.attr.name);
+}
+
void __of_remove_property_sysfs(struct device_node *np, struct property *prop)
{
if (!IS_ENABLED(CONFIG_SYSFS))
@@ -1683,7 +1753,7 @@ 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_sysfs_remove_bin_file(np, prop);
}
/**
@@ -1753,7 +1823,7 @@ void __of_update_property_sysfs(struct device_node *np, struct property *newprop
return;
if (oldprop)
- sysfs_remove_bin_file(&np->kobj, &oldprop->attr);
+ __of_sysfs_remove_bin_file(np, oldprop);
__of_add_property_sysfs(np, newprop);
}
@@ -1831,7 +1901,7 @@ void of_alias_scan(void * (*dt_alloc)(u64 size, u64 align))
if (IS_ENABLED(CONFIG_PPC) && !name)
name = of_get_property(of_aliases, "stdout", NULL);
if (name)
- of_stdout = of_find_node_by_path(name);
+ of_stdout = of_find_node_opts_by_path(name, &of_stdout_options);
}
if (!of_aliases)
@@ -1957,7 +2027,8 @@ bool of_console_check(struct device_node *dn, char *name, int index)
{
if (!dn || dn != of_stdout || console_set_on_cmdline)
return false;
- return !add_preferred_console(name, index, NULL);
+ return !add_preferred_console(name, index,
+ kstrdup(of_stdout_options, GFP_KERNEL));
}
EXPORT_SYMBOL_GPL(of_console_check);
diff --git a/drivers/of/device.c b/drivers/of/device.c
index 46d6c75c1404..20c1332a0018 100644
--- a/drivers/of/device.c
+++ b/drivers/of/device.c
@@ -2,6 +2,9 @@
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/of_device.h>
+#include <linux/of_address.h>
+#include <linux/of_iommu.h>
+#include <linux/dma-mapping.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
@@ -66,6 +69,87 @@ int of_device_add(struct platform_device *ofdev)
return device_add(&ofdev->dev);
}
+/**
+ * of_dma_configure - Setup DMA configuration
+ * @dev: Device to apply DMA configuration
+ * @np: Pointer to OF node having DMA configuration
+ *
+ * Try to get devices's DMA configuration from DT and update it
+ * accordingly.
+ *
+ * If platform code needs to use its own special DMA configuration, it
+ * can use a platform bus notifier and handle BUS_NOTIFY_ADD_DEVICE events
+ * to fix up DMA configuration.
+ */
+void of_dma_configure(struct device *dev, struct device_node *np)
+{
+ u64 dma_addr, paddr, size;
+ int ret;
+ bool coherent;
+ unsigned long offset;
+ struct iommu_ops *iommu;
+
+ /*
+ * Set default coherent_dma_mask to 32 bit. Drivers are expected to
+ * setup the correct supported mask.
+ */
+ if (!dev->coherent_dma_mask)
+ dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+ /*
+ * Set it to coherent_dma_mask by default if the architecture
+ * code has not set it.
+ */
+ if (!dev->dma_mask)
+ dev->dma_mask = &dev->coherent_dma_mask;
+
+ ret = of_dma_get_range(np, &dma_addr, &paddr, &size);
+ if (ret < 0) {
+ dma_addr = offset = 0;
+ size = dev->coherent_dma_mask + 1;
+ } else {
+ offset = PFN_DOWN(paddr - dma_addr);
+
+ /*
+ * Add a work around to treat the size as mask + 1 in case
+ * it is defined in DT as a mask.
+ */
+ if (size & 1) {
+ dev_warn(dev, "Invalid size 0x%llx for dma-range\n",
+ size);
+ size = size + 1;
+ }
+
+ if (!size) {
+ dev_err(dev, "Adjusted size 0x%llx invalid\n", size);
+ return;
+ }
+ dev_dbg(dev, "dma_pfn_offset(%#08lx)\n", offset);
+ }
+
+ dev->dma_pfn_offset = offset;
+
+ /*
+ * Limit coherent and dma mask based on size and default mask
+ * set by the driver.
+ */
+ dev->coherent_dma_mask = min(dev->coherent_dma_mask,
+ DMA_BIT_MASK(ilog2(dma_addr + size)));
+ *dev->dma_mask = min((*dev->dma_mask),
+ DMA_BIT_MASK(ilog2(dma_addr + size)));
+
+ coherent = of_dma_is_coherent(np);
+ dev_dbg(dev, "device is%sdma coherent\n",
+ coherent ? " " : " not ");
+
+ iommu = of_iommu_configure(dev, np);
+ dev_dbg(dev, "device is%sbehind an iommu\n",
+ iommu ? " " : " not ");
+
+ arch_setup_dma_ops(dev, dma_addr, size, iommu, coherent);
+}
+EXPORT_SYMBOL_GPL(of_dma_configure);
+
int of_device_register(struct platform_device *pdev)
{
device_initialize(&pdev->dev);
diff --git a/drivers/of/dynamic.c b/drivers/of/dynamic.c
index d4994177dec2..57e25f67131b 100644
--- a/drivers/of/dynamic.c
+++ b/drivers/of/dynamic.c
@@ -55,7 +55,7 @@ void __of_detach_node_sysfs(struct device_node *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);
+ __of_sysfs_remove_bin_file(np, pp);
kobject_del(&np->kobj);
}
@@ -77,18 +77,132 @@ int of_reconfig_notifier_unregister(struct notifier_block *nb)
}
EXPORT_SYMBOL_GPL(of_reconfig_notifier_unregister);
-int of_reconfig_notify(unsigned long action, void *p)
+#ifdef DEBUG
+const char *action_names[] = {
+ [OF_RECONFIG_ATTACH_NODE] = "ATTACH_NODE",
+ [OF_RECONFIG_DETACH_NODE] = "DETACH_NODE",
+ [OF_RECONFIG_ADD_PROPERTY] = "ADD_PROPERTY",
+ [OF_RECONFIG_REMOVE_PROPERTY] = "REMOVE_PROPERTY",
+ [OF_RECONFIG_UPDATE_PROPERTY] = "UPDATE_PROPERTY",
+};
+#endif
+
+int of_reconfig_notify(unsigned long action, struct of_reconfig_data *p)
{
int rc;
+#ifdef DEBUG
+ struct of_reconfig_data *pr = p;
+
+ switch (action) {
+ case OF_RECONFIG_ATTACH_NODE:
+ case OF_RECONFIG_DETACH_NODE:
+ pr_debug("of/notify %-15s %s\n", action_names[action],
+ pr->dn->full_name);
+ break;
+ case OF_RECONFIG_ADD_PROPERTY:
+ case OF_RECONFIG_REMOVE_PROPERTY:
+ case OF_RECONFIG_UPDATE_PROPERTY:
+ pr_debug("of/notify %-15s %s:%s\n", action_names[action],
+ pr->dn->full_name, pr->prop->name);
+ break;
+ }
+#endif
rc = blocking_notifier_call_chain(&of_reconfig_chain, action, p);
return notifier_to_errno(rc);
}
+/*
+ * of_reconfig_get_state_change() - Returns new state of device
+ * @action - action of the of notifier
+ * @arg - argument of the of notifier
+ *
+ * Returns the new state of a device based on the notifier used.
+ * Returns 0 on device going from enabled to disabled, 1 on device
+ * going from disabled to enabled and -1 on no change.
+ */
+int of_reconfig_get_state_change(unsigned long action, struct of_reconfig_data *pr)
+{
+ struct property *prop, *old_prop = NULL;
+ int is_status, status_state, old_status_state, prev_state, new_state;
+
+ /* figure out if a device should be created or destroyed */
+ switch (action) {
+ case OF_RECONFIG_ATTACH_NODE:
+ case OF_RECONFIG_DETACH_NODE:
+ prop = of_find_property(pr->dn, "status", NULL);
+ break;
+ case OF_RECONFIG_ADD_PROPERTY:
+ case OF_RECONFIG_REMOVE_PROPERTY:
+ prop = pr->prop;
+ break;
+ case OF_RECONFIG_UPDATE_PROPERTY:
+ prop = pr->prop;
+ old_prop = pr->old_prop;
+ break;
+ default:
+ return OF_RECONFIG_NO_CHANGE;
+ }
+
+ is_status = 0;
+ status_state = -1;
+ old_status_state = -1;
+ prev_state = -1;
+ new_state = -1;
+
+ if (prop && !strcmp(prop->name, "status")) {
+ is_status = 1;
+ status_state = !strcmp(prop->value, "okay") ||
+ !strcmp(prop->value, "ok");
+ if (old_prop)
+ old_status_state = !strcmp(old_prop->value, "okay") ||
+ !strcmp(old_prop->value, "ok");
+ }
+
+ switch (action) {
+ case OF_RECONFIG_ATTACH_NODE:
+ prev_state = 0;
+ /* -1 & 0 status either missing or okay */
+ new_state = status_state != 0;
+ break;
+ case OF_RECONFIG_DETACH_NODE:
+ /* -1 & 0 status either missing or okay */
+ prev_state = status_state != 0;
+ new_state = 0;
+ break;
+ case OF_RECONFIG_ADD_PROPERTY:
+ if (is_status) {
+ /* no status property -> enabled (legacy) */
+ prev_state = 1;
+ new_state = status_state;
+ }
+ break;
+ case OF_RECONFIG_REMOVE_PROPERTY:
+ if (is_status) {
+ prev_state = status_state;
+ /* no status property -> enabled (legacy) */
+ new_state = 1;
+ }
+ break;
+ case OF_RECONFIG_UPDATE_PROPERTY:
+ if (is_status) {
+ prev_state = old_status_state != 0;
+ new_state = status_state != 0;
+ }
+ break;
+ }
+
+ if (prev_state == new_state)
+ return OF_RECONFIG_NO_CHANGE;
+
+ return new_state ? OF_RECONFIG_CHANGE_ADD : OF_RECONFIG_CHANGE_REMOVE;
+}
+EXPORT_SYMBOL_GPL(of_reconfig_get_state_change);
+
int of_property_notify(int action, struct device_node *np,
struct property *prop, struct property *oldprop)
{
- struct of_prop_reconfig pr;
+ struct of_reconfig_data pr;
/* only call notifiers if the node is attached */
if (!of_node_is_attached(np))
@@ -117,8 +231,6 @@ void __of_attach_node(struct device_node *np)
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);
}
@@ -128,8 +240,12 @@ void __of_attach_node(struct device_node *np)
*/
int of_attach_node(struct device_node *np)
{
+ struct of_reconfig_data rd;
unsigned long flags;
+ memset(&rd, 0, sizeof(rd));
+ rd.dn = np;
+
mutex_lock(&of_mutex);
raw_spin_lock_irqsave(&devtree_lock, flags);
__of_attach_node(np);
@@ -138,7 +254,7 @@ int of_attach_node(struct device_node *np)
__of_attach_node_sysfs(np);
mutex_unlock(&of_mutex);
- of_reconfig_notify(OF_RECONFIG_ATTACH_NODE, np);
+ of_reconfig_notify(OF_RECONFIG_ATTACH_NODE, &rd);
return 0;
}
@@ -154,17 +270,6 @@ void __of_detach_node(struct device_node *np)
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 {
@@ -187,9 +292,13 @@ void __of_detach_node(struct device_node *np)
*/
int of_detach_node(struct device_node *np)
{
+ struct of_reconfig_data rd;
unsigned long flags;
int rc = 0;
+ memset(&rd, 0, sizeof(rd));
+ rd.dn = np;
+
mutex_lock(&of_mutex);
raw_spin_lock_irqsave(&devtree_lock, flags);
__of_detach_node(np);
@@ -198,7 +307,7 @@ int of_detach_node(struct device_node *np)
__of_detach_node_sysfs(np);
mutex_unlock(&of_mutex);
- of_reconfig_notify(OF_RECONFIG_DETACH_NODE, np);
+ of_reconfig_notify(OF_RECONFIG_DETACH_NODE, &rd);
return rc;
}
@@ -285,36 +394,54 @@ struct property *__of_prop_dup(const struct property *prop, gfp_t allocflags)
}
/**
- * __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)
+ * __of_node_dup() - Duplicate or create an empty device node dynamically.
+ * @fmt: Format string (plus vargs) for new full name of the device node
*
- * 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.
+ * Create an device tree node, either by duplicating an empty node or by allocating
+ * an empty one 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 *__of_node_dup(const struct device_node *np, const char *fmt, ...)
{
+ va_list vargs;
struct device_node *node;
- node = kzalloc(sizeof(*node), allocflags);
+ node = kzalloc(sizeof(*node), GFP_KERNEL);
if (!node)
return NULL;
+ va_start(vargs, fmt);
+ node->full_name = kvasprintf(GFP_KERNEL, fmt, vargs);
+ va_end(vargs);
+ if (!node->full_name) {
+ kfree(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);
+ /* Iterate over and duplicate all properties */
+ if (np) {
+ struct property *pp, *new_pp;
+ for_each_property_of_node(np, pp) {
+ new_pp = __of_prop_dup(pp, GFP_KERNEL);
+ if (!new_pp)
+ goto err_prop;
+ if (__of_add_property(node, new_pp)) {
+ kfree(new_pp->name);
+ kfree(new_pp->value);
+ kfree(new_pp);
+ goto err_prop;
+ }
+ }
+ }
return node;
- err_free:
- kfree(node->full_name);
- kfree(node);
+ err_prop:
+ of_node_put(node); /* Frees the node and properties */
return NULL;
}
@@ -330,27 +457,15 @@ 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);
+ pr_debug("of/cset<%p> %-15s %s/%s\n", ce, action_names[ce->action],
+ 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);
+ pr_debug("of/cset<%p> %-15s %s\n", ce, action_names[ce->action],
+ ce->np->full_name);
break;
}
}
@@ -388,6 +503,7 @@ static void __of_changeset_entry_invert(struct of_changeset_entry *ce,
static void __of_changeset_entry_notify(struct of_changeset_entry *ce, bool revert)
{
+ struct of_reconfig_data rd;
struct of_changeset_entry ce_inverted;
int ret;
@@ -399,7 +515,9 @@ static void __of_changeset_entry_notify(struct of_changeset_entry *ce, bool reve
switch (ce->action) {
case OF_RECONFIG_ATTACH_NODE:
case OF_RECONFIG_DETACH_NODE:
- ret = of_reconfig_notify(ce->action, ce->np);
+ memset(&rd, 0, sizeof(rd));
+ rd.dn = ce->np;
+ ret = of_reconfig_notify(ce->action, &rd);
break;
case OF_RECONFIG_ADD_PROPERTY:
case OF_RECONFIG_REMOVE_PROPERTY:
diff --git a/drivers/of/fdt.c b/drivers/of/fdt.c
index d134710de96d..3a896c9aeb74 100644
--- a/drivers/of/fdt.c
+++ b/drivers/of/fdt.c
@@ -9,6 +9,7 @@
* version 2 as published by the Free Software Foundation.
*/
+#include <linux/crc32.h>
#include <linux/kernel.h>
#include <linux/initrd.h>
#include <linux/memblock.h>
@@ -22,6 +23,7 @@
#include <linux/libfdt.h>
#include <linux/debugfs.h>
#include <linux/serial_core.h>
+#include <linux/sysfs.h>
#include <asm/setup.h> /* for COMMAND_LINE_SIZE */
#include <asm/page.h>
@@ -145,15 +147,15 @@ static void *unflatten_dt_alloc(void **mem, unsigned long size,
* @mem: Memory chunk to use for allocating device nodes and properties
* @p: pointer to node in flat tree
* @dad: Parent struct device_node
- * @allnextpp: pointer to ->allnext from last allocated device_node
* @fpsize: Size of the node path up at the current depth.
*/
static void * unflatten_dt_node(void *blob,
void *mem,
int *poffset,
struct device_node *dad,
- struct device_node ***allnextpp,
- unsigned long fpsize)
+ struct device_node **nodepp,
+ unsigned long fpsize,
+ bool dryrun)
{
const __be32 *p;
struct device_node *np;
@@ -200,7 +202,7 @@ static void * unflatten_dt_node(void *blob,
np = unflatten_dt_alloc(&mem, sizeof(struct device_node) + allocl,
__alignof__(struct device_node));
- if (allnextpp) {
+ if (!dryrun) {
char *fn;
of_node_init(np);
np->full_name = fn = ((char *)np) + sizeof(*np);
@@ -222,16 +224,10 @@ static void * unflatten_dt_node(void *blob,
memcpy(fn, pathp, l);
prev_pp = &np->properties;
- **allnextpp = np;
- *allnextpp = &np->allnext;
if (dad != NULL) {
np->parent = dad;
- /* we temporarily use the next field as `last_child'*/
- if (dad->next == NULL)
- dad->child = np;
- else
- dad->next->sibling = np;
- dad->next = np;
+ np->sibling = dad->child;
+ dad->child = np;
}
}
/* process properties */
@@ -254,7 +250,7 @@ static void * unflatten_dt_node(void *blob,
has_name = 1;
pp = unflatten_dt_alloc(&mem, sizeof(struct property),
__alignof__(struct property));
- if (allnextpp) {
+ if (!dryrun) {
/* We accept flattened tree phandles either in
* ePAPR-style "phandle" properties, or the
* legacy "linux,phandle" properties. If both
@@ -296,7 +292,7 @@ static void * unflatten_dt_node(void *blob,
sz = (pa - ps) + 1;
pp = unflatten_dt_alloc(&mem, sizeof(struct property) + sz,
__alignof__(struct property));
- if (allnextpp) {
+ if (!dryrun) {
pp->name = "name";
pp->length = sz;
pp->value = pp + 1;
@@ -308,7 +304,7 @@ static void * unflatten_dt_node(void *blob,
(char *)pp->value);
}
}
- if (allnextpp) {
+ if (!dryrun) {
*prev_pp = NULL;
np->name = of_get_property(np, "name", NULL);
np->type = of_get_property(np, "device_type", NULL);
@@ -324,12 +320,30 @@ static void * unflatten_dt_node(void *blob,
if (depth < 0)
depth = 0;
while (*poffset > 0 && depth > old_depth)
- mem = unflatten_dt_node(blob, mem, poffset, np, allnextpp,
- fpsize);
+ mem = unflatten_dt_node(blob, mem, poffset, np, NULL,
+ fpsize, dryrun);
if (*poffset < 0 && *poffset != -FDT_ERR_NOTFOUND)
pr_err("unflatten: error %d processing FDT\n", *poffset);
+ /*
+ * Reverse the child list. Some drivers assumes node order matches .dts
+ * node order
+ */
+ if (!dryrun && np->child) {
+ struct device_node *child = np->child;
+ np->child = NULL;
+ while (child) {
+ struct device_node *next = child->sibling;
+ child->sibling = np->child;
+ np->child = child;
+ child = next;
+ }
+ }
+
+ if (nodepp)
+ *nodepp = np;
+
return mem;
}
@@ -352,7 +366,6 @@ static void __unflatten_device_tree(void *blob,
unsigned long size;
int start;
void *mem;
- struct device_node **allnextp = mynodes;
pr_debug(" -> unflatten_device_tree()\n");
@@ -373,7 +386,7 @@ static void __unflatten_device_tree(void *blob,
/* First pass, scan for size */
start = 0;
- size = (unsigned long)unflatten_dt_node(blob, NULL, &start, NULL, NULL, 0);
+ size = (unsigned long)unflatten_dt_node(blob, NULL, &start, NULL, NULL, 0, true);
size = ALIGN(size, 4);
pr_debug(" size is %lx, allocating...\n", size);
@@ -388,11 +401,10 @@ static void __unflatten_device_tree(void *blob,
/* Second pass, do actual unflattening */
start = 0;
- unflatten_dt_node(blob, mem, &start, NULL, &allnextp, 0);
+ unflatten_dt_node(blob, mem, &start, NULL, mynodes, 0, false);
if (be32_to_cpup(mem + size) != 0xdeadbeef)
pr_warning("End of tree marker overwritten: %08x\n",
be32_to_cpup(mem + size));
- *allnextp = NULL;
pr_debug(" <- unflatten_device_tree()\n");
}
@@ -425,6 +437,8 @@ void *initial_boot_params;
#ifdef CONFIG_OF_EARLY_FLATTREE
+static u32 of_fdt_crc32;
+
/**
* res_mem_reserve_reg() - reserve all memory described in 'reg' property
*/
@@ -748,7 +762,7 @@ static inline void early_init_dt_check_for_initrd(unsigned long node)
#ifdef CONFIG_SERIAL_EARLYCON
extern struct of_device_id __earlycon_of_table[];
-int __init early_init_dt_scan_chosen_serial(void)
+static int __init early_init_dt_scan_chosen_serial(void)
{
int offset;
const char *p;
@@ -930,6 +944,11 @@ void __init __weak early_init_dt_add_memory_arch(u64 base, u64 size)
const u64 phys_offset = __pa(PAGE_OFFSET);
if (!PAGE_ALIGNED(base)) {
+ if (size < PAGE_SIZE - (base & ~PAGE_MASK)) {
+ pr_warn("Ignoring memory block 0x%llx - 0x%llx\n",
+ base, base + size);
+ return;
+ }
size -= PAGE_SIZE - (base & ~PAGE_MASK);
base = PAGE_ALIGN(base);
}
@@ -992,15 +1011,14 @@ bool __init early_init_dt_verify(void *params)
if (!params)
return false;
- /* Setup flat device-tree pointer */
- initial_boot_params = params;
-
/* check device tree validity */
- if (fdt_check_header(params)) {
- initial_boot_params = NULL;
+ if (fdt_check_header(params))
return false;
- }
+ /* Setup flat device-tree pointer */
+ initial_boot_params = params;
+ of_fdt_crc32 = crc32_be(~0, initial_boot_params,
+ fdt_totalsize(initial_boot_params));
return true;
}
@@ -1039,7 +1057,7 @@ bool __init early_init_dt_scan(void *params)
*/
void __init unflatten_device_tree(void)
{
- __unflatten_device_tree(initial_boot_params, &of_allnodes,
+ __unflatten_device_tree(initial_boot_params, &of_root,
early_init_dt_alloc_memory_arch);
/* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */
@@ -1078,27 +1096,32 @@ void __init unflatten_and_copy_device_tree(void)
unflatten_device_tree();
}
-#if defined(CONFIG_DEBUG_FS) && defined(DEBUG)
-static struct debugfs_blob_wrapper flat_dt_blob;
-
-static int __init of_flat_dt_debugfs_export_fdt(void)
+#ifdef CONFIG_SYSFS
+static ssize_t of_fdt_raw_read(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *bin_attr,
+ char *buf, loff_t off, size_t count)
{
- struct dentry *d = debugfs_create_dir("device-tree", NULL);
-
- if (!d)
- return -ENOENT;
+ memcpy(buf, initial_boot_params + off, count);
+ return count;
+}
- flat_dt_blob.data = initial_boot_params;
- flat_dt_blob.size = fdt_totalsize(initial_boot_params);
+static int __init of_fdt_raw_init(void)
+{
+ static struct bin_attribute of_fdt_raw_attr =
+ __BIN_ATTR(fdt, S_IRUSR, of_fdt_raw_read, NULL, 0);
- d = debugfs_create_blob("flat-device-tree", S_IFREG | S_IRUSR,
- d, &flat_dt_blob);
- if (!d)
- return -ENOENT;
+ if (!initial_boot_params)
+ return 0;
- return 0;
+ if (of_fdt_crc32 != crc32_be(~0, initial_boot_params,
+ fdt_totalsize(initial_boot_params))) {
+ pr_warn("fdt: not creating '/sys/firmware/fdt': CRC check failed\n");
+ return 0;
+ }
+ of_fdt_raw_attr.size = fdt_totalsize(initial_boot_params);
+ return sysfs_create_bin_file(firmware_kobj, &of_fdt_raw_attr);
}
-module_init(of_flat_dt_debugfs_export_fdt);
+late_initcall(of_fdt_raw_init);
#endif
#endif /* CONFIG_OF_EARLY_FLATTREE */
diff --git a/drivers/of/irq.c b/drivers/of/irq.c
index 1471e0a223a5..10145d37931f 100644
--- a/drivers/of/irq.c
+++ b/drivers/of/irq.c
@@ -18,6 +18,7 @@
* driver.
*/
+#include <linux/device.h>
#include <linux/errno.h>
#include <linux/list.h>
#include <linux/module.h>
@@ -290,7 +291,7 @@ int of_irq_parse_one(struct device_node *device, int index, struct of_phandle_ar
struct device_node *p;
const __be32 *intspec, *tmp, *addr;
u32 intsize, intlen;
- int i, res = -EINVAL;
+ int i, res;
pr_debug("of_irq_parse_one: dev=%s, index=%d\n", of_node_full_name(device), index);
@@ -323,15 +324,19 @@ int of_irq_parse_one(struct device_node *device, int index, struct of_phandle_ar
/* Get size of interrupt specifier */
tmp = of_get_property(p, "#interrupt-cells", NULL);
- if (tmp == NULL)
+ if (tmp == NULL) {
+ res = -EINVAL;
goto out;
+ }
intsize = be32_to_cpu(*tmp);
pr_debug(" intsize=%d intlen=%d\n", intsize, intlen);
/* Check index */
- if ((index + 1) * intsize > intlen)
+ if ((index + 1) * intsize > intlen) {
+ res = -EINVAL;
goto out;
+ }
/* Copy intspec into irq structure */
intspec += index * intsize;
@@ -381,13 +386,13 @@ int of_irq_to_resource(struct device_node *dev, int index, struct resource *r)
EXPORT_SYMBOL_GPL(of_irq_to_resource);
/**
- * of_irq_get - Decode a node's IRQ and return it as a Linux irq number
+ * of_irq_get - Decode a node's IRQ and return it as a Linux IRQ number
* @dev: pointer to device tree node
- * @index: zero-based index of the irq
- *
- * Returns Linux irq number on success, or -EPROBE_DEFER if the irq domain
- * is not yet created.
+ * @index: zero-based index of the IRQ
*
+ * Returns Linux IRQ number on success, or 0 on the IRQ mapping failure, or
+ * -EPROBE_DEFER if the IRQ domain is not yet created, or error code in case
+ * of any other failure.
*/
int of_irq_get(struct device_node *dev, int index)
{
@@ -405,14 +410,16 @@ int of_irq_get(struct device_node *dev, int index)
return irq_create_of_mapping(&oirq);
}
+EXPORT_SYMBOL_GPL(of_irq_get);
/**
- * of_irq_get_byname - Decode a node's IRQ and return it as a Linux irq number
+ * of_irq_get_byname - Decode a node's IRQ and return it as a Linux IRQ number
* @dev: pointer to device tree node
- * @name: irq name
+ * @name: IRQ name
*
- * Returns Linux irq number on success, or -EPROBE_DEFER if the irq domain
- * is not yet created, or error code in case of any other failure.
+ * Returns Linux IRQ number on success, or 0 on the IRQ mapping failure, or
+ * -EPROBE_DEFER if the IRQ domain is not yet created, or error code in case
+ * of any other failure.
*/
int of_irq_get_byname(struct device_node *dev, const char *name)
{
@@ -572,3 +579,23 @@ err:
kfree(desc);
}
}
+
+/**
+ * of_msi_configure - Set the msi_domain field of a device
+ * @dev: device structure to associate with an MSI irq domain
+ * @np: device node for that device
+ */
+void of_msi_configure(struct device *dev, struct device_node *np)
+{
+ struct device_node *msi_np;
+ struct irq_domain *d;
+
+ msi_np = of_parse_phandle(np, "msi-parent", 0);
+ if (!msi_np)
+ return;
+
+ d = irq_find_matching_host(msi_np, DOMAIN_BUS_PLATFORM_MSI);
+ if (!d)
+ d = irq_find_host(msi_np);
+ dev_set_msi_domain(dev, d);
+}
diff --git a/drivers/of/of_mdio.c b/drivers/of/of_mdio.c
index 1bd43053b8c7..5dc1ef955a0f 100644
--- a/drivers/of/of_mdio.c
+++ b/drivers/of/of_mdio.c
@@ -262,7 +262,8 @@ EXPORT_SYMBOL(of_phy_attach);
bool of_phy_is_fixed_link(struct device_node *np)
{
struct device_node *dn;
- int len;
+ int len, err;
+ const char *managed;
/* New binding */
dn = of_get_child_by_name(np, "fixed-link");
@@ -271,6 +272,10 @@ bool of_phy_is_fixed_link(struct device_node *np)
return true;
}
+ err = of_property_read_string(np, "managed", &managed);
+ if (err == 0 && strcmp(managed, "auto") != 0)
+ return true;
+
/* Old binding */
if (of_get_property(np, "fixed-link", &len) &&
len == (5 * sizeof(__be32)))
@@ -285,8 +290,18 @@ int of_phy_register_fixed_link(struct device_node *np)
struct fixed_phy_status status = {};
struct device_node *fixed_link_node;
const __be32 *fixed_link_prop;
- int len;
+ int len, err;
struct phy_device *phy;
+ const char *managed;
+
+ err = of_property_read_string(np, "managed", &managed);
+ if (err == 0) {
+ if (strcmp(managed, "in-band-status") == 0) {
+ /* status is zeroed, namely its .link member */
+ phy = fixed_phy_register(PHY_POLL, &status, np);
+ return IS_ERR(phy) ? PTR_ERR(phy) : 0;
+ }
+ }
/* New binding */
fixed_link_node = of_get_child_by_name(np, "fixed-link");
diff --git a/drivers/of/of_pci.c b/drivers/of/of_pci.c
index 8882b467be95..b249fdf5ecf8 100644
--- a/drivers/of/of_pci.c
+++ b/drivers/of/of_pci.c
@@ -2,6 +2,7 @@
#include <linux/export.h>
#include <linux/of.h>
#include <linux/of_address.h>
+#include <linux/of_device.h>
#include <linux/of_pci.h>
#include <linux/slab.h>
@@ -116,6 +117,26 @@ int of_get_pci_domain_nr(struct device_node *node)
}
EXPORT_SYMBOL_GPL(of_get_pci_domain_nr);
+/**
+ * of_pci_dma_configure - Setup DMA configuration
+ * @dev: ptr to pci_dev struct of the PCI device
+ *
+ * Function to update PCI devices's DMA configuration using the same
+ * info from the OF node of host bridge's parent (if any).
+ */
+void of_pci_dma_configure(struct pci_dev *pci_dev)
+{
+ struct device *dev = &pci_dev->dev;
+ struct device *bridge = pci_get_host_bridge_device(pci_dev);
+
+ if (!bridge->parent)
+ return;
+
+ of_dma_configure(dev, bridge->parent->of_node);
+ pci_put_host_bridge_device(bridge);
+}
+EXPORT_SYMBOL_GPL(of_pci_dma_configure);
+
#if defined(CONFIG_OF_ADDRESS)
/**
* of_pci_get_host_bridge_resources - Parse PCI host bridge resources from DT
@@ -140,6 +161,7 @@ int of_pci_get_host_bridge_resources(struct device_node *dev,
unsigned char busno, unsigned char bus_max,
struct list_head *resources, resource_size_t *io_base)
{
+ struct pci_host_bridge_window *window;
struct resource *res;
struct resource *bus_range;
struct of_pci_range range;
@@ -225,6 +247,8 @@ int of_pci_get_host_bridge_resources(struct device_node *dev,
conversion_failed:
kfree(res);
parse_failed:
+ list_for_each_entry(window, resources, list)
+ kfree(window->res);
pci_free_resource_list(resources);
return err;
}
@@ -236,7 +260,7 @@ EXPORT_SYMBOL_GPL(of_pci_get_host_bridge_resources);
static LIST_HEAD(of_pci_msi_chip_list);
static DEFINE_MUTEX(of_pci_msi_chip_mutex);
-int of_pci_msi_chip_add(struct msi_chip *chip)
+int of_pci_msi_chip_add(struct msi_controller *chip)
{
if (!of_property_read_bool(chip->of_node, "msi-controller"))
return -EINVAL;
@@ -249,7 +273,7 @@ int of_pci_msi_chip_add(struct msi_chip *chip)
}
EXPORT_SYMBOL_GPL(of_pci_msi_chip_add);
-void of_pci_msi_chip_remove(struct msi_chip *chip)
+void of_pci_msi_chip_remove(struct msi_controller *chip)
{
mutex_lock(&of_pci_msi_chip_mutex);
list_del(&chip->list);
@@ -257,9 +281,9 @@ void of_pci_msi_chip_remove(struct msi_chip *chip)
}
EXPORT_SYMBOL_GPL(of_pci_msi_chip_remove);
-struct msi_chip *of_pci_find_msi_chip_by_node(struct device_node *of_node)
+struct msi_controller *of_pci_find_msi_chip_by_node(struct device_node *of_node)
{
- struct msi_chip *c;
+ struct msi_controller *c;
mutex_lock(&of_pci_msi_chip_mutex);
list_for_each_entry(c, &of_pci_msi_chip_list, list) {
diff --git a/drivers/of/of_private.h b/drivers/of/of_private.h
index 858e0a5d9a11..46ddbee22ce3 100644
--- a/drivers/of/of_private.h
+++ b/drivers/of/of_private.h
@@ -61,7 +61,7 @@ static inline int of_property_notify(int action, struct device_node *np,
* 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);
+__printf(2, 3) struct device_node *__of_node_dup(const struct device_node *np, const char *fmt, ...);
extern const void *__of_get_property(const struct device_node *np,
const char *name, int *lenp);
@@ -81,6 +81,9 @@ extern int __of_attach_node_sysfs(struct device_node *np);
extern void __of_detach_node(struct device_node *np);
extern void __of_detach_node_sysfs(struct device_node *np);
+extern void __of_sysfs_remove_bin_file(struct device_node *np,
+ struct property *prop);
+
/* iterators for transactions, used for overlays */
/* forward iterator */
#define for_each_transaction_entry(_oft, _te) \
diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c
index dc566b38645f..26fb7b0b856c 100644
--- a/drivers/of/of_reserved_mem.c
+++ b/drivers/of/of_reserved_mem.c
@@ -31,11 +31,13 @@ int __init __weak early_init_dt_alloc_reserved_memory_arch(phys_addr_t size,
phys_addr_t align, phys_addr_t start, phys_addr_t end, bool nomap,
phys_addr_t *res_base)
{
+ phys_addr_t base;
/*
* We use __memblock_alloc_base() because memblock_alloc_base()
* panic()s on allocation failure.
*/
- phys_addr_t base = __memblock_alloc_base(size, align, end);
+ end = !end ? MEMBLOCK_ALLOC_ANYWHERE : end;
+ base = __memblock_alloc_base(size, align, end);
if (!base)
return -ENOMEM;
@@ -265,6 +267,7 @@ int of_reserved_mem_device_init(struct device *dev)
return ret;
}
+EXPORT_SYMBOL_GPL(of_reserved_mem_device_init);
/**
* of_reserved_mem_device_release() - release reserved memory device structures
@@ -289,3 +292,4 @@ void of_reserved_mem_device_release(struct device *dev)
rmem->ops->device_release(rmem, dev);
}
+EXPORT_SYMBOL_GPL(of_reserved_mem_device_release);
diff --git a/drivers/of/overlay.c b/drivers/of/overlay.c
new file mode 100644
index 000000000000..dee9270ba547
--- /dev/null
+++ b/drivers/of/overlay.c
@@ -0,0 +1,552 @@
+/*
+ * Functions for working with device tree overlays
+ *
+ * Copyright (C) 2012 Pantelis Antoniou <panto@antoniou-consulting.com>
+ * Copyright (C) 2012 Texas Instruments Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ */
+#undef DEBUG
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/string.h>
+#include <linux/ctype.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/idr.h>
+
+#include "of_private.h"
+
+/**
+ * struct of_overlay_info - Holds a single overlay info
+ * @target: target of the overlay operation
+ * @overlay: pointer to the overlay contents node
+ *
+ * Holds a single overlay state, including all the overlay logs &
+ * records.
+ */
+struct of_overlay_info {
+ struct device_node *target;
+ struct device_node *overlay;
+};
+
+/**
+ * struct of_overlay - Holds a complete overlay transaction
+ * @node: List on which we are located
+ * @count: Count of ovinfo structures
+ * @ovinfo_tab: Overlay info table (count sized)
+ * @cset: Changeset to be used
+ *
+ * Holds a complete overlay transaction
+ */
+struct of_overlay {
+ int id;
+ struct list_head node;
+ int count;
+ struct of_overlay_info *ovinfo_tab;
+ struct of_changeset cset;
+};
+
+static int of_overlay_apply_one(struct of_overlay *ov,
+ struct device_node *target, const struct device_node *overlay);
+
+static int of_overlay_apply_single_property(struct of_overlay *ov,
+ struct device_node *target, struct property *prop)
+{
+ struct property *propn, *tprop;
+
+ /* NOTE: Multiple changes of single properties not supported */
+ tprop = of_find_property(target, prop->name, NULL);
+
+ /* special properties are not meant to be updated (silent NOP) */
+ if (of_prop_cmp(prop->name, "name") == 0 ||
+ of_prop_cmp(prop->name, "phandle") == 0 ||
+ of_prop_cmp(prop->name, "linux,phandle") == 0)
+ return 0;
+
+ propn = __of_prop_dup(prop, GFP_KERNEL);
+ if (propn == NULL)
+ return -ENOMEM;
+
+ /* not found? add */
+ if (tprop == NULL)
+ return of_changeset_add_property(&ov->cset, target, propn);
+
+ /* found? update */
+ return of_changeset_update_property(&ov->cset, target, propn);
+}
+
+static int of_overlay_apply_single_device_node(struct of_overlay *ov,
+ struct device_node *target, struct device_node *child)
+{
+ const char *cname;
+ struct device_node *tchild;
+ int ret = 0;
+
+ cname = kbasename(child->full_name);
+ if (cname == NULL)
+ return -ENOMEM;
+
+ /* NOTE: Multiple mods of created nodes not supported */
+ tchild = of_get_child_by_name(target, cname);
+ if (tchild != NULL) {
+ /* apply overlay recursively */
+ ret = of_overlay_apply_one(ov, tchild, child);
+ of_node_put(tchild);
+ } else {
+ /* create empty tree as a target */
+ tchild = __of_node_dup(child, "%s/%s", target->full_name, cname);
+ if (!tchild)
+ return -ENOMEM;
+
+ /* point to parent */
+ tchild->parent = target;
+
+ ret = of_changeset_attach_node(&ov->cset, tchild);
+ if (ret)
+ return ret;
+
+ ret = of_overlay_apply_one(ov, tchild, child);
+ if (ret)
+ return ret;
+ }
+
+ return ret;
+}
+
+/*
+ * Apply a single overlay node recursively.
+ *
+ * Note that the in case of an error the target node is left
+ * in a inconsistent state. Error recovery should be performed
+ * by using the changeset.
+ */
+static int of_overlay_apply_one(struct of_overlay *ov,
+ struct device_node *target, const struct device_node *overlay)
+{
+ struct device_node *child;
+ struct property *prop;
+ int ret;
+
+ for_each_property_of_node(overlay, prop) {
+ ret = of_overlay_apply_single_property(ov, target, prop);
+ if (ret) {
+ pr_err("%s: Failed to apply prop @%s/%s\n",
+ __func__, target->full_name, prop->name);
+ return ret;
+ }
+ }
+
+ for_each_child_of_node(overlay, child) {
+ ret = of_overlay_apply_single_device_node(ov, target, child);
+ if (ret != 0) {
+ pr_err("%s: Failed to apply single node @%s/%s\n",
+ __func__, target->full_name,
+ child->name);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * of_overlay_apply() - Apply @count overlays pointed at by @ovinfo_tab
+ * @ov: Overlay to apply
+ *
+ * Applies the overlays given, while handling all error conditions
+ * appropriately. Either the operation succeeds, or if it fails the
+ * live tree is reverted to the state before the attempt.
+ * Returns 0, or an error if the overlay attempt failed.
+ */
+static int of_overlay_apply(struct of_overlay *ov)
+{
+ int i, err;
+
+ /* first we apply the overlays atomically */
+ for (i = 0; i < ov->count; i++) {
+ struct of_overlay_info *ovinfo = &ov->ovinfo_tab[i];
+
+ err = of_overlay_apply_one(ov, ovinfo->target, ovinfo->overlay);
+ if (err != 0) {
+ pr_err("%s: overlay failed '%s'\n",
+ __func__, ovinfo->target->full_name);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Find the target node using a number of different strategies
+ * in order of preference
+ *
+ * "target" property containing the phandle of the target
+ * "target-path" property containing the path of the target
+ */
+static struct device_node *find_target_node(struct device_node *info_node)
+{
+ const char *path;
+ u32 val;
+ int ret;
+
+ /* first try to go by using the target as a phandle */
+ ret = of_property_read_u32(info_node, "target", &val);
+ if (ret == 0)
+ return of_find_node_by_phandle(val);
+
+ /* now try to locate by path */
+ ret = of_property_read_string(info_node, "target-path", &path);
+ if (ret == 0)
+ return of_find_node_by_path(path);
+
+ pr_err("%s: Failed to find target for node %p (%s)\n", __func__,
+ info_node, info_node->name);
+
+ return NULL;
+}
+
+/**
+ * of_fill_overlay_info() - Fill an overlay info structure
+ * @ov Overlay to fill
+ * @info_node: Device node containing the overlay
+ * @ovinfo: Pointer to the overlay info structure to fill
+ *
+ * Fills an overlay info structure with the overlay information
+ * from a device node. This device node must have a target property
+ * which contains a phandle of the overlay target node, and an
+ * __overlay__ child node which has the overlay contents.
+ * Both ovinfo->target & ovinfo->overlay have their references taken.
+ *
+ * Returns 0 on success, or a negative error value.
+ */
+static int of_fill_overlay_info(struct of_overlay *ov,
+ struct device_node *info_node, struct of_overlay_info *ovinfo)
+{
+ ovinfo->overlay = of_get_child_by_name(info_node, "__overlay__");
+ if (ovinfo->overlay == NULL)
+ goto err_fail;
+
+ ovinfo->target = find_target_node(info_node);
+ if (ovinfo->target == NULL)
+ goto err_fail;
+
+ return 0;
+
+err_fail:
+ of_node_put(ovinfo->target);
+ of_node_put(ovinfo->overlay);
+
+ memset(ovinfo, 0, sizeof(*ovinfo));
+ return -EINVAL;
+}
+
+/**
+ * of_build_overlay_info() - Build an overlay info array
+ * @ov Overlay to build
+ * @tree: Device node containing all the overlays
+ *
+ * Helper function that given a tree containing overlay information,
+ * allocates and builds an overlay info array containing it, ready
+ * for use using of_overlay_apply.
+ *
+ * Returns 0 on success with the @cntp @ovinfop pointers valid,
+ * while on error a negative error value is returned.
+ */
+static int of_build_overlay_info(struct of_overlay *ov,
+ struct device_node *tree)
+{
+ struct device_node *node;
+ struct of_overlay_info *ovinfo;
+ int cnt, err;
+
+ /* worst case; every child is a node */
+ cnt = 0;
+ for_each_child_of_node(tree, node)
+ cnt++;
+
+ ovinfo = kcalloc(cnt, sizeof(*ovinfo), GFP_KERNEL);
+ if (ovinfo == NULL)
+ return -ENOMEM;
+
+ cnt = 0;
+ for_each_child_of_node(tree, node) {
+ memset(&ovinfo[cnt], 0, sizeof(*ovinfo));
+ err = of_fill_overlay_info(ov, node, &ovinfo[cnt]);
+ if (err == 0)
+ cnt++;
+ }
+
+ /* if nothing filled, return error */
+ if (cnt == 0) {
+ kfree(ovinfo);
+ return -ENODEV;
+ }
+
+ ov->count = cnt;
+ ov->ovinfo_tab = ovinfo;
+
+ return 0;
+}
+
+/**
+ * of_free_overlay_info() - Free an overlay info array
+ * @ov Overlay to free the overlay info from
+ * @ovinfo_tab: Array of overlay_info's to free
+ *
+ * Releases the memory of a previously allocated ovinfo array
+ * by of_build_overlay_info.
+ * Returns 0, or an error if the arguments are bogus.
+ */
+static int of_free_overlay_info(struct of_overlay *ov)
+{
+ struct of_overlay_info *ovinfo;
+ int i;
+
+ /* do it in reverse */
+ for (i = ov->count - 1; i >= 0; i--) {
+ ovinfo = &ov->ovinfo_tab[i];
+
+ of_node_put(ovinfo->target);
+ of_node_put(ovinfo->overlay);
+ }
+ kfree(ov->ovinfo_tab);
+
+ return 0;
+}
+
+static LIST_HEAD(ov_list);
+static DEFINE_IDR(ov_idr);
+
+/**
+ * of_overlay_create() - Create and apply an overlay
+ * @tree: Device node containing all the overlays
+ *
+ * Creates and applies an overlay while also keeping track
+ * of the overlay in a list. This list can be used to prevent
+ * illegal overlay removals.
+ *
+ * Returns the id of the created overlay, or an negative error number
+ */
+int of_overlay_create(struct device_node *tree)
+{
+ struct of_overlay *ov;
+ int err, id;
+
+ /* allocate the overlay structure */
+ ov = kzalloc(sizeof(*ov), GFP_KERNEL);
+ if (ov == NULL)
+ return -ENOMEM;
+ ov->id = -1;
+
+ INIT_LIST_HEAD(&ov->node);
+
+ of_changeset_init(&ov->cset);
+
+ mutex_lock(&of_mutex);
+
+ id = idr_alloc(&ov_idr, ov, 0, 0, GFP_KERNEL);
+ if (id < 0) {
+ pr_err("%s: idr_alloc() failed for tree@%s\n",
+ __func__, tree->full_name);
+ err = id;
+ goto err_destroy_trans;
+ }
+ ov->id = id;
+
+ /* build the overlay info structures */
+ err = of_build_overlay_info(ov, tree);
+ if (err) {
+ pr_err("%s: of_build_overlay_info() failed for tree@%s\n",
+ __func__, tree->full_name);
+ goto err_free_idr;
+ }
+
+ /* apply the overlay */
+ err = of_overlay_apply(ov);
+ if (err) {
+ pr_err("%s: of_overlay_apply() failed for tree@%s\n",
+ __func__, tree->full_name);
+ goto err_abort_trans;
+ }
+
+ /* apply the changeset */
+ err = of_changeset_apply(&ov->cset);
+ if (err) {
+ pr_err("%s: of_changeset_apply() failed for tree@%s\n",
+ __func__, tree->full_name);
+ goto err_revert_overlay;
+ }
+
+ /* add to the tail of the overlay list */
+ list_add_tail(&ov->node, &ov_list);
+
+ mutex_unlock(&of_mutex);
+
+ return id;
+
+err_revert_overlay:
+err_abort_trans:
+ of_free_overlay_info(ov);
+err_free_idr:
+ idr_remove(&ov_idr, ov->id);
+err_destroy_trans:
+ of_changeset_destroy(&ov->cset);
+ kfree(ov);
+ mutex_unlock(&of_mutex);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(of_overlay_create);
+
+/* check whether the given node, lies under the given tree */
+static int overlay_subtree_check(struct device_node *tree,
+ struct device_node *dn)
+{
+ struct device_node *child;
+
+ /* match? */
+ if (tree == dn)
+ return 1;
+
+ for_each_child_of_node(tree, child) {
+ if (overlay_subtree_check(child, dn))
+ return 1;
+ }
+
+ return 0;
+}
+
+/* check whether this overlay is the topmost */
+static int overlay_is_topmost(struct of_overlay *ov, struct device_node *dn)
+{
+ struct of_overlay *ovt;
+ struct of_changeset_entry *ce;
+
+ list_for_each_entry_reverse(ovt, &ov_list, node) {
+ /* if we hit ourselves, we're done */
+ if (ovt == ov)
+ break;
+
+ /* check against each subtree affected by this overlay */
+ list_for_each_entry(ce, &ovt->cset.entries, node) {
+ if (overlay_subtree_check(ce->np, dn)) {
+ pr_err("%s: #%d clashes #%d @%s\n",
+ __func__, ov->id, ovt->id,
+ dn->full_name);
+ return 0;
+ }
+ }
+ }
+
+ /* overlay is topmost */
+ return 1;
+}
+
+/*
+ * We can safely remove the overlay only if it's the top-most one.
+ * Newly applied overlays are inserted at the tail of the overlay list,
+ * so a top most overlay is the one that is closest to the tail.
+ *
+ * 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.
+ */
+static int overlay_removal_is_ok(struct of_overlay *ov)
+{
+ struct of_changeset_entry *ce;
+
+ list_for_each_entry(ce, &ov->cset.entries, node) {
+ if (!overlay_is_topmost(ov, ce->np)) {
+ pr_err("%s: overlay #%d is not topmost\n",
+ __func__, ov->id);
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+/**
+ * of_overlay_destroy() - Removes an overlay
+ * @id: Overlay id number returned by a previous call to of_overlay_create
+ *
+ * Removes an overlay if it is permissible.
+ *
+ * Returns 0 on success, or an negative error number
+ */
+int of_overlay_destroy(int id)
+{
+ struct of_overlay *ov;
+ int err;
+
+ mutex_lock(&of_mutex);
+
+ ov = idr_find(&ov_idr, id);
+ if (ov == NULL) {
+ err = -ENODEV;
+ pr_err("%s: Could not find overlay #%d\n",
+ __func__, id);
+ goto out;
+ }
+
+ /* check whether the overlay is safe to remove */
+ if (!overlay_removal_is_ok(ov)) {
+ err = -EBUSY;
+ pr_err("%s: removal check failed for overlay #%d\n",
+ __func__, id);
+ goto out;
+ }
+
+
+ list_del(&ov->node);
+ of_changeset_revert(&ov->cset);
+ of_free_overlay_info(ov);
+ idr_remove(&ov_idr, id);
+ of_changeset_destroy(&ov->cset);
+ kfree(ov);
+
+ err = 0;
+
+out:
+ mutex_unlock(&of_mutex);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(of_overlay_destroy);
+
+/**
+ * of_overlay_destroy_all() - Removes all overlays from the system
+ *
+ * Removes all overlays from the system in the correct order.
+ *
+ * Returns 0 on success, or an negative error number
+ */
+int of_overlay_destroy_all(void)
+{
+ struct of_overlay *ov, *ovn;
+
+ mutex_lock(&of_mutex);
+
+ /* the tail of list is guaranteed to be safe to remove */
+ list_for_each_entry_safe_reverse(ov, ovn, &ov_list, node) {
+ list_del(&ov->node);
+ of_changeset_revert(&ov->cset);
+ of_free_overlay_info(ov);
+ idr_remove(&ov_idr, ov->id);
+ kfree(ov);
+ }
+
+ mutex_unlock(&of_mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(of_overlay_destroy_all);
diff --git a/drivers/of/pdt.c b/drivers/of/pdt.c
index 36b4035881b0..d2acae825af9 100644
--- a/drivers/of/pdt.c
+++ b/drivers/of/pdt.c
@@ -25,8 +25,7 @@
static struct of_pdt_ops *of_pdt_prom_ops __initdata;
-void __initdata (*of_pdt_build_more)(struct device_node *dp,
- struct device_node ***nextp);
+void __initdata (*of_pdt_build_more)(struct device_node *dp);
#if defined(CONFIG_SPARC)
unsigned int of_pdt_unique_id __initdata;
@@ -192,8 +191,7 @@ static struct device_node * __init of_pdt_create_node(phandle node,
}
static struct device_node * __init of_pdt_build_tree(struct device_node *parent,
- phandle node,
- struct device_node ***nextp)
+ phandle node)
{
struct device_node *ret = NULL, *prev_sibling = NULL;
struct device_node *dp;
@@ -210,16 +208,12 @@ static struct device_node * __init of_pdt_build_tree(struct device_node *parent,
ret = dp;
prev_sibling = dp;
- *(*nextp) = dp;
- *nextp = &dp->allnext;
-
dp->full_name = of_pdt_build_full_name(dp);
- dp->child = of_pdt_build_tree(dp,
- of_pdt_prom_ops->getchild(node), nextp);
+ dp->child = of_pdt_build_tree(dp, of_pdt_prom_ops->getchild(node));
if (of_pdt_build_more)
- of_pdt_build_more(dp, nextp);
+ of_pdt_build_more(dp);
node = of_pdt_prom_ops->getsibling(node);
}
@@ -234,20 +228,17 @@ static void * __init kernel_tree_alloc(u64 size, u64 align)
void __init of_pdt_build_devicetree(phandle root_node, struct of_pdt_ops *ops)
{
- struct device_node **nextp;
-
BUG_ON(!ops);
of_pdt_prom_ops = ops;
- of_allnodes = of_pdt_create_node(root_node, NULL);
+ of_root = of_pdt_create_node(root_node, NULL);
#if defined(CONFIG_SPARC)
- of_allnodes->path_component_name = "";
+ of_root->path_component_name = "";
#endif
- of_allnodes->full_name = "/";
+ of_root->full_name = "/";
- nextp = &of_allnodes->allnext;
- of_allnodes->child = of_pdt_build_tree(of_allnodes,
- of_pdt_prom_ops->getchild(of_allnodes->phandle), &nextp);
+ of_root->child = of_pdt_build_tree(of_root,
+ of_pdt_prom_ops->getchild(of_root->phandle));
/* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */
of_alias_scan(kernel_tree_alloc);
diff --git a/drivers/of/platform.c b/drivers/of/platform.c
index 3b64d0bf5bba..8a002d6151f2 100644
--- a/drivers/of/platform.c
+++ b/drivers/of/platform.c
@@ -25,6 +25,7 @@
const struct of_device_id of_default_bus_match_table[] = {
{ .compatible = "simple-bus", },
+ { .compatible = "simple-mfd", },
#ifdef CONFIG_ARM_AMBA
{ .compatible = "arm,amba-bus", },
#endif /* CONFIG_ARM_AMBA */
@@ -138,7 +139,7 @@ struct platform_device *of_device_alloc(struct device_node *np,
}
dev->dev.of_node = of_node_get(np);
- dev->dev.parent = parent;
+ dev->dev.parent = parent ? : &platform_bus;
if (bus_id)
dev_set_name(&dev->dev, "%s", bus_id);
@@ -149,57 +150,9 @@ struct platform_device *of_device_alloc(struct device_node *np,
}
EXPORT_SYMBOL(of_device_alloc);
-/**
- * of_dma_configure - Setup DMA configuration
- * @dev: Device to apply DMA configuration
- *
- * Try to get devices's DMA configuration from DT and update it
- * accordingly.
- *
- * In case if platform code need to use own special DMA configuration,it
- * can use Platform bus notifier and handle BUS_NOTIFY_ADD_DEVICE event
- * to fix up DMA configuration.
- */
-static void of_dma_configure(struct device *dev)
+static void of_dma_deconfigure(struct device *dev)
{
- u64 dma_addr, paddr, size;
- int ret;
-
- /*
- * Set default dma-mask to 32 bit. Drivers are expected to setup
- * the correct supported dma_mask.
- */
- dev->coherent_dma_mask = DMA_BIT_MASK(32);
-
- /*
- * Set it to coherent_dma_mask by default if the architecture
- * code has not set it.
- */
- if (!dev->dma_mask)
- dev->dma_mask = &dev->coherent_dma_mask;
-
- /*
- * if dma-coherent property exist, call arch hook to setup
- * dma coherent operations.
- */
- if (of_dma_is_coherent(dev->of_node)) {
- set_arch_dma_coherent_ops(dev);
- dev_dbg(dev, "device is dma coherent\n");
- }
-
- /*
- * if dma-ranges property doesn't exist - just return else
- * setup the dma offset
- */
- ret = of_dma_get_range(dev->of_node, &dma_addr, &paddr, &size);
- if (ret < 0) {
- dev_dbg(dev, "no dma range information to setup\n");
- return;
- }
-
- /* DMA ranges found. Calculate and set dma_pfn_offset */
- dev->dma_pfn_offset = PFN_DOWN(paddr - dma_addr);
- dev_dbg(dev, "dma_pfn_offset(%#08lx)\n", dev->dma_pfn_offset);
+ arch_teardown_dma_ops(dev);
}
/**
@@ -228,16 +181,13 @@ static struct platform_device *of_platform_device_create_pdata(
if (!dev)
goto err_clear_flag;
- of_dma_configure(&dev->dev);
dev->dev.bus = &platform_bus_type;
dev->dev.platform_data = platform_data;
-
- /* We do not fill the DMA ops for platform devices by default.
- * This is currently the responsibility of the platform code
- * to do such, possibly using a device notifier
- */
+ of_dma_configure(&dev->dev, dev->dev.of_node);
+ of_msi_configure(&dev->dev, dev->dev.of_node);
if (of_device_add(dev) != 0) {
+ of_dma_deconfigure(&dev->dev);
platform_device_put(dev);
goto err_clear_flag;
}
@@ -291,13 +241,13 @@ static struct amba_device *of_amba_device_create(struct device_node *node,
/* setup generic device info */
dev->dev.of_node = of_node_get(node);
- dev->dev.parent = parent;
+ dev->dev.parent = parent ? : &platform_bus;
dev->dev.platform_data = platform_data;
if (bus_id)
dev_set_name(&dev->dev, "%s", bus_id);
else
of_device_make_bus_id(&dev->dev);
- of_dma_configure(&dev->dev);
+ of_dma_configure(&dev->dev, dev->dev.of_node);
/* Allow the HW Peripheral ID to be overridden */
prop = of_get_property(node, "arm,primecell-periphid", NULL);
@@ -500,6 +450,7 @@ int of_platform_populate(struct device_node *root,
if (rc)
break;
}
+ of_node_set_flag(root, OF_POPULATED_BUS);
of_node_put(root);
return rc;
@@ -523,6 +474,7 @@ static int of_platform_device_destroy(struct device *dev, void *data)
amba_device_unregister(to_amba_device(dev));
#endif
+ of_dma_deconfigure(dev);
of_node_clear_flag(dev->of_node, OF_POPULATED);
of_node_clear_flag(dev->of_node, OF_POPULATED_BUS);
return 0;
@@ -542,8 +494,75 @@ static int of_platform_device_destroy(struct device *dev, void *data)
*/
void of_platform_depopulate(struct device *parent)
{
- device_for_each_child(parent, NULL, of_platform_device_destroy);
+ if (parent->of_node && of_node_check_flag(parent->of_node, OF_POPULATED_BUS)) {
+ device_for_each_child(parent, NULL, of_platform_device_destroy);
+ of_node_clear_flag(parent->of_node, OF_POPULATED_BUS);
+ }
}
EXPORT_SYMBOL_GPL(of_platform_depopulate);
+#ifdef CONFIG_OF_DYNAMIC
+static int of_platform_notify(struct notifier_block *nb,
+ unsigned long action, void *arg)
+{
+ struct of_reconfig_data *rd = arg;
+ struct platform_device *pdev_parent, *pdev;
+ bool children_left;
+
+ switch (of_reconfig_get_state_change(action, rd)) {
+ case OF_RECONFIG_CHANGE_ADD:
+ /* verify that the parent is a bus */
+ if (!of_node_check_flag(rd->dn->parent, OF_POPULATED_BUS))
+ return NOTIFY_OK; /* not for us */
+
+ /* already populated? (driver using of_populate manually) */
+ if (of_node_check_flag(rd->dn, OF_POPULATED))
+ return NOTIFY_OK;
+
+ /* pdev_parent may be NULL when no bus platform device */
+ pdev_parent = of_find_device_by_node(rd->dn->parent);
+ pdev = of_platform_device_create(rd->dn, NULL,
+ pdev_parent ? &pdev_parent->dev : NULL);
+ of_dev_put(pdev_parent);
+
+ if (pdev == NULL) {
+ pr_err("%s: failed to create for '%s'\n",
+ __func__, rd->dn->full_name);
+ /* of_platform_device_create tosses the error code */
+ return notifier_from_errno(-EINVAL);
+ }
+ break;
+
+ case OF_RECONFIG_CHANGE_REMOVE:
+
+ /* already depopulated? */
+ if (!of_node_check_flag(rd->dn, OF_POPULATED))
+ return NOTIFY_OK;
+
+ /* find our device by node */
+ pdev = of_find_device_by_node(rd->dn);
+ if (pdev == NULL)
+ return NOTIFY_OK; /* no? not meant for us */
+
+ /* unregister takes one ref away */
+ of_platform_device_destroy(&pdev->dev, &children_left);
+
+ /* and put the reference of the find */
+ of_dev_put(pdev);
+ break;
+ }
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block platform_of_notifier = {
+ .notifier_call = of_platform_notify,
+};
+
+void of_platform_register_reconfig_notifier(void)
+{
+ WARN_ON(of_reconfig_notifier_register(&platform_of_notifier));
+}
+#endif /* CONFIG_OF_DYNAMIC */
+
#endif /* CONFIG_OF_ADDRESS */
diff --git a/drivers/of/resolver.c b/drivers/of/resolver.c
index aed7959f800d..640eb4cb46e3 100644
--- a/drivers/of/resolver.c
+++ b/drivers/of/resolver.c
@@ -111,7 +111,8 @@ static void __of_adjust_tree_phandles(struct device_node *node,
__of_adjust_tree_phandles(child, phandle_delta);
}
-static int __of_adjust_phandle_ref(struct device_node *node, struct property *rprop, int value, bool is_delta)
+static int __of_adjust_phandle_ref(struct device_node *node,
+ struct property *rprop, int value)
{
phandle phandle;
struct device_node *refnode;
@@ -181,7 +182,7 @@ static int __of_adjust_phandle_ref(struct device_node *node, struct property *rp
goto err_fail;
}
- phandle = is_delta ? be32_to_cpup(sprop->value + offset) + value : value;
+ phandle = value;
*(__be32 *)(sprop->value + offset) = cpu_to_be32(phandle);
}
@@ -190,36 +191,97 @@ err_fail:
return err;
}
+/* compare nodes taking into account that 'name' strips out the @ part */
+static int __of_node_name_cmp(const struct device_node *dn1,
+ const struct device_node *dn2)
+{
+ const char *n1 = strrchr(dn1->full_name, '/') ? : "/";
+ const char *n2 = strrchr(dn2->full_name, '/') ? : "/";
+
+ return of_node_cmp(n1, n2);
+}
+
/*
* Adjust the local phandle references by the given phandle delta.
- * Assumes the existances of a __local_fixups__ node at the root
- * of the tree. Does not take any devtree locks so make sure you
- * call this on a tree which is at the detached state.
+ * Assumes the existances of a __local_fixups__ node at the root.
+ * Assumes that __of_verify_tree_phandle_references has been called.
+ * Does not take any devtree locks so make sure you call this on a tree
+ * which is at the detached state.
*/
static int __of_adjust_tree_phandle_references(struct device_node *node,
- int phandle_delta)
+ struct device_node *target, int phandle_delta)
{
- struct device_node *child;
- struct property *rprop;
- int err;
-
- /* locate the symbols & fixups nodes on resolve */
- for_each_child_of_node(node, child)
- if (of_node_cmp(child->name, "__local_fixups__") == 0)
- break;
+ struct device_node *child, *childtarget;
+ struct property *rprop, *sprop;
+ int err, i, count;
+ unsigned int off;
+ phandle phandle;
- /* no local fixups */
- if (!child)
+ if (node == NULL)
return 0;
- /* find the local fixups property */
- for_each_property_of_node(child, rprop) {
+ for_each_property_of_node(node, rprop) {
+
/* skip properties added automatically */
- if (of_prop_cmp(rprop->name, "name") == 0)
+ if (of_prop_cmp(rprop->name, "name") == 0 ||
+ of_prop_cmp(rprop->name, "phandle") == 0 ||
+ of_prop_cmp(rprop->name, "linux,phandle") == 0)
continue;
- err = __of_adjust_phandle_ref(node, rprop, phandle_delta, true);
- if (err)
+ if ((rprop->length % 4) != 0 || rprop->length == 0) {
+ pr_err("%s: Illegal property (size) '%s' @%s\n",
+ __func__, rprop->name, node->full_name);
+ return -EINVAL;
+ }
+ count = rprop->length / sizeof(__be32);
+
+ /* now find the target property */
+ for_each_property_of_node(target, sprop) {
+ if (of_prop_cmp(sprop->name, rprop->name) == 0)
+ break;
+ }
+
+ if (sprop == NULL) {
+ pr_err("%s: Could not find target property '%s' @%s\n",
+ __func__, rprop->name, node->full_name);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < count; i++) {
+ off = be32_to_cpu(((__be32 *)rprop->value)[i]);
+ /* make sure the offset doesn't overstep (even wrap) */
+ if (off >= sprop->length ||
+ (off + 4) > sprop->length) {
+ pr_err("%s: Illegal property '%s' @%s\n",
+ __func__, rprop->name,
+ node->full_name);
+ return -EINVAL;
+ }
+
+ if (phandle_delta) {
+ /* adjust */
+ phandle = be32_to_cpu(*(__be32 *)(sprop->value + off));
+ phandle += phandle_delta;
+ *(__be32 *)(sprop->value + off) = cpu_to_be32(phandle);
+ }
+ }
+ }
+
+ for_each_child_of_node(node, child) {
+
+ for_each_child_of_node(target, childtarget)
+ if (__of_node_name_cmp(child, childtarget) == 0)
+ break;
+
+ if (!childtarget) {
+ pr_err("%s: Could not find target child '%s' @%s\n",
+ __func__, child->name, node->full_name);
+ return -EINVAL;
+ }
+
+ err = __of_adjust_tree_phandle_references(child, childtarget,
+ phandle_delta);
+ if (err != 0)
return err;
}
@@ -241,7 +303,7 @@ static int __of_adjust_tree_phandle_references(struct device_node *node,
*/
int of_resolve_phandles(struct device_node *resolve)
{
- struct device_node *child, *refnode;
+ struct device_node *child, *childroot, *refnode;
struct device_node *root_sym, *resolve_sym, *resolve_fix;
struct property *rprop;
const char *refpath;
@@ -255,9 +317,23 @@ int of_resolve_phandles(struct device_node *resolve)
/* first we need to adjust the phandles */
phandle_delta = of_get_tree_max_phandle() + 1;
__of_adjust_tree_phandles(resolve, phandle_delta);
- err = __of_adjust_tree_phandle_references(resolve, phandle_delta);
- if (err != 0)
- return err;
+
+ /* locate the local fixups */
+ childroot = NULL;
+ for_each_child_of_node(resolve, childroot)
+ if (of_node_cmp(childroot->name, "__local_fixups__") == 0)
+ break;
+
+ if (childroot != NULL) {
+ /* resolve root is guaranteed to be the '/' */
+ err = __of_adjust_tree_phandle_references(childroot,
+ resolve, 0);
+ if (err != 0)
+ return err;
+
+ BUG_ON(__of_adjust_tree_phandle_references(childroot,
+ resolve, phandle_delta));
+ }
root_sym = NULL;
resolve_sym = NULL;
@@ -322,7 +398,7 @@ int of_resolve_phandles(struct device_node *resolve)
pr_debug("%s: %s phandle is 0x%08x\n",
__func__, rprop->name, phandle);
- err = __of_adjust_phandle_ref(resolve, rprop, phandle, false);
+ err = __of_adjust_phandle_ref(resolve, rprop, phandle);
if (err)
break;
}
diff --git a/drivers/of/testcase-data/testcases.dts b/drivers/of/testcase-data/testcases.dts
deleted file mode 100644
index 6994e15c24bf..000000000000
--- a/drivers/of/testcase-data/testcases.dts
+++ /dev/null
@@ -1,50 +0,0 @@
-/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"
-
-/*
- * phandle fixup data - generated by dtc patches that aren't upstream.
- * This data must be regenerated whenever phandle references are modified in
- * the testdata tree.
- *
- * The format of this data may be subject to change. For the time being consider
- * this a kernel-internal data format.
- */
-/ { __local_fixups__ {
- fixup = "/testcase-data/testcase-device2:interrupt-parent:0",
- "/testcase-data/testcase-device1:interrupt-parent:0",
- "/testcase-data/interrupts/interrupts-extended0:interrupts-extended:60",
- "/testcase-data/interrupts/interrupts-extended0:interrupts-extended:52",
- "/testcase-data/interrupts/interrupts-extended0:interrupts-extended:44",
- "/testcase-data/interrupts/interrupts-extended0:interrupts-extended:36",
- "/testcase-data/interrupts/interrupts-extended0:interrupts-extended:24",
- "/testcase-data/interrupts/interrupts-extended0:interrupts-extended:8",
- "/testcase-data/interrupts/interrupts-extended0:interrupts-extended:0",
- "/testcase-data/interrupts/interrupts1:interrupt-parent:0",
- "/testcase-data/interrupts/interrupts0:interrupt-parent:0",
- "/testcase-data/interrupts/intmap1:interrupt-map:12",
- "/testcase-data/interrupts/intmap0:interrupt-map:52",
- "/testcase-data/interrupts/intmap0:interrupt-map:36",
- "/testcase-data/interrupts/intmap0:interrupt-map:16",
- "/testcase-data/interrupts/intmap0:interrupt-map:4",
- "/testcase-data/phandle-tests/consumer-a:phandle-list-bad-args:12",
- "/testcase-data/phandle-tests/consumer-a:phandle-list-bad-args:0",
- "/testcase-data/phandle-tests/consumer-a:phandle-list:56",
- "/testcase-data/phandle-tests/consumer-a:phandle-list:52",
- "/testcase-data/phandle-tests/consumer-a:phandle-list:40",
- "/testcase-data/phandle-tests/consumer-a:phandle-list:24",
- "/testcase-data/phandle-tests/consumer-a:phandle-list:8",
- "/testcase-data/phandle-tests/consumer-a:phandle-list:0";
-}; };
diff --git a/drivers/of/unittest-data/testcases.dts b/drivers/of/unittest-data/testcases.dts
new file mode 100644
index 000000000000..12f7c3d649c8
--- /dev/null
+++ b/drivers/of/unittest-data/testcases.dts
@@ -0,0 +1,79 @@
+/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"
+#include "tests-overlay.dtsi"
+
+/*
+ * phandle fixup data - generated by dtc patches that aren't upstream.
+ * This data must be regenerated whenever phandle references are modified in
+ * the testdata tree.
+ *
+ * The format of this data may be subject to change. For the time being consider
+ * this a kernel-internal data format.
+ */
+/ { __local_fixups__ {
+ testcase-data {
+ phandle-tests {
+ consumer-a {
+ phandle-list = <0x00000000 0x00000008
+ 0x00000018 0x00000028
+ 0x00000034 0x00000038>;
+ phandle-list-bad-args = <0x00000000 0x0000000c>;
+ };
+ };
+ interrupts {
+ intmap0 {
+ interrupt-map = <0x00000004 0x00000010
+ 0x00000024 0x00000034>;
+ };
+ intmap1 {
+ interrupt-map = <0x0000000c>;
+ };
+ interrupts0 {
+ interrupt-parent = <0x00000000>;
+ };
+ interrupts1 {
+ interrupt-parent = <0x00000000>;
+ };
+ interrupts-extended0 {
+ interrupts-extended = <0x00000000 0x00000008
+ 0x00000018 0x00000024
+ 0x0000002c 0x00000034
+ 0x0000003c>;
+ };
+ };
+ testcase-device1 {
+ interrupt-parent = <0x00000000>;
+ };
+ testcase-device2 {
+ interrupt-parent = <0x00000000>;
+ };
+ overlay2 {
+ fragment@0 {
+ target = <0x00000000>;
+ };
+ };
+ overlay3 {
+ fragment@0 {
+ target = <0x00000000>;
+ };
+ };
+ overlay4 {
+ fragment@0 {
+ target = <0x00000000>;
+ };
+ };
+ };
+}; };
diff --git a/drivers/of/testcase-data/tests-interrupts.dtsi b/drivers/of/unittest-data/tests-interrupts.dtsi
index da4695f60351..da4695f60351 100644
--- a/drivers/of/testcase-data/tests-interrupts.dtsi
+++ b/drivers/of/unittest-data/tests-interrupts.dtsi
diff --git a/drivers/of/testcase-data/tests-match.dtsi b/drivers/of/unittest-data/tests-match.dtsi
index c9e541129534..c9e541129534 100644
--- a/drivers/of/testcase-data/tests-match.dtsi
+++ b/drivers/of/unittest-data/tests-match.dtsi
diff --git a/drivers/of/unittest-data/tests-overlay.dtsi b/drivers/of/unittest-data/tests-overlay.dtsi
new file mode 100644
index 000000000000..a2b687d5f324
--- /dev/null
+++ b/drivers/of/unittest-data/tests-overlay.dtsi
@@ -0,0 +1,235 @@
+
+/ {
+ testcase-data {
+ overlay-node {
+
+ /* test bus */
+ selftestbus: test-bus {
+ compatible = "simple-bus";
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ selftest100: test-selftest100 {
+ compatible = "selftest";
+ status = "okay";
+ reg = <100>;
+ };
+
+ selftest101: test-selftest101 {
+ compatible = "selftest";
+ status = "disabled";
+ reg = <101>;
+ };
+
+ selftest0: test-selftest0 {
+ compatible = "selftest";
+ status = "disabled";
+ reg = <0>;
+ };
+
+ selftest1: test-selftest1 {
+ compatible = "selftest";
+ status = "okay";
+ reg = <1>;
+ };
+
+ selftest2: test-selftest2 {
+ compatible = "selftest";
+ status = "disabled";
+ reg = <2>;
+ };
+
+ selftest3: test-selftest3 {
+ compatible = "selftest";
+ status = "okay";
+ reg = <3>;
+ };
+
+ selftest5: test-selftest5 {
+ compatible = "selftest";
+ status = "disabled";
+ reg = <5>;
+ };
+
+ selftest6: test-selftest6 {
+ compatible = "selftest";
+ status = "disabled";
+ reg = <6>;
+ };
+
+ selftest7: test-selftest7 {
+ compatible = "selftest";
+ status = "disabled";
+ reg = <7>;
+ };
+
+ selftest8: test-selftest8 {
+ compatible = "selftest";
+ status = "disabled";
+ reg = <8>;
+ };
+ };
+ };
+
+ /* test enable using absolute target path */
+ overlay0 {
+ fragment@0 {
+ target-path = "/testcase-data/overlay-node/test-bus/test-selftest0";
+ __overlay__ {
+ status = "okay";
+ };
+ };
+ };
+
+ /* test disable using absolute target path */
+ overlay1 {
+ fragment@0 {
+ target-path = "/testcase-data/overlay-node/test-bus/test-selftest1";
+ __overlay__ {
+ status = "disabled";
+ };
+ };
+ };
+
+ /* test enable using label */
+ overlay2 {
+ fragment@0 {
+ target = <&selftest2>;
+ __overlay__ {
+ status = "okay";
+ };
+ };
+ };
+
+ /* test disable using label */
+ overlay3 {
+ fragment@0 {
+ target = <&selftest3>;
+ __overlay__ {
+ status = "disabled";
+ };
+ };
+ };
+
+ /* test insertion of a full node */
+ overlay4 {
+ fragment@0 {
+ target = <&selftestbus>;
+ __overlay__ {
+
+ /* suppress DTC warning */
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ test-selftest4 {
+ compatible = "selftest";
+ status = "okay";
+ reg = <4>;
+ };
+ };
+ };
+ };
+
+ /* test overlay apply revert */
+ overlay5 {
+ fragment@0 {
+ target-path = "/testcase-data/overlay-node/test-bus/test-selftest5";
+ __overlay__ {
+ status = "okay";
+ };
+ };
+ };
+
+ /* test overlays application and removal in sequence */
+ overlay6 {
+ fragment@0 {
+ target-path = "/testcase-data/overlay-node/test-bus/test-selftest6";
+ __overlay__ {
+ status = "okay";
+ };
+ };
+ };
+ overlay7 {
+ fragment@0 {
+ target-path = "/testcase-data/overlay-node/test-bus/test-selftest7";
+ __overlay__ {
+ status = "okay";
+ };
+ };
+ };
+
+ /* test overlays application and removal in bad sequence */
+ overlay8 {
+ fragment@0 {
+ target-path = "/testcase-data/overlay-node/test-bus/test-selftest8";
+ __overlay__ {
+ status = "okay";
+ };
+ };
+ };
+ overlay9 {
+ fragment@0 {
+ target-path = "/testcase-data/overlay-node/test-bus/test-selftest8";
+ __overlay__ {
+ property-foo = "bar";
+ };
+ };
+ };
+
+ overlay10 {
+ fragment@0 {
+ target-path = "/testcase-data/overlay-node/test-bus";
+ __overlay__ {
+
+ /* suppress DTC warning */
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ test-selftest10 {
+ compatible = "selftest";
+ status = "okay";
+ reg = <10>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ test-selftest101 {
+ compatible = "selftest";
+ status = "okay";
+ reg = <1>;
+ };
+
+ };
+ };
+ };
+ };
+
+ overlay11 {
+ fragment@0 {
+ target-path = "/testcase-data/overlay-node/test-bus";
+ __overlay__ {
+
+ /* suppress DTC warning */
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ test-selftest11 {
+ compatible = "selftest";
+ status = "okay";
+ reg = <11>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ test-selftest111 {
+ compatible = "selftest";
+ status = "okay";
+ reg = <1>;
+ };
+
+ };
+ };
+ };
+ };
+ };
+};
diff --git a/drivers/of/testcase-data/tests-phandle.dtsi b/drivers/of/unittest-data/tests-phandle.dtsi
index 5b1527e8a7fb..5b1527e8a7fb 100644
--- a/drivers/of/testcase-data/tests-phandle.dtsi
+++ b/drivers/of/unittest-data/tests-phandle.dtsi
diff --git a/drivers/of/testcase-data/tests-platform.dtsi b/drivers/of/unittest-data/tests-platform.dtsi
index eb20eeb2b062..eb20eeb2b062 100644
--- a/drivers/of/testcase-data/tests-platform.dtsi
+++ b/drivers/of/unittest-data/tests-platform.dtsi
diff --git a/drivers/of/selftest.c b/drivers/of/unittest.c
index e2d79afa9dc6..e5fa3e91f82f 100644
--- a/drivers/of/selftest.c
+++ b/drivers/of/unittest.c
@@ -17,6 +17,8 @@
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/of_platform.h>
#include "of_private.h"
@@ -25,24 +27,22 @@ static struct selftest_results {
int failed;
} selftest_results;
-#define NO_OF_NODES 3
-static struct device_node *nodes[NO_OF_NODES];
-static int last_node_index;
-static bool selftest_live_tree;
-
-#define selftest(result, fmt, ...) { \
- if (!(result)) { \
+#define selftest(result, fmt, ...) ({ \
+ bool failed = !(result); \
+ if (failed) { \
selftest_results.failed++; \
pr_err("FAIL %s():%i " fmt, __func__, __LINE__, ##__VA_ARGS__); \
} else { \
selftest_results.passed++; \
pr_debug("pass %s():%i\n", __func__, __LINE__); \
} \
-}
+ failed; \
+})
static void __init of_selftest_find_node_by_name(void)
{
struct device_node *np;
+ const char *options;
np = of_find_node_by_path("/testcase-data");
selftest(np && !strcmp("/testcase-data", np->full_name),
@@ -83,6 +83,51 @@ static void __init of_selftest_find_node_by_name(void)
np = of_find_node_by_path("testcase-alias/missing-path");
selftest(!np, "non-existent alias with relative path returned node %s\n", np->full_name);
of_node_put(np);
+
+ np = of_find_node_opts_by_path("/testcase-data:testoption", &options);
+ selftest(np && !strcmp("testoption", options),
+ "option path test failed\n");
+ of_node_put(np);
+
+ np = of_find_node_opts_by_path("/testcase-data:test/option", &options);
+ selftest(np && !strcmp("test/option", options),
+ "option path test, subcase #1 failed\n");
+ of_node_put(np);
+
+ np = of_find_node_opts_by_path("/testcase-data/testcase-device1:test/option", &options);
+ selftest(np && !strcmp("test/option", options),
+ "option path test, subcase #2 failed\n");
+ of_node_put(np);
+
+ np = of_find_node_opts_by_path("/testcase-data:testoption", NULL);
+ selftest(np, "NULL option path test failed\n");
+ of_node_put(np);
+
+ np = of_find_node_opts_by_path("testcase-alias:testaliasoption",
+ &options);
+ selftest(np && !strcmp("testaliasoption", options),
+ "option alias path test failed\n");
+ of_node_put(np);
+
+ np = of_find_node_opts_by_path("testcase-alias:test/alias/option",
+ &options);
+ selftest(np && !strcmp("test/alias/option", options),
+ "option alias path test, subcase #1 failed\n");
+ of_node_put(np);
+
+ np = of_find_node_opts_by_path("testcase-alias:testaliasoption", NULL);
+ selftest(np, "NULL option alias path test failed\n");
+ of_node_put(np);
+
+ options = "testoption";
+ np = of_find_node_opts_by_path("testcase-alias", &options);
+ selftest(np && !options, "option clearing test failed\n");
+ of_node_put(np);
+
+ options = "testoption";
+ np = of_find_node_opts_by_path("/", &options);
+ selftest(np && !options, "option clearing root node test failed\n");
+ of_node_put(np);
}
static void __init of_selftest_dynamic(void)
@@ -148,7 +193,7 @@ static void __init of_selftest_dynamic(void)
static int __init of_selftest_check_node_linkage(struct device_node *np)
{
- struct device_node *child, *allnext_index = np;
+ struct device_node *child;
int count = 0, rc;
for_each_child_of_node(np, child) {
@@ -158,14 +203,6 @@ static int __init of_selftest_check_node_linkage(struct device_node *np)
return -EINVAL;
}
- while (allnext_index && allnext_index != child)
- allnext_index = allnext_index->allnext;
- if (allnext_index != child) {
- pr_err("Node %s is ordered differently in sibling and allnode lists\n",
- child->name);
- return -EINVAL;
- }
-
rc = of_selftest_check_node_linkage(child);
if (rc < 0)
return rc;
@@ -180,12 +217,12 @@ static void __init of_selftest_check_tree_linkage(void)
struct device_node *np;
int allnode_count = 0, child_count;
- if (!of_allnodes)
+ if (!of_root)
return;
for_each_of_allnodes(np)
allnode_count++;
- child_count = of_selftest_check_node_linkage(of_allnodes);
+ child_count = of_selftest_check_node_linkage(of_root);
selftest(child_count > 0, "Device node data structure is corrupted\n");
selftest(child_count == allnode_count, "allnodes list size (%i) doesn't match"
@@ -354,9 +391,9 @@ static void __init of_selftest_property_string(void)
rc = of_property_match_string(np, "phandle-list-names", "first");
selftest(rc == 0, "first expected:0 got:%i\n", rc);
rc = of_property_match_string(np, "phandle-list-names", "second");
- selftest(rc == 1, "second expected:0 got:%i\n", rc);
+ selftest(rc == 1, "second expected:1 got:%i\n", rc);
rc = of_property_match_string(np, "phandle-list-names", "third");
- selftest(rc == 2, "third expected:0 got:%i\n", rc);
+ selftest(rc == 2, "third expected:2 got:%i\n", rc);
rc = of_property_match_string(np, "phandle-list-names", "fourth");
selftest(rc == -ENODATA, "unmatched string; rc=%i\n", rc);
rc = of_property_match_string(np, "missing-property", "blah");
@@ -451,15 +488,14 @@ static void __init of_selftest_changeset(void)
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 device_node *n1, *n2, *n21, *nremove, *parent, *np;
struct of_changeset chgset;
- of_changeset_init(&chgset);
- n1 = __of_node_alloc("/testcase-data/changeset/n1", GFP_KERNEL);
+ n1 = __of_node_dup(NULL, "/testcase-data/changeset/n1");
selftest(n1, "testcase setup failure\n");
- n2 = __of_node_alloc("/testcase-data/changeset/n2", GFP_KERNEL);
+ n2 = __of_node_dup(NULL, "/testcase-data/changeset/n2");
selftest(n2, "testcase setup failure\n");
- n21 = __of_node_alloc("/testcase-data/changeset/n2/n21", GFP_KERNEL);
+ n21 = __of_node_dup(NULL, "%s/%s", "/testcase-data/changeset/n2", "n21");
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");
@@ -487,6 +523,11 @@ static void __init of_selftest_changeset(void)
selftest(!of_changeset_apply(&chgset), "apply failed\n");
mutex_unlock(&of_mutex);
+ /* Make sure node names are constructed correctly */
+ selftest((np = of_find_node_by_path("/testcase-data/changeset/n2/n21")),
+ "'%s' not added\n", n21->full_name);
+ of_node_put(np);
+
mutex_lock(&of_mutex);
selftest(!of_changeset_revert(&chgset), "revert failed\n");
mutex_unlock(&of_mutex);
@@ -702,10 +743,13 @@ static void __init of_selftest_match_node(void)
}
}
+struct device test_bus = {
+ .init_name = "unittest-bus",
+};
static void __init of_selftest_platform_populate(void)
{
- int irq;
- struct device_node *np, *child;
+ int irq, rc;
+ struct device_node *np, *child, *grandchild;
struct platform_device *pdev;
struct of_device_id match[] = {
{ .compatible = "test-device", },
@@ -730,20 +774,32 @@ static void __init of_selftest_platform_populate(void)
irq = platform_get_irq(pdev, 0);
selftest(irq < 0 && irq != -EPROBE_DEFER, "device parsing error failed - %d\n", irq);
- np = of_find_node_by_path("/testcase-data/platform-tests");
- if (!np) {
- pr_err("No testcase data in device tree\n");
+ if (selftest(np = of_find_node_by_path("/testcase-data/platform-tests"),
+ "No testcase data in device tree\n"));
+ return;
+
+ if (selftest(!(rc = device_register(&test_bus)),
+ "testbus registration failed; rc=%i\n", rc));
return;
- }
for_each_child_of_node(np, child) {
- struct device_node *grandchild;
- of_platform_populate(child, match, NULL, NULL);
+ of_platform_populate(child, match, NULL, &test_bus);
for_each_child_of_node(child, grandchild)
selftest(of_find_device_by_node(grandchild),
"Could not create device for node '%s'\n",
grandchild->name);
}
+
+ of_platform_depopulate(&test_bus);
+ for_each_child_of_node(np, child) {
+ for_each_child_of_node(child, grandchild)
+ selftest(!of_find_device_by_node(grandchild),
+ "device didn't get destroyed '%s'\n",
+ grandchild->name);
+ }
+
+ device_unregister(&test_bus);
+ of_node_put(np);
}
/**
@@ -775,33 +831,32 @@ static void update_node_properties(struct device_node *np,
*/
static int attach_node_and_children(struct device_node *np)
{
- struct device_node *next, *root = np, *dup;
+ struct device_node *next, *dup, *child;
+ unsigned long flags;
- /* skip root node */
- np = np->child;
- /* storing a copy in temporary node */
- dup = np;
-
- while (dup) {
- if (WARN_ON(last_node_index >= NO_OF_NODES))
- return -EINVAL;
- nodes[last_node_index++] = dup;
- dup = dup->sibling;
+ dup = of_find_node_by_path(np->full_name);
+ if (dup) {
+ update_node_properties(np, dup);
+ return 0;
}
- 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;
+ child = np->child;
+ np->child = NULL;
+
+ mutex_lock(&of_mutex);
+ raw_spin_lock_irqsave(&devtree_lock, flags);
+ np->sibling = np->parent->child;
+ np->parent->child = np;
+ of_node_clear_flag(np, OF_DETACHED);
+ raw_spin_unlock_irqrestore(&devtree_lock, flags);
+
+ __of_attach_node_sysfs(np);
+ mutex_unlock(&of_mutex);
+
+ while (child) {
+ next = child->sibling;
+ attach_node_and_children(child);
+ child = next;
}
return 0;
@@ -846,11 +901,8 @@ static int __init selftest_data_add(void)
return -EINVAL;
}
- if (!of_allnodes) {
- /* enabling flag for removing nodes */
- selftest_live_tree = true;
- of_allnodes = selftest_data_node;
-
+ if (!of_root) {
+ of_root = selftest_data_node;
for_each_of_allnodes(np)
__of_attach_node_sysfs(np);
of_aliases = of_find_node_by_path("/aliases");
@@ -859,62 +911,532 @@ static int __init selftest_data_add(void)
}
/* attach the sub-tree to live tree */
- return attach_node_and_children(selftest_data_node);
+ np = selftest_data_node->child;
+ while (np) {
+ struct device_node *next = np->sibling;
+ np->parent = of_root;
+ attach_node_and_children(np);
+ np = next;
+ }
+ return 0;
}
-/**
- * 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)
+#ifdef CONFIG_OF_OVERLAY
+
+static int selftest_probe(struct platform_device *pdev)
{
- while (np->child)
- detach_node_and_children(np->child);
- of_detach_node(np);
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+
+ if (np == NULL) {
+ dev_err(dev, "No OF data for device\n");
+ return -EINVAL;
+
+ }
+
+ dev_dbg(dev, "%s for node @%s\n", __func__, np->full_name);
+
+ of_platform_populate(np, NULL, NULL, &pdev->dev);
+
+ return 0;
}
-/**
- * selftest_data_remove - removes the selftest data
- * nodes from the live tree
- */
-static void selftest_data_remove(void)
+static int selftest_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+
+ dev_dbg(dev, "%s for node @%s\n", __func__, np->full_name);
+ return 0;
+}
+
+static struct of_device_id selftest_match[] = {
+ { .compatible = "selftest", },
+ {},
+};
+
+static struct platform_driver selftest_driver = {
+ .probe = selftest_probe,
+ .remove = selftest_remove,
+ .driver = {
+ .name = "selftest",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(selftest_match),
+ },
+};
+
+/* get the platform device instantiated at the path */
+static struct platform_device *of_path_to_platform_device(const char *path)
{
struct device_node *np;
- struct property *prop;
+ struct platform_device *pdev;
+
+ np = of_find_node_by_path(path);
+ if (np == NULL)
+ return NULL;
+
+ pdev = of_find_device_by_node(np);
+ of_node_put(np);
+
+ return pdev;
+}
+
+/* find out if a platform device exists at that path */
+static int of_path_platform_device_exists(const char *path)
+{
+ struct platform_device *pdev;
+
+ pdev = of_path_to_platform_device(path);
+ platform_device_put(pdev);
+ return pdev != NULL;
+}
+
+static const char *selftest_path(int nr)
+{
+ static char buf[256];
+
+ snprintf(buf, sizeof(buf) - 1,
+ "/testcase-data/overlay-node/test-bus/test-selftest%d", nr);
+ buf[sizeof(buf) - 1] = '\0';
+
+ return buf;
+}
+
+static const char *overlay_path(int nr)
+{
+ static char buf[256];
+
+ snprintf(buf, sizeof(buf) - 1,
+ "/testcase-data/overlay%d", nr);
+ buf[sizeof(buf) - 1] = '\0';
+
+ return buf;
+}
+
+static const char *bus_path = "/testcase-data/overlay-node/test-bus";
+
+static int of_selftest_apply_overlay(int selftest_nr, int overlay_nr,
+ int *overlay_id)
+{
+ struct device_node *np = NULL;
+ int ret, id = -1;
+
+ np = of_find_node_by_path(overlay_path(overlay_nr));
+ if (np == NULL) {
+ selftest(0, "could not find overlay node @\"%s\"\n",
+ overlay_path(overlay_nr));
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = of_overlay_create(np);
+ if (ret < 0) {
+ selftest(0, "could not create overlay from \"%s\"\n",
+ overlay_path(overlay_nr));
+ goto out;
+ }
+ id = ret;
+
+ ret = 0;
+
+out:
+ of_node_put(np);
+
+ if (overlay_id)
+ *overlay_id = id;
- if (selftest_live_tree) {
- of_node_put(of_aliases);
- of_node_put(of_chosen);
- of_aliases = NULL;
- of_chosen = NULL;
- for_each_child_of_node(of_allnodes, np)
- detach_node_and_children(np);
- __of_detach_node_sysfs(of_allnodes);
- of_allnodes = NULL;
+ return ret;
+}
+
+/* apply an overlay while checking before and after states */
+static int of_selftest_apply_overlay_check(int overlay_nr, int selftest_nr,
+ int before, int after)
+{
+ int ret;
+
+ /* selftest device must not be in before state */
+ if (of_path_platform_device_exists(selftest_path(selftest_nr))
+ != before) {
+ selftest(0, "overlay @\"%s\" with device @\"%s\" %s\n",
+ overlay_path(overlay_nr),
+ selftest_path(selftest_nr),
+ !before ? "enabled" : "disabled");
+ return -EINVAL;
+ }
+
+ ret = of_selftest_apply_overlay(overlay_nr, selftest_nr, NULL);
+ if (ret != 0) {
+ /* of_selftest_apply_overlay already called selftest() */
+ return ret;
+ }
+
+ /* selftest device must be to set to after state */
+ if (of_path_platform_device_exists(selftest_path(selftest_nr))
+ != after) {
+ selftest(0, "overlay @\"%s\" failed to create @\"%s\" %s\n",
+ overlay_path(overlay_nr),
+ selftest_path(selftest_nr),
+ !after ? "enabled" : "disabled");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* apply an overlay and then revert it while checking before, after states */
+static int of_selftest_apply_revert_overlay_check(int overlay_nr,
+ int selftest_nr, int before, int after)
+{
+ int ret, ov_id;
+
+ /* selftest device must be in before state */
+ if (of_path_platform_device_exists(selftest_path(selftest_nr))
+ != before) {
+ selftest(0, "overlay @\"%s\" with device @\"%s\" %s\n",
+ overlay_path(overlay_nr),
+ selftest_path(selftest_nr),
+ !before ? "enabled" : "disabled");
+ return -EINVAL;
+ }
+
+ /* apply the overlay */
+ ret = of_selftest_apply_overlay(overlay_nr, selftest_nr, &ov_id);
+ if (ret != 0) {
+ /* of_selftest_apply_overlay already called selftest() */
+ return ret;
+ }
+
+ /* selftest device must be in after state */
+ if (of_path_platform_device_exists(selftest_path(selftest_nr))
+ != after) {
+ selftest(0, "overlay @\"%s\" failed to create @\"%s\" %s\n",
+ overlay_path(overlay_nr),
+ selftest_path(selftest_nr),
+ !after ? "enabled" : "disabled");
+ return -EINVAL;
+ }
+
+ ret = of_overlay_destroy(ov_id);
+ if (ret != 0) {
+ selftest(0, "overlay @\"%s\" failed to be destroyed @\"%s\"\n",
+ overlay_path(overlay_nr),
+ selftest_path(selftest_nr));
+ return ret;
+ }
+
+ /* selftest device must be again in before state */
+ if (of_path_platform_device_exists(selftest_path(selftest_nr))
+ != before) {
+ selftest(0, "overlay @\"%s\" with device @\"%s\" %s\n",
+ overlay_path(overlay_nr),
+ selftest_path(selftest_nr),
+ !before ? "enabled" : "disabled");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* test activation of device */
+static void of_selftest_overlay_0(void)
+{
+ int ret;
+
+ /* device should enable */
+ ret = of_selftest_apply_overlay_check(0, 0, 0, 1);
+ if (ret != 0)
+ return;
+
+ selftest(1, "overlay test %d passed\n", 0);
+}
+
+/* test deactivation of device */
+static void of_selftest_overlay_1(void)
+{
+ int ret;
+
+ /* device should disable */
+ ret = of_selftest_apply_overlay_check(1, 1, 1, 0);
+ if (ret != 0)
+ return;
+
+ selftest(1, "overlay test %d passed\n", 1);
+}
+
+/* test activation of device */
+static void of_selftest_overlay_2(void)
+{
+ int ret;
+
+ /* device should enable */
+ ret = of_selftest_apply_overlay_check(2, 2, 0, 1);
+ if (ret != 0)
+ return;
+
+ selftest(1, "overlay test %d passed\n", 2);
+}
+
+/* test deactivation of device */
+static void of_selftest_overlay_3(void)
+{
+ int ret;
+
+ /* device should disable */
+ ret = of_selftest_apply_overlay_check(3, 3, 1, 0);
+ if (ret != 0)
+ return;
+
+ selftest(1, "overlay test %d passed\n", 3);
+}
+
+/* test activation of a full device node */
+static void of_selftest_overlay_4(void)
+{
+ int ret;
+
+ /* device should disable */
+ ret = of_selftest_apply_overlay_check(4, 4, 0, 1);
+ if (ret != 0)
+ return;
+
+ selftest(1, "overlay test %d passed\n", 4);
+}
+
+/* test overlay apply/revert sequence */
+static void of_selftest_overlay_5(void)
+{
+ int ret;
+
+ /* device should disable */
+ ret = of_selftest_apply_revert_overlay_check(5, 5, 0, 1);
+ if (ret != 0)
return;
+
+ selftest(1, "overlay test %d passed\n", 5);
+}
+
+/* test overlay application in sequence */
+static void of_selftest_overlay_6(void)
+{
+ struct device_node *np;
+ int ret, i, ov_id[2];
+ int overlay_nr = 6, selftest_nr = 6;
+ int before = 0, after = 1;
+
+ /* selftest device must be in before state */
+ for (i = 0; i < 2; i++) {
+ if (of_path_platform_device_exists(
+ selftest_path(selftest_nr + i))
+ != before) {
+ selftest(0, "overlay @\"%s\" with device @\"%s\" %s\n",
+ overlay_path(overlay_nr + i),
+ selftest_path(selftest_nr + i),
+ !before ? "enabled" : "disabled");
+ return;
+ }
}
- while (last_node_index-- > 0) {
- if (nodes[last_node_index]) {
- np = of_find_node_by_path(nodes[last_node_index]->full_name);
- if (np == nodes[last_node_index]) {
- if (of_aliases == np) {
- of_node_put(of_aliases);
- of_aliases = NULL;
- }
- detach_node_and_children(np);
- } else {
- for_each_property_of_node(np, prop) {
- if (strcmp(prop->name, "testcase-alias") == 0)
- of_remove_property(np, prop);
- }
- }
+ /* apply the overlays */
+ for (i = 0; i < 2; i++) {
+
+ np = of_find_node_by_path(overlay_path(overlay_nr + i));
+ if (np == NULL) {
+ selftest(0, "could not find overlay node @\"%s\"\n",
+ overlay_path(overlay_nr + i));
+ return;
+ }
+
+ ret = of_overlay_create(np);
+ if (ret < 0) {
+ selftest(0, "could not create overlay from \"%s\"\n",
+ overlay_path(overlay_nr + i));
+ return;
+ }
+ ov_id[i] = ret;
+ }
+
+ for (i = 0; i < 2; i++) {
+ /* selftest device must be in after state */
+ if (of_path_platform_device_exists(
+ selftest_path(selftest_nr + i))
+ != after) {
+ selftest(0, "overlay @\"%s\" failed @\"%s\" %s\n",
+ overlay_path(overlay_nr + i),
+ selftest_path(selftest_nr + i),
+ !after ? "enabled" : "disabled");
+ return;
}
}
+
+ for (i = 1; i >= 0; i--) {
+ ret = of_overlay_destroy(ov_id[i]);
+ if (ret != 0) {
+ selftest(0, "overlay @\"%s\" failed destroy @\"%s\"\n",
+ overlay_path(overlay_nr + i),
+ selftest_path(selftest_nr + i));
+ return;
+ }
+ }
+
+ for (i = 0; i < 2; i++) {
+ /* selftest device must be again in before state */
+ if (of_path_platform_device_exists(
+ selftest_path(selftest_nr + i))
+ != before) {
+ selftest(0, "overlay @\"%s\" with device @\"%s\" %s\n",
+ overlay_path(overlay_nr + i),
+ selftest_path(selftest_nr + i),
+ !before ? "enabled" : "disabled");
+ return;
+ }
+ }
+
+ selftest(1, "overlay test %d passed\n", 6);
+}
+
+/* test overlay application in sequence */
+static void of_selftest_overlay_8(void)
+{
+ struct device_node *np;
+ int ret, i, ov_id[2];
+ int overlay_nr = 8, selftest_nr = 8;
+
+ /* we don't care about device state in this test */
+
+ /* apply the overlays */
+ for (i = 0; i < 2; i++) {
+
+ np = of_find_node_by_path(overlay_path(overlay_nr + i));
+ if (np == NULL) {
+ selftest(0, "could not find overlay node @\"%s\"\n",
+ overlay_path(overlay_nr + i));
+ return;
+ }
+
+ ret = of_overlay_create(np);
+ if (ret < 0) {
+ selftest(0, "could not create overlay from \"%s\"\n",
+ overlay_path(overlay_nr + i));
+ return;
+ }
+ ov_id[i] = ret;
+ }
+
+ /* now try to remove first overlay (it should fail) */
+ ret = of_overlay_destroy(ov_id[0]);
+ if (ret == 0) {
+ selftest(0, "overlay @\"%s\" was destroyed @\"%s\"\n",
+ overlay_path(overlay_nr + 0),
+ selftest_path(selftest_nr));
+ return;
+ }
+
+ /* removing them in order should work */
+ for (i = 1; i >= 0; i--) {
+ ret = of_overlay_destroy(ov_id[i]);
+ if (ret != 0) {
+ selftest(0, "overlay @\"%s\" not destroyed @\"%s\"\n",
+ overlay_path(overlay_nr + i),
+ selftest_path(selftest_nr));
+ return;
+ }
+ }
+
+ selftest(1, "overlay test %d passed\n", 8);
+}
+
+/* test insertion of a bus with parent devices */
+static void of_selftest_overlay_10(void)
+{
+ int ret;
+ char *child_path;
+
+ /* device should disable */
+ ret = of_selftest_apply_overlay_check(10, 10, 0, 1);
+ if (selftest(ret == 0, "overlay test %d failed; overlay application\n", 10))
+ return;
+
+ child_path = kasprintf(GFP_KERNEL, "%s/test-selftest101",
+ selftest_path(10));
+ if (selftest(child_path, "overlay test %d failed; kasprintf\n", 10))
+ return;
+
+ ret = of_path_platform_device_exists(child_path);
+ kfree(child_path);
+ if (selftest(ret, "overlay test %d failed; no child device\n", 10))
+ return;
+}
+
+/* test insertion of a bus with parent devices (and revert) */
+static void of_selftest_overlay_11(void)
+{
+ int ret;
+
+ /* device should disable */
+ ret = of_selftest_apply_revert_overlay_check(11, 11, 0, 1);
+ if (selftest(ret == 0, "overlay test %d failed; overlay application\n", 11))
+ return;
+}
+
+static void __init of_selftest_overlay(void)
+{
+ struct device_node *bus_np = NULL;
+ int ret;
+
+ ret = platform_driver_register(&selftest_driver);
+ if (ret != 0) {
+ selftest(0, "could not register selftest driver\n");
+ goto out;
+ }
+
+ bus_np = of_find_node_by_path(bus_path);
+ if (bus_np == NULL) {
+ selftest(0, "could not find bus_path \"%s\"\n", bus_path);
+ goto out;
+ }
+
+ ret = of_platform_populate(bus_np, of_default_bus_match_table,
+ NULL, NULL);
+ if (ret != 0) {
+ selftest(0, "could not populate bus @ \"%s\"\n", bus_path);
+ goto out;
+ }
+
+ if (!of_path_platform_device_exists(selftest_path(100))) {
+ selftest(0, "could not find selftest0 @ \"%s\"\n",
+ selftest_path(100));
+ goto out;
+ }
+
+ if (of_path_platform_device_exists(selftest_path(101))) {
+ selftest(0, "selftest1 @ \"%s\" should not exist\n",
+ selftest_path(101));
+ goto out;
+ }
+
+ selftest(1, "basic infrastructure of overlays passed");
+
+ /* tests in sequence */
+ of_selftest_overlay_0();
+ of_selftest_overlay_1();
+ of_selftest_overlay_2();
+ of_selftest_overlay_3();
+ of_selftest_overlay_4();
+ of_selftest_overlay_5();
+ of_selftest_overlay_6();
+ of_selftest_overlay_8();
+
+ of_selftest_overlay_10();
+ of_selftest_overlay_11();
+
+out:
+ of_node_put(bus_np);
}
+#else
+static inline void __init of_selftest_overlay(void) { }
+#endif
+
static int __init of_selftest(void)
{
struct device_node *np;
@@ -947,9 +1469,7 @@ static int __init of_selftest(void)
of_selftest_parse_interrupts_extended();
of_selftest_match_node();
of_selftest_platform_populate();
-
- /* removing selftest data from live tree */
- selftest_data_remove();
+ of_selftest_overlay();
/* Double check linkage after removing testcase data */
of_selftest_check_tree_linkage();