Linux Audio

Check our new training course

Loading...
v4.6
 
  1/*
  2 * Functions for dealing with DT resolution
  3 *
  4 * Copyright (C) 2012 Pantelis Antoniou <panto@antoniou-consulting.com>
  5 * Copyright (C) 2012 Texas Instruments Inc.
  6 *
  7 * This program is free software; you can redistribute it and/or
  8 * modify it under the terms of the GNU General Public License
  9 * version 2 as published by the Free Software Foundation.
 10 */
 11
 
 
 12#include <linux/kernel.h>
 13#include <linux/module.h>
 14#include <linux/of.h>
 15#include <linux/of_device.h>
 16#include <linux/string.h>
 17#include <linux/ctype.h>
 18#include <linux/errno.h>
 19#include <linux/string.h>
 20#include <linux/slab.h>
 21
 22/* illegal phandle value (set when unresolved) */
 23#define OF_PHANDLE_ILLEGAL	0xdeadbeef
 24
 25/**
 26 * Find a node with the give full name by recursively following any of
 27 * the child node links.
 28 */
 29static struct device_node *__of_find_node_by_full_name(struct device_node *node,
 30		const char *full_name)
 31{
 32	struct device_node *child, *found;
 33
 34	if (node == NULL)
 35		return NULL;
 36
 37	/* check */
 38	if (of_node_cmp(node->full_name, full_name) == 0)
 39		return of_node_get(node);
 40
 41	for_each_child_of_node(node, child) {
 42		found = __of_find_node_by_full_name(child, full_name);
 43		if (found != NULL) {
 44			of_node_put(child);
 45			return found;
 46		}
 47	}
 48
 49	return NULL;
 50}
 51
 52/*
 53 * Find live tree's maximum phandle value.
 54 */
 55static phandle of_get_tree_max_phandle(void)
 56{
 57	struct device_node *node;
 58	phandle phandle;
 59	unsigned long flags;
 60
 61	/* now search recursively */
 62	raw_spin_lock_irqsave(&devtree_lock, flags);
 63	phandle = 0;
 64	for_each_of_allnodes(node) {
 65		if (node->phandle != OF_PHANDLE_ILLEGAL &&
 66				node->phandle > phandle)
 67			phandle = node->phandle;
 68	}
 69	raw_spin_unlock_irqrestore(&devtree_lock, flags);
 70
 71	return phandle;
 72}
 73
 74/*
 75 * Adjust a subtree's phandle values by a given delta.
 76 * Makes sure not to just adjust the device node's phandle value,
 77 * but modify the phandle properties values as well.
 78 */
 79static void __of_adjust_tree_phandles(struct device_node *node,
 80		int phandle_delta)
 81{
 82	struct device_node *child;
 83	struct property *prop;
 84	phandle phandle;
 85
 86	/* first adjust the node's phandle direct value */
 87	if (node->phandle != 0 && node->phandle != OF_PHANDLE_ILLEGAL)
 88		node->phandle += phandle_delta;
 89
 90	/* now adjust phandle & linux,phandle values */
 91	for_each_property_of_node(node, prop) {
 92
 93		/* only look for these two */
 94		if (of_prop_cmp(prop->name, "phandle") != 0 &&
 95		    of_prop_cmp(prop->name, "linux,phandle") != 0)
 96			continue;
 97
 98		/* must be big enough */
 99		if (prop->length < 4)
100			continue;
101
102		/* read phandle value */
103		phandle = be32_to_cpup(prop->value);
104		if (phandle == OF_PHANDLE_ILLEGAL)	/* unresolved */
105			continue;
106
107		/* adjust */
108		*(uint32_t *)prop->value = cpu_to_be32(node->phandle);
109	}
110
111	/* now do the children recursively */
112	for_each_child_of_node(node, child)
113		__of_adjust_tree_phandles(child, phandle_delta);
114}
115
116static int __of_adjust_phandle_ref(struct device_node *node,
117		struct property *rprop, int value)
118{
119	phandle phandle;
120	struct device_node *refnode;
121	struct property *sprop;
122	char *propval, *propcur, *propend, *nodestr, *propstr, *s;
123	int offset, propcurlen;
124	int err = 0;
125
126	/* make a copy */
127	propval = kmalloc(rprop->length, GFP_KERNEL);
128	if (!propval) {
129		pr_err("%s: Could not copy value of '%s'\n",
130				__func__, rprop->name);
131		return -ENOMEM;
132	}
133	memcpy(propval, rprop->value, rprop->length);
134
135	propend = propval + rprop->length;
136	for (propcur = propval; propcur < propend; propcur += propcurlen + 1) {
137		propcurlen = strlen(propcur);
 
138
139		nodestr = propcur;
140		s = strchr(propcur, ':');
141		if (!s) {
142			pr_err("%s: Illegal symbol entry '%s' (1)\n",
143				__func__, propcur);
144			err = -EINVAL;
145			goto err_fail;
146		}
147		*s++ = '\0';
148
149		propstr = s;
150		s = strchr(s, ':');
151		if (!s) {
152			pr_err("%s: Illegal symbol entry '%s' (2)\n",
153				__func__, (char *)rprop->value);
154			err = -EINVAL;
155			goto err_fail;
156		}
157
158		*s++ = '\0';
 
159		err = kstrtoint(s, 10, &offset);
160		if (err != 0) {
161			pr_err("%s: Could get offset '%s'\n",
162				__func__, (char *)rprop->value);
163			goto err_fail;
164		}
165
166		/* look into the resolve node for the full path */
167		refnode = __of_find_node_by_full_name(node, nodestr);
168		if (!refnode) {
169			pr_warn("%s: Could not find refnode '%s'\n",
170				__func__, (char *)rprop->value);
171			continue;
172		}
173
174		/* now find the property */
175		for_each_property_of_node(refnode, sprop) {
176			if (of_prop_cmp(sprop->name, propstr) == 0)
177				break;
178		}
179		of_node_put(refnode);
180
181		if (!sprop) {
182			pr_err("%s: Could not find property '%s'\n",
183				__func__, (char *)rprop->value);
184			err = -ENOENT;
185			goto err_fail;
186		}
187
188		phandle = value;
189		*(__be32 *)(sprop->value + offset) = cpu_to_be32(phandle);
 
 
 
 
190	}
191
192err_fail:
193	kfree(propval);
194	return err;
195}
196
197/* compare nodes taking into account that 'name' strips out the @ part */
198static int __of_node_name_cmp(const struct device_node *dn1,
199		const struct device_node *dn2)
200{
201	const char *n1 = strrchr(dn1->full_name, '/') ? : "/";
202	const char *n2 = strrchr(dn2->full_name, '/') ? : "/";
203
204	return of_node_cmp(n1, n2);
205}
206
207/*
208 * Adjust the local phandle references by the given phandle delta.
209 * Assumes the existances of a __local_fixups__ node at the root.
210 * Assumes that __of_verify_tree_phandle_references has been called.
211 * Does not take any devtree locks so make sure you call this on a tree
212 * which is at the detached state.
 
 
 
 
213 */
214static int __of_adjust_tree_phandle_references(struct device_node *node,
215		struct device_node *target, int phandle_delta)
216{
217	struct device_node *child, *childtarget;
218	struct property *rprop, *sprop;
219	int err, i, count;
220	unsigned int off;
221	phandle phandle;
222
223	if (node == NULL)
224		return 0;
225
226	for_each_property_of_node(node, rprop) {
227
228		/* skip properties added automatically */
229		if (of_prop_cmp(rprop->name, "name") == 0 ||
230		    of_prop_cmp(rprop->name, "phandle") == 0 ||
231		    of_prop_cmp(rprop->name, "linux,phandle") == 0)
232			continue;
233
234		if ((rprop->length % 4) != 0 || rprop->length == 0) {
235			pr_err("%s: Illegal property (size) '%s' @%s\n",
236					__func__, rprop->name, node->full_name);
237			return -EINVAL;
238		}
239		count = rprop->length / sizeof(__be32);
240
241		/* now find the target property */
242		for_each_property_of_node(target, sprop) {
243			if (of_prop_cmp(sprop->name, rprop->name) == 0)
244				break;
245		}
246
247		if (sprop == NULL) {
248			pr_err("%s: Could not find target property '%s' @%s\n",
249					__func__, rprop->name, node->full_name);
250			return -EINVAL;
251		}
252
253		for (i = 0; i < count; i++) {
254			off = be32_to_cpu(((__be32 *)rprop->value)[i]);
255			/* make sure the offset doesn't overstep (even wrap) */
256			if (off >= sprop->length ||
257					(off + 4) > sprop->length) {
258				pr_err("%s: Illegal property '%s' @%s\n",
259						__func__, rprop->name,
260						node->full_name);
261				return -EINVAL;
262			}
263
264			if (phandle_delta) {
265				/* adjust */
266				phandle = be32_to_cpu(*(__be32 *)(sprop->value + off));
267				phandle += phandle_delta;
268				*(__be32 *)(sprop->value + off) = cpu_to_be32(phandle);
269			}
270		}
271	}
272
273	for_each_child_of_node(node, child) {
274
275		for_each_child_of_node(target, childtarget)
276			if (__of_node_name_cmp(child, childtarget) == 0)
 
 
 
 
 
 
 
 
277				break;
 
278
279		if (!childtarget) {
280			pr_err("%s: Could not find target child '%s' @%s\n",
281					__func__, child->name, node->full_name);
282			return -EINVAL;
283		}
284
285		err = __of_adjust_tree_phandle_references(child, childtarget,
286				phandle_delta);
287		if (err != 0)
 
288			return err;
 
289	}
290
291	return 0;
292}
293
294/**
295 * of_resolve	- Resolve the given node against the live tree.
 
 
 
 
 
 
 
 
 
 
 
 
 
296 *
297 * @resolve:	Node to resolve
 
 
 
 
 
298 *
299 * Perform dynamic Device Tree resolution against the live tree
300 * to the given node to resolve. This depends on the live tree
301 * having a __symbols__ node, and the resolve node the __fixups__ &
302 * __local_fixups__ nodes (if needed).
303 * The result of the operation is a resolve node that it's contents
304 * are fit to be inserted or operate upon the live tree.
305 * Returns 0 on success or a negative error value on error.
 
 
306 */
307int of_resolve_phandles(struct device_node *resolve)
308{
309	struct device_node *child, *childroot, *refnode;
310	struct device_node *root_sym, *resolve_sym, *resolve_fix;
311	struct property *rprop;
312	const char *refpath;
313	phandle phandle, phandle_delta;
314	int err;
315
316	/* the resolve node must exist, and be detached */
317	if (!resolve || !of_node_check_flag(resolve, OF_DETACHED))
318		return -EINVAL;
319
320	/* first we need to adjust the phandles */
321	phandle_delta = of_get_tree_max_phandle() + 1;
322	__of_adjust_tree_phandles(resolve, phandle_delta);
323
324	/* locate the local fixups */
325	childroot = NULL;
326	for_each_child_of_node(resolve, childroot)
327		if (of_node_cmp(childroot->name, "__local_fixups__") == 0)
328			break;
329
330	if (childroot != NULL) {
331		/* resolve root is guaranteed to be the '/' */
332		err = __of_adjust_tree_phandle_references(childroot,
333				resolve, 0);
334		if (err != 0)
335			return err;
336
337		BUG_ON(__of_adjust_tree_phandle_references(childroot,
338				resolve, phandle_delta));
 
 
339	}
340
341	root_sym = NULL;
342	resolve_sym = NULL;
343	resolve_fix = NULL;
 
 
344
345	/* this may fail (if no fixups are required) */
346	root_sym = of_find_node_by_path("/__symbols__");
347
348	/* locate the symbols & fixups nodes on resolve */
349	for_each_child_of_node(resolve, child) {
 
350
351		if (!resolve_sym &&
352				of_node_cmp(child->name, "__symbols__") == 0)
353			resolve_sym = child;
354
355		if (!resolve_fix &&
356				of_node_cmp(child->name, "__fixups__") == 0)
357			resolve_fix = child;
358
359		/* both found, don't bother anymore */
360		if (resolve_sym && resolve_fix)
361			break;
362	}
363
364	/* we do allow for the case where no fixups are needed */
365	if (!resolve_fix) {
366		err = 0;	/* no error */
367		goto out;
368	}
369
370	/* we need to fixup, but no root symbols... */
371	if (!root_sym) {
 
372		err = -EINVAL;
373		goto out;
374	}
375
376	for_each_property_of_node(resolve_fix, rprop) {
377
378		/* skip properties added automatically */
379		if (of_prop_cmp(rprop->name, "name") == 0)
380			continue;
381
382		err = of_property_read_string(root_sym,
383				rprop->name, &refpath);
384		if (err != 0) {
385			pr_err("%s: Could not find symbol '%s'\n",
386					__func__, rprop->name);
387			goto out;
388		}
389
390		refnode = of_find_node_by_path(refpath);
391		if (!refnode) {
392			pr_err("%s: Could not find node by path '%s'\n",
393					__func__, refpath);
394			err = -ENOENT;
395			goto out;
396		}
397
398		phandle = refnode->phandle;
399		of_node_put(refnode);
400
401		pr_debug("%s: %s phandle is 0x%08x\n",
402				__func__, rprop->name, phandle);
403
404		err = __of_adjust_phandle_ref(resolve, rprop, phandle);
405		if (err)
406			break;
407	}
408
409out:
410	/* NULL is handled by of_node_put as NOP */
411	of_node_put(root_sym);
 
412
413	return err;
414}
415EXPORT_SYMBOL_GPL(of_resolve_phandles);
v6.8
  1// SPDX-License-Identifier: GPL-2.0
  2/*
  3 * Functions for dealing with DT resolution
  4 *
  5 * Copyright (C) 2012 Pantelis Antoniou <panto@antoniou-consulting.com>
  6 * Copyright (C) 2012 Texas Instruments Inc.
 
 
 
 
  7 */
  8
  9#define pr_fmt(fmt)	"OF: resolver: " fmt
 10
 11#include <linux/kernel.h>
 12#include <linux/module.h>
 13#include <linux/of.h>
 14#include <linux/of_device.h>
 15#include <linux/string.h>
 16#include <linux/ctype.h>
 17#include <linux/errno.h>
 
 18#include <linux/slab.h>
 19
 20#include "of_private.h"
 
 
 
 
 
 
 
 
 
 
 
 
 
 21
 22static phandle live_tree_max_phandle(void)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 23{
 24	struct device_node *node;
 25	phandle phandle;
 26	unsigned long flags;
 27
 
 28	raw_spin_lock_irqsave(&devtree_lock, flags);
 29	phandle = 0;
 30	for_each_of_allnodes(node) {
 31		if (node->phandle != OF_PHANDLE_ILLEGAL &&
 32				node->phandle > phandle)
 33			phandle = node->phandle;
 34	}
 35	raw_spin_unlock_irqrestore(&devtree_lock, flags);
 36
 37	return phandle;
 38}
 39
 40static void adjust_overlay_phandles(struct device_node *overlay,
 
 
 
 
 
 41		int phandle_delta)
 42{
 43	struct device_node *child;
 44	struct property *prop;
 45	phandle phandle;
 46
 47	/* adjust node's phandle in node */
 48	if (overlay->phandle != 0 && overlay->phandle != OF_PHANDLE_ILLEGAL)
 49		overlay->phandle += phandle_delta;
 50
 51	/* copy adjusted phandle into *phandle properties */
 52	for_each_property_of_node(overlay, prop) {
 53
 54		if (of_prop_cmp(prop->name, "phandle") &&
 55		    of_prop_cmp(prop->name, "linux,phandle"))
 
 56			continue;
 57
 
 58		if (prop->length < 4)
 59			continue;
 60
 
 61		phandle = be32_to_cpup(prop->value);
 62		if (phandle == OF_PHANDLE_ILLEGAL)
 63			continue;
 64
 65		*(__be32 *)prop->value = cpu_to_be32(overlay->phandle);
 
 66	}
 67
 68	for_each_child_of_node(overlay, child)
 69		adjust_overlay_phandles(child, phandle_delta);
 
 70}
 71
 72static int update_usages_of_a_phandle_reference(struct device_node *overlay,
 73		struct property *prop_fixup, phandle phandle)
 74{
 
 75	struct device_node *refnode;
 76	struct property *prop;
 77	char *value, *cur, *end, *node_path, *prop_name, *s;
 78	int offset, len;
 79	int err = 0;
 80
 81	value = kmemdup(prop_fixup->value, prop_fixup->length, GFP_KERNEL);
 82	if (!value)
 
 
 
 83		return -ENOMEM;
 
 
 84
 85	/* prop_fixup contains a list of tuples of path:property_name:offset */
 86	end = value + prop_fixup->length;
 87	for (cur = value; cur < end; cur += len + 1) {
 88		len = strlen(cur);
 89
 90		node_path = cur;
 91		s = strchr(cur, ':');
 92		if (!s) {
 
 
 93			err = -EINVAL;
 94			goto err_fail;
 95		}
 96		*s++ = '\0';
 97
 98		prop_name = s;
 99		s = strchr(s, ':');
100		if (!s) {
 
 
101			err = -EINVAL;
102			goto err_fail;
103		}
 
104		*s++ = '\0';
105
106		err = kstrtoint(s, 10, &offset);
107		if (err)
 
 
108			goto err_fail;
 
109
110		refnode = __of_find_node_by_full_path(of_node_get(overlay), node_path);
111		if (!refnode)
 
 
 
112			continue;
 
113
114		for_each_property_of_node(refnode, prop) {
115			if (!of_prop_cmp(prop->name, prop_name))
 
116				break;
117		}
118		of_node_put(refnode);
119
120		if (!prop) {
 
 
121			err = -ENOENT;
122			goto err_fail;
123		}
124
125		if (offset < 0 || offset + sizeof(__be32) > prop->length) {
126			err = -EINVAL;
127			goto err_fail;
128		}
129
130		*(__be32 *)(prop->value + offset) = cpu_to_be32(phandle);
131	}
132
133err_fail:
134	kfree(value);
135	return err;
136}
137
138/* compare nodes taking into account that 'name' strips out the @ part */
139static int node_name_cmp(const struct device_node *dn1,
140		const struct device_node *dn2)
141{
142	const char *n1 = kbasename(dn1->full_name);
143	const char *n2 = kbasename(dn2->full_name);
144
145	return of_node_cmp(n1, n2);
146}
147
148/*
149 * Adjust the local phandle references by the given phandle delta.
150 *
151 * Subtree @local_fixups, which is overlay node __local_fixups__,
152 * mirrors the fragment node structure at the root of the overlay.
153 *
154 * For each property in the fragments that contains a phandle reference,
155 * @local_fixups has a property of the same name that contains a list
156 * of offsets of the phandle reference(s) within the respective property
157 * value(s).  The values at these offsets will be fixed up.
158 */
159static int adjust_local_phandle_references(struct device_node *local_fixups,
160		struct device_node *overlay, int phandle_delta)
161{
162	struct device_node *child, *overlay_child;
163	struct property *prop_fix, *prop;
164	int err, i, count;
165	unsigned int off;
 
166
167	if (!local_fixups)
168		return 0;
169
170	for_each_property_of_node(local_fixups, prop_fix) {
171
172		/* skip properties added automatically */
173		if (!of_prop_cmp(prop_fix->name, "name") ||
174		    !of_prop_cmp(prop_fix->name, "phandle") ||
175		    !of_prop_cmp(prop_fix->name, "linux,phandle"))
176			continue;
177
178		if ((prop_fix->length % 4) != 0 || prop_fix->length == 0)
 
 
179			return -EINVAL;
180		count = prop_fix->length / sizeof(__be32);
 
181
182		for_each_property_of_node(overlay, prop) {
183			if (!of_prop_cmp(prop->name, prop_fix->name))
 
184				break;
185		}
186
187		if (!prop)
 
 
188			return -EINVAL;
 
189
190		for (i = 0; i < count; i++) {
191			off = be32_to_cpu(((__be32 *)prop_fix->value)[i]);
192			if ((off + 4) > prop->length)
 
 
 
 
 
193				return -EINVAL;
 
194
195			be32_add_cpu(prop->value + off, phandle_delta);
 
 
 
 
 
196		}
197	}
198
199	/*
200	 * These nested loops recurse down two subtrees in parallel, where the
201	 * node names in the two subtrees match.
202	 *
203	 * The roots of the subtrees are the overlay's __local_fixups__ node
204	 * and the overlay's root node.
205	 */
206	for_each_child_of_node(local_fixups, child) {
207
208		for_each_child_of_node(overlay, overlay_child)
209			if (!node_name_cmp(child, overlay_child)) {
210				of_node_put(overlay_child);
211				break;
212			}
213
214		if (!overlay_child) {
215			of_node_put(child);
 
216			return -EINVAL;
217		}
218
219		err = adjust_local_phandle_references(child, overlay_child,
220				phandle_delta);
221		if (err) {
222			of_node_put(child);
223			return err;
224		}
225	}
226
227	return 0;
228}
229
230/**
231 * of_resolve_phandles - Relocate and resolve overlay against live tree
232 *
233 * @overlay:	Pointer to devicetree overlay to relocate and resolve
234 *
235 * Modify (relocate) values of local phandles in @overlay to a range that
236 * does not conflict with the live expanded devicetree.  Update references
237 * to the local phandles in @overlay.  Update (resolve) phandle references
238 * in @overlay that refer to the live expanded devicetree.
239 *
240 * Phandle values in the live tree are in the range of
241 * 1 .. live_tree_max_phandle().  The range of phandle values in the overlay
242 * also begin with at 1.  Adjust the phandle values in the overlay to begin
243 * at live_tree_max_phandle() + 1.  Update references to the phandles to
244 * the adjusted phandle values.
245 *
246 * The name of each property in the "__fixups__" node in the overlay matches
247 * the name of a symbol (a label) in the live tree.  The values of each
248 * property in the "__fixups__" node is a list of the property values in the
249 * overlay that need to be updated to contain the phandle reference
250 * corresponding to that symbol in the live tree.  Update the references in
251 * the overlay with the phandle values in the live tree.
252 *
253 * @overlay must be detached.
254 *
255 * Resolving and applying @overlay to the live expanded devicetree must be
256 * protected by a mechanism to ensure that multiple overlays are processed
257 * in a single threaded manner so that multiple overlays will not relocate
258 * phandles to overlapping ranges.  The mechanism to enforce this is not
259 * yet implemented.
260 *
261 * Return: %0 on success or a negative error value on error.
262 */
263int of_resolve_phandles(struct device_node *overlay)
264{
265	struct device_node *child, *local_fixups, *refnode;
266	struct device_node *tree_symbols, *overlay_fixups;
267	struct property *prop;
268	const char *refpath;
269	phandle phandle, phandle_delta;
270	int err;
271
272	tree_symbols = NULL;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
274	if (!overlay) {
275		pr_err("null overlay\n");
276		err = -EINVAL;
277		goto out;
278	}
279
280	if (!of_node_check_flag(overlay, OF_DETACHED)) {
281		pr_err("overlay not detached\n");
282		err = -EINVAL;
283		goto out;
284	}
285
286	phandle_delta = live_tree_max_phandle() + 1;
287	adjust_overlay_phandles(overlay, phandle_delta);
288
289	for_each_child_of_node(overlay, local_fixups)
290		if (of_node_name_eq(local_fixups, "__local_fixups__"))
291			break;
292
293	err = adjust_local_phandle_references(local_fixups, overlay, phandle_delta);
294	if (err)
295		goto out;
296
297	overlay_fixups = NULL;
 
 
298
299	for_each_child_of_node(overlay, child) {
300		if (of_node_name_eq(child, "__fixups__"))
301			overlay_fixups = child;
302	}
303
304	if (!overlay_fixups) {
305		err = 0;
 
306		goto out;
307	}
308
309	tree_symbols = of_find_node_by_path("/__symbols__");
310	if (!tree_symbols) {
311		pr_err("no symbols in root of device tree.\n");
312		err = -EINVAL;
313		goto out;
314	}
315
316	for_each_property_of_node(overlay_fixups, prop) {
317
318		/* skip properties added automatically */
319		if (!of_prop_cmp(prop->name, "name"))
320			continue;
321
322		err = of_property_read_string(tree_symbols,
323				prop->name, &refpath);
324		if (err) {
325			pr_err("node label '%s' not found in live devicetree symbols table\n",
326			       prop->name);
327			goto out;
328		}
329
330		refnode = of_find_node_by_path(refpath);
331		if (!refnode) {
 
 
332			err = -ENOENT;
333			goto out;
334		}
335
336		phandle = refnode->phandle;
337		of_node_put(refnode);
338
339		err = update_usages_of_a_phandle_reference(overlay, prop, phandle);
 
 
 
340		if (err)
341			break;
342	}
343
344out:
345	if (err)
346		pr_err("overlay phandle fixup failed: %d\n", err);
347	of_node_put(tree_symbols);
348
349	return err;
350}
351EXPORT_SYMBOL_GPL(of_resolve_phandles);