diff options
Diffstat (limited to 'drivers/of/base.c')
| -rw-r--r-- | drivers/of/base.c | 318 | 
1 files changed, 312 insertions, 6 deletions
diff --git a/drivers/of/base.c b/drivers/of/base.c index e6627b2320f1..cb96888d1427 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -20,8 +20,10 @@  #include <linux/module.h>  #include <linux/of.h>  #include <linux/spinlock.h> +#include <linux/proc_fs.h>  struct device_node *allnodes; +struct device_node *of_chosen;  /* use when traversing tree through the allnext, child, sibling,   * or parent members of struct device_node. @@ -37,7 +39,7 @@ int of_n_addr_cells(struct device_node *np)  			np = np->parent;  		ip = of_get_property(np, "#address-cells", NULL);  		if (ip) -			return *ip; +			return be32_to_cpup(ip);  	} while (np->parent);  	/* No #address-cells property for the root node */  	return OF_ROOT_NODE_ADDR_CELLS_DEFAULT; @@ -53,13 +55,88 @@ int of_n_size_cells(struct device_node *np)  			np = np->parent;  		ip = of_get_property(np, "#size-cells", NULL);  		if (ip) -			return *ip; +			return be32_to_cpup(ip);  	} while (np->parent);  	/* No #size-cells property for the root node */  	return OF_ROOT_NODE_SIZE_CELLS_DEFAULT;  }  EXPORT_SYMBOL(of_n_size_cells); +#if !defined(CONFIG_SPARC)   /* SPARC doesn't do ref counting (yet) */ +/** + *	of_node_get - Increment refcount of a node + *	@node:	Node to inc refcount, NULL is supported to + *		simplify writing of callers + * + *	Returns node. + */ +struct device_node *of_node_get(struct device_node *node) +{ +	if (node) +		kref_get(&node->kref); +	return node; +} +EXPORT_SYMBOL(of_node_get); + +static inline struct device_node *kref_to_device_node(struct kref *kref) +{ +	return container_of(kref, struct device_node, kref); +} + +/** + *	of_node_release - release a dynamically allocated node + *	@kref:  kref element of the node to be released + * + *	In of_node_put() this function is passed to kref_put() + *	as the destructor. + */ +static void of_node_release(struct kref *kref) +{ +	struct device_node *node = kref_to_device_node(kref); +	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; +	} + +	if (!of_node_check_flag(node, OF_DYNAMIC)) +		return; + +	while (prop) { +		struct property *next = prop->next; +		kfree(prop->name); +		kfree(prop->value); +		kfree(prop); +		prop = next; + +		if (!prop) { +			prop = node->deadprops; +			node->deadprops = NULL; +		} +	} +	kfree(node->full_name); +	kfree(node->data); +	kfree(node); +} + +/** + *	of_node_put - Decrement refcount of a node + *	@node:	Node to dec refcount, NULL is supported to + *		simplify writing of callers + * + */ +void of_node_put(struct device_node *node) +{ +	if (node) +		kref_put(&node->kref, of_node_release); +} +EXPORT_SYMBOL(of_node_put); +#endif /* !CONFIG_SPARC */ +  struct property *of_find_property(const struct device_node *np,  				  const char *name,  				  int *lenp) @@ -144,6 +221,27 @@ int of_device_is_compatible(const struct device_node *device,  EXPORT_SYMBOL(of_device_is_compatible);  /** + * of_machine_is_compatible - Test root of device tree for a given compatible value + * @compat: compatible string to look for in root node's compatible property. + * + * Returns true if the root node has the given value in its + * compatible property. + */ +int of_machine_is_compatible(const char *compat) +{ +	struct device_node *root; +	int rc = 0; + +	root = of_find_node_by_path("/"); +	if (root) { +		rc = of_device_is_compatible(root, compat); +		of_node_put(root); +	} +	return rc; +} +EXPORT_SYMBOL(of_machine_is_compatible); + +/**   *  of_device_is_available - check if a device is available for use   *   *  @device: Node to check for availability @@ -519,6 +617,27 @@ int of_modalias_node(struct device_node *node, char *modalias, int len)  EXPORT_SYMBOL_GPL(of_modalias_node);  /** + * of_find_node_by_phandle - Find a node given a phandle + * @handle:	phandle of the node to find + * + * Returns a node pointer with refcount incremented, use + * of_node_put() on it when done. + */ +struct device_node *of_find_node_by_phandle(phandle handle) +{ +	struct device_node *np; + +	read_lock(&devtree_lock); +	for (np = allnodes; np; np = np->allnext) +		if (np->phandle == handle) +			break; +	of_node_get(np); +	read_unlock(&devtree_lock); +	return np; +} +EXPORT_SYMBOL(of_find_node_by_phandle); + +/**   * of_parse_phandle - Resolve a phandle property to a device_node pointer   * @np: Pointer to device node holding phandle property   * @phandle_name: Name of property holding a phandle value @@ -578,8 +697,8 @@ int of_parse_phandles_with_args(struct device_node *np, const char *list_name,  				const void **out_args)  {  	int ret = -EINVAL; -	const u32 *list; -	const u32 *list_end; +	const __be32 *list; +	const __be32 *list_end;  	int size;  	int cur_index = 0;  	struct device_node *node = NULL; @@ -593,7 +712,7 @@ int of_parse_phandles_with_args(struct device_node *np, const char *list_name,  	list_end = list + size / sizeof(*list);  	while (list < list_end) { -		const u32 *cells; +		const __be32 *cells;  		const phandle *phandle;  		phandle = list++; @@ -617,7 +736,7 @@ int of_parse_phandles_with_args(struct device_node *np, const char *list_name,  			goto err1;  		} -		list += *cells; +		list += be32_to_cpup(cells);  		if (list > list_end) {  			pr_debug("%s: insufficient arguments length\n",  				 np->full_name); @@ -658,3 +777,190 @@ err0:  	return ret;  }  EXPORT_SYMBOL(of_parse_phandles_with_args); + +/** + * prom_add_property - Add a property to a node + */ +int prom_add_property(struct device_node *np, struct property *prop) +{ +	struct property **next; +	unsigned long flags; + +	prop->next = NULL; +	write_lock_irqsave(&devtree_lock, flags); +	next = &np->properties; +	while (*next) { +		if (strcmp(prop->name, (*next)->name) == 0) { +			/* duplicate ! don't insert it */ +			write_unlock_irqrestore(&devtree_lock, flags); +			return -1; +		} +		next = &(*next)->next; +	} +	*next = prop; +	write_unlock_irqrestore(&devtree_lock, flags); + +#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 */ + +	return 0; +} + +/** + * prom_remove_property - Remove a property from a node. + * + * Note that we don't actually remove it, since we have given out + * who-knows-how-many pointers to the data using get-property. + * Instead we just move the property to the "dead properties" + * list, so it won't be found any more. + */ +int prom_remove_property(struct device_node *np, struct property *prop) +{ +	struct property **next; +	unsigned long flags; +	int found = 0; + +	write_lock_irqsave(&devtree_lock, flags); +	next = &np->properties; +	while (*next) { +		if (*next == prop) { +			/* found the node */ +			*next = prop->next; +			prop->next = np->deadprops; +			np->deadprops = prop; +			found = 1; +			break; +		} +		next = &(*next)->next; +	} +	write_unlock_irqrestore(&devtree_lock, flags); + +	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 */ + +	return 0; +} + +/* + * prom_update_property - Update a property in a node. + * + * Note that we don't actually remove it, since we have given out + * who-knows-how-many pointers to the data using get-property. + * Instead we just move the property to the "dead properties" list, + * and add the new property to the property list + */ +int prom_update_property(struct device_node *np, +			 struct property *newprop, +			 struct property *oldprop) +{ +	struct property **next; +	unsigned long flags; +	int found = 0; + +	write_lock_irqsave(&devtree_lock, flags); +	next = &np->properties; +	while (*next) { +		if (*next == oldprop) { +			/* found the node */ +			newprop->next = oldprop->next; +			*next = newprop; +			oldprop->next = np->deadprops; +			np->deadprops = oldprop; +			found = 1; +			break; +		} +		next = &(*next)->next; +	} +	write_unlock_irqrestore(&devtree_lock, flags); + +	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; +} + +#if defined(CONFIG_OF_DYNAMIC) +/* + * Support for dynamic device trees. + * + * On some platforms, the device tree can be manipulated at runtime. + * The routines in this section support adding, removing and changing + * device tree nodes. + */ + +/** + * of_attach_node - Plug a device node into the tree and global list. + */ +void of_attach_node(struct device_node *np) +{ +	unsigned long flags; + +	write_lock_irqsave(&devtree_lock, flags); +	np->sibling = np->parent->child; +	np->allnext = allnodes; +	np->parent->child = np; +	allnodes = np; +	write_unlock_irqrestore(&devtree_lock, flags); +} + +/** + * of_detach_node - "Unplug" a node from the device tree. + * + * The caller must hold a reference to the node.  The memory associated with + * the node is not freed until its refcount goes to zero. + */ +void of_detach_node(struct device_node *np) +{ +	struct device_node *parent; +	unsigned long flags; + +	write_lock_irqsave(&devtree_lock, flags); + +	parent = np->parent; +	if (!parent) +		goto out_unlock; + +	if (allnodes == np) +		allnodes = np->allnext; +	else { +		struct device_node *prev; +		for (prev = allnodes; +		     prev->allnext != np; +		     prev = prev->allnext) +			; +		prev->allnext = np->allnext; +	} + +	if (parent->child == np) +		parent->child = np->sibling; +	else { +		struct device_node *prevsib; +		for (prevsib = np->parent->child; +		     prevsib->sibling != np; +		     prevsib = prevsib->sibling) +			; +		prevsib->sibling = np->sibling; +	} + +	of_node_set_flag(np, OF_DETACHED); + +out_unlock: +	write_unlock_irqrestore(&devtree_lock, flags); +} +#endif /* defined(CONFIG_OF_DYNAMIC) */ +  | 
