xref: /openbmc/linux/scripts/dtc/libfdt/fdt.c (revision 7ae9fb1b7ecbb5d85d07857943f677fd1a559b18)
112869ecdSRob Herring // SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause)
29fffb55fSDavid Gibson /*
39fffb55fSDavid Gibson  * libfdt - Flat Device Tree manipulation
49fffb55fSDavid Gibson  * Copyright (C) 2006 David Gibson, IBM Corporation.
59fffb55fSDavid Gibson  */
69fffb55fSDavid Gibson #include "libfdt_env.h"
79fffb55fSDavid Gibson 
89fffb55fSDavid Gibson #include <fdt.h>
99fffb55fSDavid Gibson #include <libfdt.h>
109fffb55fSDavid Gibson 
119fffb55fSDavid Gibson #include "libfdt_internal.h"
129fffb55fSDavid Gibson 
13f858927fSRob Herring /*
14f858927fSRob Herring  * Minimal sanity check for a read-only tree. fdt_ro_probe_() checks
15f858927fSRob Herring  * that the given buffer contains what appears to be a flattened
16f858927fSRob Herring  * device tree with sane information in its header.
17f858927fSRob Herring  */
fdt_ro_probe_(const void * fdt)180cec114eSRob Herring int32_t fdt_ro_probe_(const void *fdt)
199fffb55fSDavid Gibson {
200cec114eSRob Herring 	uint32_t totalsize = fdt_totalsize(fdt);
210cec114eSRob Herring 
22d047cd8aSRob Herring 	if (can_assume(VALID_DTB))
23d047cd8aSRob Herring 		return totalsize;
24d047cd8aSRob Herring 
2579edff12SRob Herring 	/* The device tree must be at an 8-byte aligned address */
2679edff12SRob Herring 	if ((uintptr_t)fdt & 7)
2779edff12SRob Herring 		return -FDT_ERR_ALIGNMENT;
2879edff12SRob Herring 
299fffb55fSDavid Gibson 	if (fdt_magic(fdt) == FDT_MAGIC) {
309fffb55fSDavid Gibson 		/* Complete tree */
31d047cd8aSRob Herring 		if (!can_assume(LATEST)) {
329fffb55fSDavid Gibson 			if (fdt_version(fdt) < FDT_FIRST_SUPPORTED_VERSION)
339fffb55fSDavid Gibson 				return -FDT_ERR_BADVERSION;
34d047cd8aSRob Herring 			if (fdt_last_comp_version(fdt) >
35d047cd8aSRob Herring 					FDT_LAST_SUPPORTED_VERSION)
369fffb55fSDavid Gibson 				return -FDT_ERR_BADVERSION;
37d047cd8aSRob Herring 		}
389fffb55fSDavid Gibson 	} else if (fdt_magic(fdt) == FDT_SW_MAGIC) {
399fffb55fSDavid Gibson 		/* Unfinished sequential-write blob */
40d047cd8aSRob Herring 		if (!can_assume(VALID_INPUT) && fdt_size_dt_struct(fdt) == 0)
419fffb55fSDavid Gibson 			return -FDT_ERR_BADSTATE;
429fffb55fSDavid Gibson 	} else {
439fffb55fSDavid Gibson 		return -FDT_ERR_BADMAGIC;
449fffb55fSDavid Gibson 	}
459fffb55fSDavid Gibson 
460cec114eSRob Herring 	if (totalsize < INT32_MAX)
470cec114eSRob Herring 		return totalsize;
480cec114eSRob Herring 	else
490cec114eSRob Herring 		return -FDT_ERR_TRUNCATED;
509fffb55fSDavid Gibson }
519fffb55fSDavid Gibson 
check_off_(uint32_t hdrsize,uint32_t totalsize,uint32_t off)52f858927fSRob Herring static int check_off_(uint32_t hdrsize, uint32_t totalsize, uint32_t off)
53f858927fSRob Herring {
54f858927fSRob Herring 	return (off >= hdrsize) && (off <= totalsize);
55f858927fSRob Herring }
56f858927fSRob Herring 
check_block_(uint32_t hdrsize,uint32_t totalsize,uint32_t base,uint32_t size)57f858927fSRob Herring static int check_block_(uint32_t hdrsize, uint32_t totalsize,
58f858927fSRob Herring 			uint32_t base, uint32_t size)
59f858927fSRob Herring {
60f858927fSRob Herring 	if (!check_off_(hdrsize, totalsize, base))
61f858927fSRob Herring 		return 0; /* block start out of bounds */
62f858927fSRob Herring 	if ((base + size) < base)
63f858927fSRob Herring 		return 0; /* overflow */
64f858927fSRob Herring 	if (!check_off_(hdrsize, totalsize, base + size))
65f858927fSRob Herring 		return 0; /* block end out of bounds */
66f858927fSRob Herring 	return 1;
67f858927fSRob Herring }
68f858927fSRob Herring 
fdt_header_size_(uint32_t version)69f858927fSRob Herring size_t fdt_header_size_(uint32_t version)
70f858927fSRob Herring {
71f858927fSRob Herring 	if (version <= 1)
72f858927fSRob Herring 		return FDT_V1_SIZE;
73f858927fSRob Herring 	else if (version <= 2)
74f858927fSRob Herring 		return FDT_V2_SIZE;
75f858927fSRob Herring 	else if (version <= 3)
76f858927fSRob Herring 		return FDT_V3_SIZE;
77f858927fSRob Herring 	else if (version <= 16)
78f858927fSRob Herring 		return FDT_V16_SIZE;
79f858927fSRob Herring 	else
80f858927fSRob Herring 		return FDT_V17_SIZE;
81f858927fSRob Herring }
82f858927fSRob Herring 
fdt_header_size(const void * fdt)83d047cd8aSRob Herring size_t fdt_header_size(const void *fdt)
84d047cd8aSRob Herring {
85d047cd8aSRob Herring 	return can_assume(LATEST) ? FDT_V17_SIZE :
86d047cd8aSRob Herring 		fdt_header_size_(fdt_version(fdt));
87d047cd8aSRob Herring }
88d047cd8aSRob Herring 
fdt_check_header(const void * fdt)89f858927fSRob Herring int fdt_check_header(const void *fdt)
90f858927fSRob Herring {
91f858927fSRob Herring 	size_t hdrsize;
92f858927fSRob Herring 
93a77725a9SRob Herring 	/* The device tree must be at an 8-byte aligned address */
94a77725a9SRob Herring 	if ((uintptr_t)fdt & 7)
95a77725a9SRob Herring 		return -FDT_ERR_ALIGNMENT;
96a77725a9SRob Herring 
97f858927fSRob Herring 	if (fdt_magic(fdt) != FDT_MAGIC)
98f858927fSRob Herring 		return -FDT_ERR_BADMAGIC;
99d047cd8aSRob Herring 	if (!can_assume(LATEST)) {
100f858927fSRob Herring 		if ((fdt_version(fdt) < FDT_FIRST_SUPPORTED_VERSION)
101d047cd8aSRob Herring 		    || (fdt_last_comp_version(fdt) >
102d047cd8aSRob Herring 			FDT_LAST_SUPPORTED_VERSION))
103f858927fSRob Herring 			return -FDT_ERR_BADVERSION;
104f858927fSRob Herring 		if (fdt_version(fdt) < fdt_last_comp_version(fdt))
105f858927fSRob Herring 			return -FDT_ERR_BADVERSION;
106d047cd8aSRob Herring 	}
107d047cd8aSRob Herring 	hdrsize = fdt_header_size(fdt);
108d047cd8aSRob Herring 	if (!can_assume(VALID_DTB)) {
109f858927fSRob Herring 		if ((fdt_totalsize(fdt) < hdrsize)
110f858927fSRob Herring 		    || (fdt_totalsize(fdt) > INT_MAX))
111f858927fSRob Herring 			return -FDT_ERR_TRUNCATED;
112f858927fSRob Herring 
113f858927fSRob Herring 		/* Bounds check memrsv block */
114d047cd8aSRob Herring 		if (!check_off_(hdrsize, fdt_totalsize(fdt),
115d047cd8aSRob Herring 				fdt_off_mem_rsvmap(fdt)))
116f858927fSRob Herring 			return -FDT_ERR_TRUNCATED;
117f858927fSRob Herring 
118f858927fSRob Herring 		/* Bounds check structure block */
119d047cd8aSRob Herring 		if (!can_assume(LATEST) && fdt_version(fdt) < 17) {
120f858927fSRob Herring 			if (!check_off_(hdrsize, fdt_totalsize(fdt),
121f858927fSRob Herring 					fdt_off_dt_struct(fdt)))
122f858927fSRob Herring 				return -FDT_ERR_TRUNCATED;
123f858927fSRob Herring 		} else {
124f858927fSRob Herring 			if (!check_block_(hdrsize, fdt_totalsize(fdt),
125f858927fSRob Herring 					  fdt_off_dt_struct(fdt),
126f858927fSRob Herring 					  fdt_size_dt_struct(fdt)))
127f858927fSRob Herring 				return -FDT_ERR_TRUNCATED;
128f858927fSRob Herring 		}
129f858927fSRob Herring 
130f858927fSRob Herring 		/* Bounds check strings block */
131f858927fSRob Herring 		if (!check_block_(hdrsize, fdt_totalsize(fdt),
132d047cd8aSRob Herring 				  fdt_off_dt_strings(fdt),
133d047cd8aSRob Herring 				  fdt_size_dt_strings(fdt)))
134f858927fSRob Herring 			return -FDT_ERR_TRUNCATED;
135d047cd8aSRob Herring 	}
136f858927fSRob Herring 
137f858927fSRob Herring 	return 0;
138f858927fSRob Herring }
139f858927fSRob Herring 
fdt_offset_ptr(const void * fdt,int offset,unsigned int len)140cd296721SStephen Warren const void *fdt_offset_ptr(const void *fdt, int offset, unsigned int len)
1419fffb55fSDavid Gibson {
1426e9c9686SRob Herring 	unsigned int uoffset = offset;
1436e9c9686SRob Herring 	unsigned int absoffset = offset + fdt_off_dt_struct(fdt);
1446e9c9686SRob Herring 
1456e9c9686SRob Herring 	if (offset < 0)
1466e9c9686SRob Herring 		return NULL;
14791feabc2SRob Herring 
148d047cd8aSRob Herring 	if (!can_assume(VALID_INPUT))
1496e9c9686SRob Herring 		if ((absoffset < uoffset)
15091feabc2SRob Herring 		    || ((absoffset + len) < absoffset)
15191feabc2SRob Herring 		    || (absoffset + len) > fdt_totalsize(fdt))
15291feabc2SRob Herring 			return NULL;
1539fffb55fSDavid Gibson 
154d047cd8aSRob Herring 	if (can_assume(LATEST) || fdt_version(fdt) >= 0x11)
1556e9c9686SRob Herring 		if (((uoffset + len) < uoffset)
1569fffb55fSDavid Gibson 		    || ((offset + len) > fdt_size_dt_struct(fdt)))
1579fffb55fSDavid Gibson 			return NULL;
1589fffb55fSDavid Gibson 
1599130ba88SRob Herring 	return fdt_offset_ptr_(fdt, offset);
1609fffb55fSDavid Gibson }
1619fffb55fSDavid Gibson 
fdt_next_tag(const void * fdt,int startoffset,int * nextoffset)162cd296721SStephen Warren uint32_t fdt_next_tag(const void *fdt, int startoffset, int *nextoffset)
1639fffb55fSDavid Gibson {
16447605971SRob Herring 	const fdt32_t *tagp, *lenp;
165*ea3723a5SRob Herring 	uint32_t tag, len, sum;
166cd296721SStephen Warren 	int offset = startoffset;
1679fffb55fSDavid Gibson 	const char *p;
1689fffb55fSDavid Gibson 
169cd296721SStephen Warren 	*nextoffset = -FDT_ERR_TRUNCATED;
1709fffb55fSDavid Gibson 	tagp = fdt_offset_ptr(fdt, offset, FDT_TAGSIZE);
171d047cd8aSRob Herring 	if (!can_assume(VALID_DTB) && !tagp)
1729fffb55fSDavid Gibson 		return FDT_END; /* premature end */
1739fffb55fSDavid Gibson 	tag = fdt32_to_cpu(*tagp);
1749fffb55fSDavid Gibson 	offset += FDT_TAGSIZE;
1759fffb55fSDavid Gibson 
176cd296721SStephen Warren 	*nextoffset = -FDT_ERR_BADSTRUCTURE;
1779fffb55fSDavid Gibson 	switch (tag) {
1789fffb55fSDavid Gibson 	case FDT_BEGIN_NODE:
1799fffb55fSDavid Gibson 		/* skip name */
1809fffb55fSDavid Gibson 		do {
1819fffb55fSDavid Gibson 			p = fdt_offset_ptr(fdt, offset++, 1);
1829fffb55fSDavid Gibson 		} while (p && (*p != '\0'));
183d047cd8aSRob Herring 		if (!can_assume(VALID_DTB) && !p)
184cd296721SStephen Warren 			return FDT_END; /* premature end */
1859fffb55fSDavid Gibson 		break;
186cd296721SStephen Warren 
1879fffb55fSDavid Gibson 	case FDT_PROP:
1889fffb55fSDavid Gibson 		lenp = fdt_offset_ptr(fdt, offset, sizeof(*lenp));
189d047cd8aSRob Herring 		if (!can_assume(VALID_DTB) && !lenp)
190cd296721SStephen Warren 			return FDT_END; /* premature end */
191*ea3723a5SRob Herring 
192*ea3723a5SRob Herring 		len = fdt32_to_cpu(*lenp);
193*ea3723a5SRob Herring 		sum = len + offset;
194*ea3723a5SRob Herring 		if (!can_assume(VALID_DTB) &&
195*ea3723a5SRob Herring 		    (INT_MAX <= sum || sum < (uint32_t) offset))
196*ea3723a5SRob Herring 			return FDT_END; /* premature end */
197*ea3723a5SRob Herring 
198cd296721SStephen Warren 		/* skip-name offset, length and value */
199*ea3723a5SRob Herring 		offset += sizeof(struct fdt_property) - FDT_TAGSIZE + len;
200*ea3723a5SRob Herring 
201d047cd8aSRob Herring 		if (!can_assume(LATEST) &&
202*ea3723a5SRob Herring 		    fdt_version(fdt) < 0x10 && len >= 8 &&
203*ea3723a5SRob Herring 		    ((offset - len) % 8) != 0)
2049130ba88SRob Herring 			offset += 4;
2059fffb55fSDavid Gibson 		break;
206cd296721SStephen Warren 
207cd296721SStephen Warren 	case FDT_END:
208cd296721SStephen Warren 	case FDT_END_NODE:
209cd296721SStephen Warren 	case FDT_NOP:
210cd296721SStephen Warren 		break;
211cd296721SStephen Warren 
212cd296721SStephen Warren 	default:
213cd296721SStephen Warren 		return FDT_END;
2149fffb55fSDavid Gibson 	}
2159fffb55fSDavid Gibson 
216cd296721SStephen Warren 	if (!fdt_offset_ptr(fdt, startoffset, offset - startoffset))
217cd296721SStephen Warren 		return FDT_END; /* premature end */
2189fffb55fSDavid Gibson 
219cd296721SStephen Warren 	*nextoffset = FDT_TAGALIGN(offset);
2209fffb55fSDavid Gibson 	return tag;
2219fffb55fSDavid Gibson }
2229fffb55fSDavid Gibson 
fdt_check_node_offset_(const void * fdt,int offset)2239130ba88SRob Herring int fdt_check_node_offset_(const void *fdt, int offset)
2249fffb55fSDavid Gibson {
2256e9c9686SRob Herring 	if (!can_assume(VALID_INPUT)
2266e9c9686SRob Herring 	    && ((offset < 0) || (offset % FDT_TAGSIZE)))
2276e9c9686SRob Herring 		return -FDT_ERR_BADOFFSET;
2286e9c9686SRob Herring 
2296e9c9686SRob Herring 	if (fdt_next_tag(fdt, offset, &offset) != FDT_BEGIN_NODE)
2309fffb55fSDavid Gibson 		return -FDT_ERR_BADOFFSET;
2319fffb55fSDavid Gibson 
2329fffb55fSDavid Gibson 	return offset;
2339fffb55fSDavid Gibson }
2349fffb55fSDavid Gibson 
fdt_check_prop_offset_(const void * fdt,int offset)2359130ba88SRob Herring int fdt_check_prop_offset_(const void *fdt, int offset)
236cd296721SStephen Warren {
2376e9c9686SRob Herring 	if (!can_assume(VALID_INPUT)
2386e9c9686SRob Herring 	    && ((offset < 0) || (offset % FDT_TAGSIZE)))
2396e9c9686SRob Herring 		return -FDT_ERR_BADOFFSET;
2406e9c9686SRob Herring 
2416e9c9686SRob Herring 	if (fdt_next_tag(fdt, offset, &offset) != FDT_PROP)
242cd296721SStephen Warren 		return -FDT_ERR_BADOFFSET;
243cd296721SStephen Warren 
244cd296721SStephen Warren 	return offset;
245cd296721SStephen Warren }
246cd296721SStephen Warren 
fdt_next_node(const void * fdt,int offset,int * depth)2479fffb55fSDavid Gibson int fdt_next_node(const void *fdt, int offset, int *depth)
2489fffb55fSDavid Gibson {
2499fffb55fSDavid Gibson 	int nextoffset = 0;
2509fffb55fSDavid Gibson 	uint32_t tag;
2519fffb55fSDavid Gibson 
2529fffb55fSDavid Gibson 	if (offset >= 0)
2539130ba88SRob Herring 		if ((nextoffset = fdt_check_node_offset_(fdt, offset)) < 0)
2549fffb55fSDavid Gibson 			return nextoffset;
2559fffb55fSDavid Gibson 
2569fffb55fSDavid Gibson 	do {
2579fffb55fSDavid Gibson 		offset = nextoffset;
2589fffb55fSDavid Gibson 		tag = fdt_next_tag(fdt, offset, &nextoffset);
2599fffb55fSDavid Gibson 
2609fffb55fSDavid Gibson 		switch (tag) {
2619fffb55fSDavid Gibson 		case FDT_PROP:
2629fffb55fSDavid Gibson 		case FDT_NOP:
2639fffb55fSDavid Gibson 			break;
2649fffb55fSDavid Gibson 
2659fffb55fSDavid Gibson 		case FDT_BEGIN_NODE:
2669fffb55fSDavid Gibson 			if (depth)
2679fffb55fSDavid Gibson 				(*depth)++;
2689fffb55fSDavid Gibson 			break;
2699fffb55fSDavid Gibson 
2709fffb55fSDavid Gibson 		case FDT_END_NODE:
271cd296721SStephen Warren 			if (depth && ((--(*depth)) < 0))
272cd296721SStephen Warren 				return nextoffset;
2739fffb55fSDavid Gibson 			break;
2749fffb55fSDavid Gibson 
2759fffb55fSDavid Gibson 		case FDT_END:
276cd296721SStephen Warren 			if ((nextoffset >= 0)
277cd296721SStephen Warren 			    || ((nextoffset == -FDT_ERR_TRUNCATED) && !depth))
2789fffb55fSDavid Gibson 				return -FDT_ERR_NOTFOUND;
279cd296721SStephen Warren 			else
280cd296721SStephen Warren 				return nextoffset;
2819fffb55fSDavid Gibson 		}
2829fffb55fSDavid Gibson 	} while (tag != FDT_BEGIN_NODE);
2839fffb55fSDavid Gibson 
2849fffb55fSDavid Gibson 	return offset;
2859fffb55fSDavid Gibson }
2869fffb55fSDavid Gibson 
fdt_first_subnode(const void * fdt,int offset)28747605971SRob Herring int fdt_first_subnode(const void *fdt, int offset)
28847605971SRob Herring {
28947605971SRob Herring 	int depth = 0;
29047605971SRob Herring 
29147605971SRob Herring 	offset = fdt_next_node(fdt, offset, &depth);
29247605971SRob Herring 	if (offset < 0 || depth != 1)
29347605971SRob Herring 		return -FDT_ERR_NOTFOUND;
29447605971SRob Herring 
29547605971SRob Herring 	return offset;
29647605971SRob Herring }
29747605971SRob Herring 
fdt_next_subnode(const void * fdt,int offset)29847605971SRob Herring int fdt_next_subnode(const void *fdt, int offset)
29947605971SRob Herring {
30047605971SRob Herring 	int depth = 1;
30147605971SRob Herring 
30247605971SRob Herring 	/*
30347605971SRob Herring 	 * With respect to the parent, the depth of the next subnode will be
30447605971SRob Herring 	 * the same as the last.
30547605971SRob Herring 	 */
30647605971SRob Herring 	do {
30747605971SRob Herring 		offset = fdt_next_node(fdt, offset, &depth);
30847605971SRob Herring 		if (offset < 0 || depth < 1)
30947605971SRob Herring 			return -FDT_ERR_NOTFOUND;
31047605971SRob Herring 	} while (depth > 1);
31147605971SRob Herring 
31247605971SRob Herring 	return offset;
31347605971SRob Herring }
31447605971SRob Herring 
fdt_find_string_(const char * strtab,int tabsize,const char * s)3159130ba88SRob Herring const char *fdt_find_string_(const char *strtab, int tabsize, const char *s)
3169fffb55fSDavid Gibson {
3179fffb55fSDavid Gibson 	int len = strlen(s) + 1;
3189fffb55fSDavid Gibson 	const char *last = strtab + tabsize - len;
3199fffb55fSDavid Gibson 	const char *p;
3209fffb55fSDavid Gibson 
3219fffb55fSDavid Gibson 	for (p = strtab; p <= last; p++)
3229fffb55fSDavid Gibson 		if (memcmp(p, s, len) == 0)
3239fffb55fSDavid Gibson 			return p;
3249fffb55fSDavid Gibson 	return NULL;
3259fffb55fSDavid Gibson }
3269fffb55fSDavid Gibson 
fdt_move(const void * fdt,void * buf,int bufsize)3279fffb55fSDavid Gibson int fdt_move(const void *fdt, void *buf, int bufsize)
3289fffb55fSDavid Gibson {
3296e9c9686SRob Herring 	if (!can_assume(VALID_INPUT) && bufsize < 0)
3306e9c9686SRob Herring 		return -FDT_ERR_NOSPACE;
3316e9c9686SRob Herring 
332f858927fSRob Herring 	FDT_RO_PROBE(fdt);
3339fffb55fSDavid Gibson 
3346e9c9686SRob Herring 	if (fdt_totalsize(fdt) > (unsigned int)bufsize)
3359fffb55fSDavid Gibson 		return -FDT_ERR_NOSPACE;
3369fffb55fSDavid Gibson 
3379fffb55fSDavid Gibson 	memmove(buf, fdt, fdt_totalsize(fdt));
3389fffb55fSDavid Gibson 	return 0;
3399fffb55fSDavid Gibson }
340