diff options
Diffstat (limited to 'drivers/of')
-rw-r--r-- | drivers/of/Kconfig | 18 | ||||
-rw-r--r-- | drivers/of/Makefile | 1 | ||||
-rw-r--r-- | drivers/of/address.c | 8 | ||||
-rw-r--r-- | drivers/of/base.c | 458 | ||||
-rw-r--r-- | drivers/of/fdt.c | 145 | ||||
-rw-r--r-- | drivers/of/of_mdio.c | 23 | ||||
-rw-r--r-- | drivers/of/of_net.c | 33 | ||||
-rw-r--r-- | drivers/of/of_reserved_mem.c | 217 | ||||
-rw-r--r-- | drivers/of/pdt.c | 3 | ||||
-rw-r--r-- | drivers/of/selftest.c | 62 | ||||
-rw-r--r-- | drivers/of/testcase-data/tests-phandle.dtsi | 3 |
11 files changed, 861 insertions, 110 deletions
diff --git a/drivers/of/Kconfig b/drivers/of/Kconfig index c6973f1..889005f 100644 --- a/drivers/of/Kconfig +++ b/drivers/of/Kconfig @@ -7,14 +7,6 @@ config OF menu "Device Tree and Open Firmware support" depends on OF -config PROC_DEVICETREE - bool "Support for device tree in /proc" - depends on PROC_FS && !SPARC - help - This option adds a device-tree directory under /proc which contains - an image of the device tree that the kernel copies from Open - Firmware or other boot firmware. If unsure, say Y here. - config OF_SELFTEST bool "Device Tree Runtime self tests" depends on OF_IRQ @@ -44,6 +36,10 @@ config OF_DYNAMIC config OF_ADDRESS def_bool y depends on !SPARC + select OF_ADDRESS_PCI if PCI + +config OF_ADDRESS_PCI + bool config OF_IRQ def_bool y @@ -75,4 +71,10 @@ config OF_MTD depends on MTD def_bool y +config OF_RESERVED_MEM + depends on OF_EARLY_FLATTREE + bool + help + Helpers to allow for reservation of memory regions + endmenu # OF diff --git a/drivers/of/Makefile b/drivers/of/Makefile index efd0510..ed9660a 100644 --- a/drivers/of/Makefile +++ b/drivers/of/Makefile @@ -9,3 +9,4 @@ 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 diff --git a/drivers/of/address.c b/drivers/of/address.c index 1a54f1f..cb4242a 100644 --- a/drivers/of/address.c +++ b/drivers/of/address.c @@ -91,7 +91,7 @@ static unsigned int of_bus_default_get_flags(const __be32 *addr) return IORESOURCE_MEM; } -#ifdef CONFIG_PCI +#ifdef CONFIG_OF_ADDRESS_PCI /* * PCI bus specific translator */ @@ -166,7 +166,9 @@ static int of_bus_pci_translate(__be32 *addr, u64 offset, int na) { return of_bus_default_translate(addr + 1, offset, na - 1); } +#endif /* CONFIG_OF_ADDRESS_PCI */ +#ifdef CONFIG_PCI const __be32 *of_get_pci_address(struct device_node *dev, int bar_no, u64 *size, unsigned int *flags) { @@ -356,7 +358,7 @@ static unsigned int of_bus_isa_get_flags(const __be32 *addr) */ static struct of_bus of_busses[] = { -#ifdef CONFIG_PCI +#ifdef CONFIG_OF_ADDRESS_PCI /* PCI */ { .name = "pci", @@ -367,7 +369,7 @@ static struct of_bus of_busses[] = { .translate = of_bus_pci_translate, .get_flags = of_bus_pci_get_flags, }, -#endif /* CONFIG_PCI */ +#endif /* CONFIG_OF_ADDRESS_PCI */ /* ISA */ { .name = "isa", diff --git a/drivers/of/base.c b/drivers/of/base.c index 89e888a..f72d19b 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -21,8 +21,10 @@ #include <linux/cpu.h> #include <linux/module.h> #include <linux/of.h> +#include <linux/of_graph.h> #include <linux/spinlock.h> #include <linux/slab.h> +#include <linux/string.h> #include <linux/proc_fs.h> #include "of_private.h" @@ -35,6 +37,12 @@ struct device_node *of_chosen; struct device_node *of_aliases; static struct device_node *of_stdout; +static struct kset *of_kset; + +/* + * Used to protect the of_aliases; but also overloaded to hold off addition of + * nodes to sysfs + */ DEFINE_MUTEX(of_aliases_mutex); /* use when traversing tree through the allnext, child, sibling, @@ -92,14 +100,14 @@ int __weak of_node_to_nid(struct device_node *np) struct device_node *of_node_get(struct device_node *node) { if (node) - kref_get(&node->kref); + kobject_get(&node->kobj); return node; } EXPORT_SYMBOL(of_node_get); -static inline struct device_node *kref_to_device_node(struct kref *kref) +static inline struct device_node *kobj_to_device_node(struct kobject *kobj) { - return container_of(kref, struct device_node, kref); + return container_of(kobj, struct device_node, kobj); } /** @@ -109,16 +117,15 @@ static inline struct device_node *kref_to_device_node(struct kref *kref) * In of_node_put() this function is passed to kref_put() * as the destructor. */ -static void of_node_release(struct kref *kref) +static void of_node_release(struct kobject *kobj) { - struct device_node *node = kref_to_device_node(kref); + struct device_node *node = kobj_to_device_node(kobj); struct property *prop = node->properties; /* We should never be releasing nodes that haven't been detached. */ if (!of_node_check_flag(node, OF_DETACHED)) { pr_err("ERROR: Bad of_node_put() on %s\n", node->full_name); dump_stack(); - kref_init(&node->kref); return; } @@ -151,11 +158,154 @@ static void of_node_release(struct kref *kref) void of_node_put(struct device_node *node) { if (node) - kref_put(&node->kref, of_node_release); + kobject_put(&node->kobj); } EXPORT_SYMBOL(of_node_put); +#else +static void of_node_release(struct kobject *kobj) +{ + /* Without CONFIG_OF_DYNAMIC, no nodes gets freed */ +} #endif /* CONFIG_OF_DYNAMIC */ +struct kobj_type of_node_ktype = { + .release = of_node_release, +}; + +static ssize_t of_node_property_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t offset, size_t count) +{ + struct property *pp = container_of(bin_attr, struct property, attr); + return memory_read_from_buffer(buf, count, &offset, pp->value, pp->length); +} + +static const char *safe_name(struct kobject *kobj, const char *orig_name) +{ + const char *name = orig_name; + struct kernfs_node *kn; + int i = 0; + + /* don't be a hero. After 16 tries give up */ + while (i < 16 && (kn = sysfs_get_dirent(kobj->sd, name))) { + sysfs_put(kn); + if (name != orig_name) + kfree(name); + name = kasprintf(GFP_KERNEL, "%s#%i", orig_name, ++i); + } + + if (name != orig_name) + pr_warn("device-tree: Duplicate name in %s, renamed to \"%s\"\n", + kobject_name(kobj), name); + return name; +} + +static int __of_add_property_sysfs(struct device_node *np, struct property *pp) +{ + int rc; + + /* Important: Don't leak passwords */ + bool secure = strncmp(pp->name, "security-", 9) == 0; + + sysfs_bin_attr_init(&pp->attr); + pp->attr.attr.name = safe_name(&np->kobj, pp->name); + pp->attr.attr.mode = secure ? S_IRUSR : S_IRUGO; + pp->attr.size = secure ? 0 : pp->length; + pp->attr.read = of_node_property_read; + + rc = sysfs_create_bin_file(&np->kobj, &pp->attr); + WARN(rc, "error adding attribute %s to node %s\n", pp->name, np->full_name); + return rc; +} + +static int __of_node_add(struct device_node *np) +{ + const char *name; + struct property *pp; + int rc; + + np->kobj.kset = of_kset; + if (!np->parent) { + /* Nodes without parents are new top level trees */ + rc = kobject_add(&np->kobj, NULL, safe_name(&of_kset->kobj, "base")); + } 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); + } + if (rc) + return rc; + + for_each_property_of_node(np, pp) + __of_add_property_sysfs(np, pp); + + return 0; +} + +int of_node_add(struct device_node *np) +{ + int rc = 0; + + BUG_ON(!of_node_is_initialized(np)); + + /* + * Grab the mutex here so that in a race condition between of_init() and + * of_node_add(), node addition will still be consistent. + */ + mutex_lock(&of_aliases_mutex); + if (of_kset) + rc = __of_node_add(np); + else + /* This scenario may be perfectly valid, but report it anyway */ + pr_info("of_node_add(%s) before of_init()\n", np->full_name); + mutex_unlock(&of_aliases_mutex); + return rc; +} + +#if defined(CONFIG_OF_DYNAMIC) +static void of_node_remove(struct device_node *np) +{ + struct property *pp; + + BUG_ON(!of_node_is_initialized(np)); + + /* only remove properties if on sysfs */ + if (of_node_is_attached(np)) { + for_each_property_of_node(np, pp) + sysfs_remove_bin_file(&np->kobj, &pp->attr); + kobject_del(&np->kobj); + } + + /* finally remove the kobj_init ref */ + of_node_put(np); +} +#endif + +static int __init of_init(void) +{ + struct device_node *np; + + /* Create the kset, and register existing nodes */ + mutex_lock(&of_aliases_mutex); + of_kset = kset_create_and_add("devicetree", NULL, firmware_kobj); + if (!of_kset) { + mutex_unlock(&of_aliases_mutex); + return -ENOMEM; + } + for_each_of_allnodes(np) + __of_node_add(np); + mutex_unlock(&of_aliases_mutex); + + /* Symlink in /proc as required by userspace ABI */ + if (of_allnodes) + proc_symlink("device-tree", NULL, "/sys/firmware/devicetree/base"); + + return 0; +} +core_initcall(of_init); + static struct property *__of_find_property(const struct device_node *np, const char *name, int *lenp) { @@ -904,6 +1054,38 @@ struct device_node *of_find_node_by_phandle(phandle handle) EXPORT_SYMBOL(of_find_node_by_phandle); /** + * of_property_count_elems_of_size - Count the number of elements in a property + * + * @np: device node from which the property value is to be read. + * @propname: name of the property to be searched. + * @elem_size: size of the individual element + * + * Search for a property in a device node and count the number of elements of + * size elem_size in it. Returns number of elements on sucess, -EINVAL if the + * property does not exist or its length does not match a multiple of elem_size + * and -ENODATA if the property does not have a value. + */ +int of_property_count_elems_of_size(const struct device_node *np, + const char *propname, int elem_size) +{ + struct property *prop = of_find_property(np, propname, NULL); + + if (!prop) + return -EINVAL; + if (!prop->value) + return -ENODATA; + + if (prop->length % elem_size != 0) { + pr_err("size of %s in node %s is not a multiple of %d\n", + propname, np->full_name, elem_size); + return -EINVAL; + } + + return prop->length / elem_size; +} +EXPORT_SYMBOL_GPL(of_property_count_elems_of_size); + +/** * of_find_property_value_of_size * * @np: device node from which the property value is to be read. @@ -1498,6 +1680,10 @@ static int of_property_notify(int action, struct device_node *np, { struct of_prop_reconfig pr; + /* only call notifiers if the node is attached */ + if (!of_node_is_attached(np)) + return 0; + pr.dn = np; pr.prop = prop; return of_reconfig_notify(action, &pr); @@ -1511,11 +1697,31 @@ static int of_property_notify(int action, struct device_node *np, #endif /** + * __of_add_property - Add a property to a node without lock operations + */ +static int __of_add_property(struct device_node *np, struct property *prop) +{ + struct property **next; + + prop->next = NULL; + next = &np->properties; + while (*next) { + if (strcmp(prop->name, (*next)->name) == 0) + /* duplicate ! don't insert it */ + return -EEXIST; + + next = &(*next)->next; + } + *next = prop; + + return 0; +} + +/** * of_add_property - Add a property to a node */ int of_add_property(struct device_node *np, struct property *prop) { - struct property **next; unsigned long flags; int rc; @@ -1523,27 +1729,16 @@ int of_add_property(struct device_node *np, struct property *prop) if (rc) return rc; - prop->next = NULL; raw_spin_lock_irqsave(&devtree_lock, flags); - next = &np->properties; - while (*next) { - if (strcmp(prop->name, (*next)->name) == 0) { - /* duplicate ! don't insert it */ - raw_spin_unlock_irqrestore(&devtree_lock, flags); - return -1; - } - next = &(*next)->next; - } - *next = prop; + rc = __of_add_property(np, prop); raw_spin_unlock_irqrestore(&devtree_lock, flags); + if (rc) + return rc; -#ifdef CONFIG_PROC_DEVICETREE - /* try to add to proc as well if it was initialized */ - if (np->pde) - proc_device_tree_add_prop(np->pde, prop); -#endif /* CONFIG_PROC_DEVICETREE */ + if (of_node_is_attached(np)) + __of_add_property_sysfs(np, prop); - return 0; + return rc; } /** @@ -1583,11 +1778,11 @@ int of_remove_property(struct device_node *np, struct property *prop) if (!found) return -ENODEV; -#ifdef CONFIG_PROC_DEVICETREE - /* try to remove the proc node as well */ - if (np->pde) - proc_device_tree_remove_prop(np->pde, prop); -#endif /* CONFIG_PROC_DEVICETREE */ + /* at early boot, bail hear and defer setup to of_init() */ + if (!of_kset) + return 0; + + sysfs_remove_bin_file(&np->kobj, &prop->attr); return 0; } @@ -1633,16 +1828,17 @@ int of_update_property(struct device_node *np, struct property *newprop) next = &(*next)->next; } raw_spin_unlock_irqrestore(&devtree_lock, flags); + if (rc) + return rc; + + /* Update the sysfs attribute */ + if (oldprop) + sysfs_remove_bin_file(&np->kobj, &oldprop->attr); + __of_add_property_sysfs(np, newprop); if (!found) return -ENODEV; -#ifdef CONFIG_PROC_DEVICETREE - /* try to add to proc as well if it was initialized */ - if (np->pde) - proc_device_tree_update_prop(np->pde, newprop, oldprop); -#endif /* CONFIG_PROC_DEVICETREE */ - return 0; } @@ -1677,22 +1873,6 @@ int of_reconfig_notify(unsigned long action, void *p) return notifier_to_errno(rc); } -#ifdef CONFIG_PROC_DEVICETREE -static void of_add_proc_dt_entry(struct device_node *dn) -{ - struct proc_dir_entry *ent; - - ent = proc_mkdir(strrchr(dn->full_name, '/') + 1, dn->parent->pde); - if (ent) - proc_device_tree_add_node(dn, ent); -} -#else -static void of_add_proc_dt_entry(struct device_node *dn) -{ - return; -} -#endif - /** * of_attach_node - Plug a device node into the tree and global list. */ @@ -1710,24 +1890,13 @@ int of_attach_node(struct device_node *np) np->allnext = of_allnodes; np->parent->child = np; of_allnodes = np; + of_node_clear_flag(np, OF_DETACHED); raw_spin_unlock_irqrestore(&devtree_lock, flags); - of_add_proc_dt_entry(np); + of_node_add(np); return 0; } -#ifdef CONFIG_PROC_DEVICETREE -static void of_remove_proc_dt_entry(struct device_node *dn) -{ - proc_remove(dn->pde); -} -#else -static void of_remove_proc_dt_entry(struct device_node *dn) -{ - return; -} -#endif - /** * of_detach_node - "Unplug" a node from the device tree. * @@ -1783,7 +1952,7 @@ int of_detach_node(struct device_node *np) of_node_set_flag(np, OF_DETACHED); raw_spin_unlock_irqrestore(&devtree_lock, flags); - of_remove_proc_dt_entry(np); + of_node_remove(np); return rc; } #endif /* defined(CONFIG_OF_DYNAMIC) */ @@ -1819,9 +1988,9 @@ void of_alias_scan(void * (*dt_alloc)(u64 size, u64 align)) of_chosen = of_find_node_by_path("/chosen@0"); if (of_chosen) { - const char *name; - - name = of_get_property(of_chosen, "linux,stdout-path", NULL); + const char *name = of_get_property(of_chosen, "stdout-path", NULL); + if (!name) + name = of_get_property(of_chosen, "linux,stdout-path", NULL); if (name) of_stdout = of_find_node_by_path(name); } @@ -1982,3 +2151,154 @@ struct device_node *of_find_next_cache_node(const struct device_node *np) return NULL; } + +/** + * of_graph_parse_endpoint() - parse common endpoint node properties + * @node: pointer to endpoint device_node + * @endpoint: pointer to the OF endpoint data structure + * + * The caller should hold a reference to @node. + */ +int of_graph_parse_endpoint(const struct device_node *node, + struct of_endpoint *endpoint) +{ + struct device_node *port_node = of_get_parent(node); + + WARN_ONCE(!port_node, "%s(): endpoint %s has no parent node\n", + __func__, node->full_name); + + memset(endpoint, 0, sizeof(*endpoint)); + + endpoint->local_node = node; + /* + * It doesn't matter whether the two calls below succeed. + * If they don't then the default value 0 is used. + */ + of_property_read_u32(port_node, "reg", &endpoint->port); + of_property_read_u32(node, "reg", &endpoint->id); + + of_node_put(port_node); + + return 0; +} +EXPORT_SYMBOL(of_graph_parse_endpoint); + +/** + * of_graph_get_next_endpoint() - get next endpoint node + * @parent: pointer to the parent device node + * @prev: previous endpoint node, or NULL to get first + * + * Return: An 'endpoint' node pointer with refcount incremented. Refcount + * of the passed @prev node is not decremented, the caller have to use + * of_node_put() on it when done. + */ +struct device_node *of_graph_get_next_endpoint(const struct device_node *parent, + struct device_node *prev) +{ + struct device_node *endpoint; + struct device_node *port; + + if (!parent) + return NULL; + + /* + * Start by locating the port node. If no previous endpoint is specified + * search for the first port node, otherwise get the previous endpoint + * parent port node. + */ + if (!prev) { + struct device_node *node; + + node = of_get_child_by_name(parent, "ports"); + if (node) + parent = node; + + port = of_get_child_by_name(parent, "port"); + of_node_put(node); + + if (!port) { + pr_err("%s(): no port node found in %s\n", + __func__, parent->full_name); + return NULL; + } + } else { + port = of_get_parent(prev); + if (WARN_ONCE(!port, "%s(): endpoint %s has no parent node\n", + __func__, prev->full_name)) + return NULL; + + /* + * Avoid dropping prev node refcount to 0 when getting the next + * child below. + */ + of_node_get(prev); + } + + while (1) { + /* + * Now that we have a port node, get the next endpoint by + * getting the next child. If the previous endpoint is NULL this + * will return the first child. + */ + endpoint = of_get_next_child(port, prev); + if (endpoint) { + of_node_put(port); + return endpoint; + } + + /* No more endpoints under this port, try the next one. */ + prev = NULL; + + do { + port = of_get_next_child(parent, port); + if (!port) + return NULL; + } while (of_node_cmp(port->name, "port")); + } +} +EXPORT_SYMBOL(of_graph_get_next_endpoint); + +/** + * of_graph_get_remote_port_parent() - get remote port's parent node + * @node: pointer to a local endpoint device_node + * + * Return: Remote device node associated with remote endpoint node linked + * to @node. Use of_node_put() on it when done. + */ +struct device_node *of_graph_get_remote_port_parent( + const struct device_node *node) +{ + struct device_node *np; + unsigned int depth; + + /* Get remote endpoint node. */ + np = of_parse_phandle(node, "remote-endpoint", 0); + + /* Walk 3 levels up only if there is 'ports' node. */ + for (depth = 3; depth && np; depth--) { + np = of_get_next_parent(np); + if (depth == 2 && of_node_cmp(np->name, "ports")) + break; + } + return np; +} +EXPORT_SYMBOL(of_graph_get_remote_port_parent); + +/** + * of_graph_get_remote_port() - get remote port node + * @node: pointer to a local endpoint device_node + * + * Return: Remote port node associated with remote endpoint node linked + * to @node. Use of_node_put() on it when done. + */ +struct device_node *of_graph_get_remote_port(const struct device_node *node) +{ + struct device_node *np; + + /* Get remote endpoint node. */ + np = of_parse_phandle(node, "remote-endpoint", 0); + if (!np) + return NULL; + return of_get_next_parent(np); +} +EXPORT_SYMBOL(of_graph_get_remote_port); diff --git a/drivers/of/fdt.c b/drivers/of/fdt.c index 758b4f8..fa16a91 100644 --- a/drivers/of/fdt.c +++ b/drivers/of/fdt.c @@ -15,6 +15,8 @@ #include <linux/module.h> #include <linux/of.h> #include <linux/of_fdt.h> +#include <linux/of_reserved_mem.h> +#include <linux/sizes.h> #include <linux/string.h> #include <linux/errno.h> #include <linux/slab.h> @@ -202,6 +204,7 @@ static void * unflatten_dt_node(struct boot_param_header *blob, __alignof__(struct device_node)); if (allnextpp) { char *fn; + of_node_init(np); np->full_name = fn = ((char *)np) + sizeof(*np); if (new_format) { /* rebuild full path for new format */ @@ -232,7 +235,6 @@ static void * unflatten_dt_node(struct boot_param_header *blob, dad->next->sibling = np; dad->next = np; } - kref_init(&np->kref); } /* process properties */ while (1) { @@ -440,6 +442,129 @@ struct boot_param_header *initial_boot_params; #ifdef CONFIG_OF_EARLY_FLATTREE /** + * res_mem_reserve_reg() - reserve all memory described in 'reg' property + */ +static int __init __reserved_mem_reserve_reg(unsigned long node, + const char *uname) +{ + int t_len = (dt_root_addr_cells + dt_root_size_cells) * sizeof(__be32); + phys_addr_t base, size; + unsigned long len; + __be32 *prop; + int nomap, first = 1; + + prop = of_get_flat_dt_prop(node, "reg", &len); + if (!prop) + return -ENOENT; + + if (len && len % t_len != 0) { + pr_err("Reserved memory: invalid reg property in '%s', skipping node.\n", + uname); + return -EINVAL; + } + + nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL; + + while (len >= t_len) { + base = dt_mem_next_cell(dt_root_addr_cells, &prop); + size = dt_mem_next_cell(dt_root_size_cells, &prop); + + if (base && size && + early_init_dt_reserve_memory_arch(base, size, nomap) == 0) + pr_debug("Reserved memory: reserved region for node '%s': base %pa, size %ld MiB\n", + uname, &base, (unsigned long)size / SZ_1M); + else + pr_info("Reserved memory: failed to reserve memory for node '%s': base %pa, size %ld MiB\n", + uname, &base, (unsigned long)size / SZ_1M); + + len -= t_len; + if (first) { + fdt_reserved_mem_save_node(node, uname, base, size); + first = 0; + } + } + return 0; +} + +/** + * __reserved_mem_check_root() - check if #size-cells, #address-cells provided + * in /reserved-memory matches the values supported by the current implementation, + * also check if ranges property has been provided + */ +static int __reserved_mem_check_root(unsigned long node) +{ + __be32 *prop; + + prop = of_get_flat_dt_prop(node, "#size-cells", NULL); + if (!prop || be32_to_cpup(prop) != dt_root_size_cells) + return -EINVAL; + + prop = of_get_flat_dt_prop(node, "#address-cells", NULL); + if (!prop || be32_to_cpup(prop) != dt_root_addr_cells) + return -EINVAL; + + prop = of_get_flat_dt_prop(node, "ranges", NULL); + if (!prop) + return -EINVAL; + return 0; +} + +/** + * fdt_scan_reserved_mem() - scan a single FDT node for reserved memory + */ +static int __init __fdt_scan_reserved_mem(unsigned long node, const char *uname, + int depth, void *data) +{ + static int found; + const char *status; + int err; + + if (!found && depth == 1 && strcmp(uname, "reserved-memory") == 0) { + if (__reserved_mem_check_root(node) != 0) { + pr_err("Reserved memory: unsupported node format, ignoring\n"); + /* break scan */ + return 1; + } + found = 1; + /* scan next node */ + return 0; + } else if (!found) { + /* scan next node */ + return 0; + } else if (found && depth < 2) { + /* scanning of /reserved-memory has been finished */ + return 1; + } + + status = of_get_flat_dt_prop(node, "status", NULL); + if (status && strcmp(status, "okay") != 0 && strcmp(status, "ok") != 0) + return 0; + + err = __reserved_mem_reserve_reg(node, uname); + if (err == -ENOENT && of_get_flat_dt_prop(node, "size", NULL)) + fdt_reserved_mem_save_node(node, uname, 0, 0); + + /* scan next node */ + return 0; +} + +/** + * early_init_fdt_scan_reserved_mem() - create reserved memory regions + * + * This function grabs memory from early allocator for device exclusive use + * defined in device tree structures. It should be called by arch specific code + * once the early allocator (i.e. memblock) has been fully activated. + */ +void __init early_init_fdt_scan_reserved_mem(void) +{ + if (!initial_boot_params) + return; + + of_scan_flat_dt(__fdt_scan_reserved_mem, NULL); + fdt_init_reserved_mem(); +} + +/** * of_scan_flat_dt - scan flattened tree blob and call callback on each. * @it: callback function * @data: context data pointer @@ -856,6 +981,16 @@ void __init __weak early_init_dt_add_memory_arch(u64 base, u64 size) memblock_add(base, size); } +int __init __weak early_init_dt_reserve_memory_arch(phys_addr_t base, + phys_addr_t size, bool nomap) +{ + if (memblock_is_region_reserved(base, size)) + return -EBUSY; + if (nomap) + return memblock_remove(base, size); + return memblock_reserve(base, size); +} + /* * called from unflatten_device_tree() to bootstrap devicetree itself * Architectures can override this definition if memblock isn't used @@ -864,6 +999,14 @@ void * __init __weak early_init_dt_alloc_memory_arch(u64 size, u64 align) { return __va(memblock_alloc(size, align)); } +#else +int __init __weak early_init_dt_reserve_memory_arch(phys_addr_t base, + phys_addr_t size, bool nomap) +{ + pr_err("Reserved memory not supported, ignoring range 0x%llx - 0x%llx%s\n", + base, size, nomap ? " (nomap)" : ""); + return -ENOSYS; +} #endif bool __init early_init_dt_scan(void *params) diff --git a/drivers/of/of_mdio.c b/drivers/of/of_mdio.c index 5b3c24f..9a95831 100644 --- a/drivers/of/of_mdio.c +++ b/drivers/of/of_mdio.c @@ -43,6 +43,23 @@ static void of_set_phy_supported(struct phy_device *phydev, u32 max_speed) } } +/* Extract the clause 22 phy ID from the compatible string of the form + * ethernet-phy-idAAAA.BBBB */ +static int of_get_phy_id(struct device_node *device, u32 *phy_id) +{ + struct property *prop; + const char *cp; + unsigned int upper, lower; + + of_property_for_each_string(device, "compatible", prop, cp) { + if (sscanf(cp, "ethernet-phy-id%4x.%4x", &upper, &lower) == 2) { + *phy_id = ((upper & 0xFFFF) << 16) | (lower & 0xFFFF); + return 0; + } + } + return -EINVAL; +} + static int of_mdiobus_register_phy(struct mii_bus *mdio, struct device_node *child, u32 addr) { @@ -50,11 +67,15 @@ static int of_mdiobus_register_phy(struct mii_bus *mdio, struct device_node *chi bool is_c45; int rc; u32 max_speed = 0; + u32 phy_id; is_c45 = of_device_is_compatible(child, "ethernet-phy-ieee802.3-c45"); - phy = get_phy_device(mdio, addr, is_c45); + if (!is_c45 && !of_get_phy_id(child, &phy_id)) + phy = phy_device_create(mdio, addr, phy_id, 0, NULL); + else + phy = get_phy_device(mdio, addr, is_c45); if (!phy || IS_ERR(phy)) return 1; diff --git a/drivers/of/of_net.c b/drivers/of/of_net.c index a208a45..73e1418 100644 --- a/drivers/of/of_net.c +++ b/drivers/of/of_net.c @@ -12,33 +12,12 @@ #include <linux/export.h> /** - * It maps 'enum phy_interface_t' found in include/linux/phy.h - * into the device tree binding of 'phy-mode', so that Ethernet - * device driver can get phy interface from device tree. - */ -static const char *phy_modes[] = { - [PHY_INTERFACE_MODE_NA] = "", - [PHY_INTERFACE_MODE_MII] = "mii", - [PHY_INTERFACE_MODE_GMII] = "gmii", - [PHY_INTERFACE_MODE_SGMII] = "sgmii", - [PHY_INTERFACE_MODE_TBI] = "tbi", - [PHY_INTERFACE_MODE_REVMII] = "rev-mii", - [PHY_INTERFACE_MODE_RMII] = "rmii", - [PHY_INTERFACE_MODE_RGMII] = "rgmii", - [PHY_INTERFACE_MODE_RGMII_ID] = "rgmii-id", - [PHY_INTERFACE_MODE_RGMII_RXID] = "rgmii-rxid", - [PHY_INTERFACE_MODE_RGMII_TXID] = "rgmii-txid", - [PHY_INTERFACE_MODE_RTBI] = "rtbi", - [PHY_INTERFACE_MODE_SMII] = "smii", - [PHY_INTERFACE_MODE_XGMII] = "xgmii", -}; - -/** * of_get_phy_mode - Get phy mode for given device_node * @np: Pointer to the given device_node * - * The function gets phy interface string from property 'phy-mode', - * and return its index in phy_modes table, or errno in error case. + * The function gets phy interface string from property 'phy-mode' or + * 'phy-connection-type', and return its index in phy_modes table, or errno in + * error case. */ int of_get_phy_mode(struct device_node *np) { @@ -47,10 +26,12 @@ int of_get_phy_mode(struct device_node *np) err = of_property_read_string(np, "phy-mode", &pm); if (err < 0) + err = of_property_read_string(np, "phy-connection-type", &pm); + if (err < 0) return err; - for (i = 0; i < ARRAY_SIZE(phy_modes); i++) - if (!strcasecmp(pm, phy_modes[i])) + for (i = 0; i < PHY_INTERFACE_MODE_MAX; i++) + if (!strcasecmp(pm, phy_modes(i))) return i; return -ENODEV; diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c new file mode 100644 index 0000000..daaaf93 --- /dev/null +++ b/drivers/of/of_reserved_mem.c @@ -0,0 +1,217 @@ +/* + * Device tree based initialization code for reserved memory. + * + * Copyright (c) 2013, The Linux Foundation. All Rights Reserved. + * Copyright (c) 2013,2014 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * Author: Marek Szyprowski <m.szyprowski@samsung.com> + * Author: Josh Cartwright <joshc@codeaurora.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License or (at your optional) any later version of the license. + */ + +#include <linux/err.h> +#include <linux/of.h> +#include <linux/of_fdt.h> +#include <linux/of_platform.h> +#include <linux/mm.h> +#include <linux/sizes.h> +#include <linux/of_reserved_mem.h> + +#define MAX_RESERVED_REGIONS 16 +static struct reserved_mem reserved_mem[MAX_RESERVED_REGIONS]; +static int reserved_mem_count; + +#if defined(CONFIG_HAVE_MEMBLOCK) +#include <linux/memblock.h> +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) +{ + /* + * We use __memblock_alloc_base() because memblock_alloc_base() + * panic()s on allocation failure. + */ + phys_addr_t base = __memblock_alloc_base(size, align, end); + if (!base) + return -ENOMEM; + + /* + * Check if the allocated region fits in to start..end window + */ + if (base < start) { + memblock_free(base, size); + return -ENOMEM; + } + + *res_base = base; + if (nomap) + return memblock_remove(base, size); + return 0; +} +#else +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) +{ + pr_err("Reserved memory not supported, ignoring region 0x%llx%s\n", + size, nomap ? " (nomap)" : ""); + return -ENOSYS; +} +#endif + +/** + * res_mem_save_node() - save fdt node for second pass initialization + */ +void __init fdt_reserved_mem_save_node(unsigned long node, const char *uname, + phys_addr_t base, phys_addr_t size) +{ + struct reserved_mem *rmem = &reserved_mem[reserved_mem_count]; + + if (reserved_mem_count == ARRAY_SIZE(reserved_mem)) { + pr_err("Reserved memory: not enough space all defined regions.\n"); + return; + } + + rmem->fdt_node = node; + rmem->name = uname; + rmem->base = base; + rmem->size = size; + + reserved_mem_count++; + return; +} + +/** + * res_mem_alloc_size() - allocate reserved memory described by 'size', 'align' + * and 'alloc-ranges' properties + */ +static int __init __reserved_mem_alloc_size(unsigned long node, + const char *uname, phys_addr_t *res_base, phys_addr_t *res_size) +{ + int t_len = (dt_root_addr_cells + dt_root_size_cells) * sizeof(__be32); + phys_addr_t start = 0, end = 0; + phys_addr_t base = 0, align = 0, size; + unsigned long len; + __be32 *prop; + int nomap; + int ret; + + prop = of_get_flat_dt_prop(node, "size", &len); + if (!prop) + return -EINVAL; + + if (len != dt_root_size_cells * sizeof(__be32)) { + pr_err("Reserved memory: invalid size property in '%s' node.\n", + uname); + return -EINVAL; + } + size = dt_mem_next_cell(dt_root_size_cells, &prop); + + nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL; + + prop = of_get_flat_dt_prop(node, "alignment", &len); + if (prop) { + if (len != dt_root_addr_cells * sizeof(__be32)) { + pr_err("Reserved memory: invalid alignment property in '%s' node.\n", + uname); + return -EINVAL; + } + align = dt_mem_next_cell(dt_root_addr_cells, &prop); + } + + prop = of_get_flat_dt_prop(node, "alloc-ranges", &len); + if (prop) { + + if (len % t_len != 0) { + pr_err("Reserved memory: invalid alloc-ranges property in '%s', skipping node.\n", + uname); + return -EINVAL; + } + + base = 0; + + while (len > 0) { + start = dt_mem_next_cell(dt_root_addr_cells, &prop); + end = start + dt_mem_next_cell(dt_root_size_cells, + &prop); + + ret = early_init_dt_alloc_reserved_memory_arch(size, + align, start, end, nomap, &base); + if (ret == 0) { + pr_debug("Reserved memory: allocated memory for '%s' node: base %pa, size %ld MiB\n", + uname, &base, + (unsigned long)size / SZ_1M); + break; + } + len -= t_len; + } + + } else { + ret = early_init_dt_alloc_reserved_memory_arch(size, align, + 0, 0, nomap, &base); + if (ret == 0) + pr_debug("Reserved memory: allocated memory for '%s' node: base %pa, size %ld MiB\n", + uname, &base, (unsigned long)size / SZ_1M); + } + + if (base == 0) { + pr_info("Reserved memory: failed to allocate memory for node '%s'\n", + uname); + return -ENOMEM; + } + + *res_base = base; + *res_size = size; + + return 0; +} + +static const struct of_device_id __rmem_of_table_sentinel + __used __section(__reservedmem_of_table_end); + +/** + * res_mem_init_node() - call region specific reserved memory init code + */ +static int __init __reserved_mem_init_node(struct reserved_mem *rmem) +{ + extern const struct of_device_id __reservedmem_of_table[]; + const struct of_device_id *i; + + for (i = __reservedmem_of_table; i < &__rmem_of_table_sentinel; i++) { + reservedmem_of_init_fn initfn = i->data; + const char *compat = i->compatible; + + if (!of_flat_dt_is_compatible(rmem->fdt_node, compat)) + continue; + + if (initfn(rmem, rmem->fdt_node, rmem->name) == 0) { + pr_info("Reserved memory: initialized node %s, compatible id %s\n", + rmem->name, compat); + return 0; + } + } + return -ENOENT; +} + +/** + * fdt_init_reserved_mem - allocate and init all saved reserved memory regions + */ +void __init fdt_init_reserved_mem(void) +{ + int i; + for (i = 0; i < reserved_mem_count; i++) { + struct reserved_mem *rmem = &reserved_mem[i]; + unsigned long node = rmem->fdt_node; + int err = 0; + + if (rmem->size == 0) + err = __reserved_mem_alloc_size(node, rmem->name, + &rmem->base, &rmem->size); + if (err == 0) + __reserved_mem_init_node(rmem); + } +} diff --git a/drivers/of/pdt.c b/drivers/of/pdt.c index 7b66673..36b4035 100644 --- a/drivers/of/pdt.c +++ b/drivers/of/pdt.c @@ -176,11 +176,10 @@ static struct device_node * __init of_pdt_create_node(phandle node, return NULL; dp = prom_early_alloc(sizeof(*dp)); + of_node_init(dp); of_pdt_incr_unique_id(dp); dp->parent = parent; - kref_init(&dp->kref); - dp->name = of_pdt_get_one_property(node, "name"); dp->type = of_pdt_get_one_property(node, "device_type"); dp->phandle = node; diff --git a/drivers/of/selftest.c b/drivers/of/selftest.c index 6643d19..ae44500 100644 --- a/drivers/of/selftest.c +++ b/drivers/of/selftest.c @@ -30,6 +30,67 @@ static struct selftest_results { } \ } +static void __init of_selftest_dynamic(void) +{ + struct device_node *np; + struct property *prop; + + np = of_find_node_by_path("/testcase-data"); + if (!np) { + pr_err("missing testcase data\n"); + return; + } + + /* Array of 4 properties for the purpose of testing */ + prop = kzalloc(sizeof(*prop) * 4, GFP_KERNEL); + if (!prop) { + selftest(0, "kzalloc() failed\n"); + return; + } + + /* Add a new property - should pass*/ + prop->name = "new-property"; + prop->value = "new-property-data"; + prop->length = strlen(prop->value); + selftest(of_add_property(np, prop) == 0, "Adding a new property failed\n"); + + /* Try to add an existing property - should fail */ + prop++; + prop->name = "new-property"; + prop->value = "new-property-data-should-fail"; + prop->length = strlen(prop->value); + selftest(of_add_property(np, prop) != 0, + "Adding an existing property should have failed\n"); + + /* Try to modify an existing property - should pass */ + prop->value = "modify-property-data-should-pass"; + prop->length = strlen(prop->value); + selftest(of_update_property(np, prop) == 0, + "Updating an existing property should have passed\n"); + + /* Try to modify non-existent property - should pass*/ + prop++; + prop->name = "modify-property"; + prop->value = "modify-missing-property-data-should-pass"; + prop->length = strlen(prop->value); + selftest(of_update_property(np, prop) == 0, + "Updating a missing property should have passed\n"); + + /* Remove property - should pass */ + selftest(of_remove_property(np, prop) == 0, + "Removing a property should have passed\n"); + + /* Adding very large property - should pass */ + prop++; + prop->name = "large-property-PAGE_SIZEx8"; + prop->length = PAGE_SIZE * 8; + prop->value = kzalloc(prop->length, GFP_KERNEL); + selftest(prop->value != NULL, "Unable to allocate large buffer\n"); + if (prop->value) + selftest(of_add_property(np, prop) == 0, + "Adding a large property should have passed\n"); +} + static void __init of_selftest_parse_phandle_with_args(void) { struct device_node *np; @@ -378,6 +439,7 @@ static int __init of_selftest(void) of_node_put(np); pr_info("start of selftest - you will see error messages\n"); + of_selftest_dynamic(); of_selftest_parse_phandle_with_args(); of_selftest_property_match_string(); of_selftest_parse_interrupts(); diff --git a/drivers/of/testcase-data/tests-phandle.dtsi b/drivers/of/testcase-data/tests-phandle.dtsi index 0007d3c..788a4c2 100644 --- a/drivers/of/testcase-data/tests-phandle.dtsi +++ b/drivers/of/testcase-data/tests-phandle.dtsi @@ -1,6 +1,9 @@ / { testcase-data { + security-password = "password"; + duplicate-name = "duplicate"; + duplicate-name { }; phandle-tests { provider0: provider0 { #phandle-cells = <0>; |