pyfdt.py revision 12468:b4b8bdb9712a
1# -*- coding: utf-8 -*-
2"""
3Device Tree Blob Parser
4
5   Copyright 2014  Neil 'superna' Armstrong <superna9999@gmail.com>
6
7   Licensed under the Apache License, Version 2.0 (the "License");
8   you may not use this file except in compliance with the License.
9   You may obtain a copy of the License at
10
11     http://www.apache.org/licenses/LICENSE-2.0
12
13   Unless required by applicable law or agreed to in writing, software
14   distributed under the License is distributed on an "AS IS" BASIS,
15   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   See the License for the specific language governing permissions and
17   limitations under the License.
18
19@author: Neil 'superna' Armstrong <superna9999@gmail.com>
20"""
21
22import string
23import os
24import json
25from copy import deepcopy, copy
26from struct import Struct, unpack, pack
27
28FDT_MAGIC = 0xd00dfeed
29FDT_BEGIN_NODE = 0x1
30FDT_END_NODE = 0x2
31FDT_PROP = 0x3
32FDT_NOP = 0x4
33FDT_END = 0x9
34
35INDENT = ' ' * 4
36
37FDT_MAX_VERSION = 17
38
39
40class FdtProperty(object):
41    """ Represents an empty property"""
42
43    @staticmethod
44    def __validate_dt_name(name):
45        """Checks the name validity"""
46        return not any([True for char in name
47                        if char not in string.printable])
48
49    def __init__(self, name):
50        """Init with name"""
51        self.name = name
52        if not FdtProperty.__validate_dt_name(self.name):
53            raise Exception("Invalid name '%s'" % self.name)
54
55    def get_name(self):
56        """Get property name"""
57        return self.name
58
59    def __str__(self):
60        """String representation"""
61        return "Property(%s)" % self.name
62
63    def dts_represent(self, depth=0):
64        """Get dts string representation"""
65        return INDENT*depth + self.name + ';'
66
67    def dtb_represent(self, string_store, pos=0, version=17):
68        """Get blob representation"""
69        # print "%x:%s" % (pos, self)
70        strpos = string_store.find(self.name+'\0')
71        if strpos < 0:
72            strpos = len(string_store)
73            string_store += self.name+'\0'
74        pos += 12
75        return (pack('>III', FDT_PROP, 0, strpos),
76                string_store, pos)
77
78    def json_represent(self, depth=0):
79        """Ouput JSON"""
80        return '%s: null' % json.dumps(self.name)
81
82    def to_raw(self):
83        """Return RAW value representation"""
84        return ''
85
86    def __getitem__(self, value):
87        """Returns No Items"""
88        return None
89
90    def __ne__(self, node):
91        """Check property inequality
92        """
93        return not self.__eq__(node)
94
95    def __eq__(self, node):
96        """Check node equality
97           check properties are the same (same values)
98        """
99        if not isinstance(node, FdtProperty):
100            raise Exception("Invalid object type")
101        if self.name != node.get_name():
102            return False
103        return True
104
105    @staticmethod
106    def __check_prop_strings(value):
107        """Check property string validity
108           Python version of util_is_printable_string from dtc
109        """
110        pos = 0
111        posi = 0
112        end = len(value)
113
114        if not len(value):
115            return None
116
117        #Needed for python 3 support: If a bytes object is passed,
118        #decode it with the ascii codec. If the decoding fails, assume
119        #it was not a string object.
120        try:
121            value = value.decode('ascii')
122        except ValueError:
123            return None
124
125        #Test both against string 0 and int 0 because of
126        # python2/3 compatibility
127        if value[-1] != '\0':
128            return None
129
130        while pos < end:
131            posi = pos
132            while pos < end and value[pos] != '\0' \
133                  and value[pos] in string.printable \
134                  and value[pos] not in ('\r', '\n'):
135                pos += 1
136
137            if value[pos] != '\0' or pos == posi:
138                return None
139            pos += 1
140
141        return True
142
143    @staticmethod
144    def new_raw_property(name, raw_value):
145        """Instantiate property with raw value type"""
146        if FdtProperty.__check_prop_strings(raw_value):
147            return FdtPropertyStrings.init_raw(name, raw_value)
148        elif len(raw_value) and len(raw_value) % 4 == 0:
149            return FdtPropertyWords.init_raw(name, raw_value)
150        elif len(raw_value) and len(raw_value):
151            return FdtPropertyBytes.init_raw(name, raw_value)
152        else:
153            return FdtProperty(name)
154
155
156class FdtPropertyStrings(FdtProperty):
157    """Property with strings as value"""
158
159    @classmethod
160    def __extract_prop_strings(cls, value):
161        """Extract strings from raw_value"""
162        return [st for st in \
163            value.decode('ascii').split('\0') if len(st)]
164
165    def __init__(self, name, strings):
166        """Init with strings"""
167        FdtProperty.__init__(self, name)
168        if not strings:
169            raise Exception("Invalid strings")
170        for stri in strings:
171            if len(stri) == 0:
172                raise Exception("Invalid strings")
173            if any([True for char in stri
174                        if char not in string.printable
175                           or char in ('\r', '\n')]):
176                raise Exception("Invalid chars in strings")
177        self.strings = strings
178
179    @classmethod
180    def init_raw(cls, name, raw_value):
181        """Init from raw"""
182        return cls(name, cls.__extract_prop_strings(raw_value))
183
184    def dts_represent(self, depth=0):
185        """Get dts string representation"""
186        return INDENT*depth + self.name + ' = "' + \
187            '", "'.join(self.strings) + '";'
188
189    def dtb_represent(self, string_store, pos=0, version=17):
190        """Get blob representation"""
191        # print "%x:%s" % (pos, self)
192        blob = pack('')
193        for chars in self.strings:
194            blob += chars.encode('ascii') + pack('b', 0)
195        blob_len = len(blob)
196        if version < 16 and (pos+12) % 8 != 0:
197            blob = pack('b', 0) * (8-((pos+12) % 8)) + blob
198        if blob_len % 4:
199            blob += pack('b', 0) * (4-(blob_len % 4))
200        strpos = string_store.find(self.name+'\0')
201        if strpos < 0:
202            strpos = len(string_store)
203            string_store += self.name+'\0'
204        blob = pack('>III', FDT_PROP, blob_len, strpos) + blob
205        pos += len(blob)
206        return (blob, string_store, pos)
207
208    def json_represent(self, depth=0):
209        """Ouput JSON"""
210        result = '%s: ["strings", ' % json.dumps(self.name)
211        result += ', '.join([json.dumps(stri) for stri in self.strings])
212        result += ']'
213        return result
214
215    def to_raw(self):
216        """Return RAW value representation"""
217        return ''.join([chars+'\0' for chars in self.strings])
218
219    def __str__(self):
220        """String representation"""
221        return "Property(%s,Strings:%s)" % (self.name, self.strings)
222
223    def __getitem__(self, index):
224        """Get strings, returns a string"""
225        return self.strings[index]
226
227    def __len__(self):
228        """Get strings count"""
229        return len(self.strings)
230
231    def __eq__(self, node):
232        """Check node equality
233           check properties are the same (same values)
234        """
235        if not FdtProperty.__eq__(self, node):
236            return False
237        if self.__len__() != len(node):
238            return False
239        for index in range(self.__len__()):
240            if self.strings[index] != node[index]:
241                return False
242        return True
243
244class FdtPropertyWords(FdtProperty):
245    """Property with words as value"""
246
247    def __init__(self, name, words):
248        """Init with words"""
249        FdtProperty.__init__(self, name)
250        for word in words:
251            if not 0 <= word <= 4294967295:
252                raise Exception(("Invalid word value %d, requires " +
253                                 "0 <= number <= 4294967295") % word)
254        if not len(words):
255            raise Exception("Invalid Words")
256        self.words = words
257
258    @classmethod
259    def init_raw(cls, name, raw_value):
260        """Init from raw"""
261        if len(raw_value) % 4 == 0:
262            words = [unpack(">I", raw_value[i:i+4])[0]
263                     for i in range(0, len(raw_value), 4)]
264            return cls(name, words)
265        else:
266            raise Exception("Invalid raw Words")
267
268    def dts_represent(self, depth=0):
269        """Get dts string representation"""
270        return INDENT*depth + self.name + ' = <' + \
271               ' '.join(["0x%08x" % word for word in self.words]) + ">;"
272
273    def dtb_represent(self, string_store, pos=0, version=17):
274        """Get blob representation"""
275        # # print "%x:%s" % (pos, self)
276        strpos = string_store.find(self.name+'\0')
277        if strpos < 0:
278            strpos = len(string_store)
279            string_store += self.name+'\0'
280        blob = pack('>III', FDT_PROP, len(self.words)*4, strpos) + \
281                pack('').join([pack('>I', word) for word in self.words])
282        pos += len(blob)
283        return (blob, string_store, pos)
284
285    def json_represent(self, depth=0):
286        """Ouput JSON"""
287        result = '%s: ["words", "' % json.dumps(self.name)
288        result += '", "'.join(["0x%08x" % word for word in self.words])
289        result += '"]'
290        return result
291
292    def to_raw(self):
293        """Return RAW value representation"""
294        return ''.join([pack('>I', word) for word in self.words])
295
296    def __str__(self):
297        """String representation"""
298        return "Property(%s,Words:%s)" % (self.name, self.words)
299
300    def __getitem__(self, index):
301        """Get words, returns a word integer"""
302        return self.words[index]
303
304    def __len__(self):
305        """Get words count"""
306        return len(self.words)
307
308    def __eq__(self, node):
309        """Check node equality
310           check properties are the same (same values)
311        """
312        if not FdtProperty.__eq__(self, node):
313            return False
314        if self.__len__() != len(node):
315            return False
316        for index in range(self.__len__()):
317            if self.words[index] != node[index]:
318                return False
319        return True
320
321
322class FdtPropertyBytes(FdtProperty):
323    """Property with signed bytes as value"""
324
325    def __init__(self, name, bytez):
326        """Init with bytes"""
327        FdtProperty.__init__(self, name)
328        for byte in bytez:
329            if not -128 <= byte <= 127:
330                raise Exception(("Invalid value for byte %d, " +
331                                 "requires -128 <= number <= 127") % byte)
332        if not bytez:
333            raise Exception("Invalid Bytes")
334        self.bytes = bytez
335
336    @classmethod
337    def init_raw(cls, name, raw_value):
338        """Init from raw"""
339        return cls(name, unpack('b' * len(raw_value), raw_value))
340
341    def dts_represent(self, depth=0):
342        """Get dts string representation"""
343        return INDENT*depth + self.name + ' = [' + \
344            ' '.join(["%02x" % (byte & int('ffffffff',16))
345                      for byte in self.bytes]) + "];"
346
347    def dtb_represent(self, string_store, pos=0, version=17):
348        """Get blob representation"""
349        # print "%x:%s" % (pos, self)
350        strpos = string_store.find(self.name+'\0')
351        if strpos < 0:
352            strpos = len(string_store)
353            string_store += self.name+'\0'
354        blob = pack('>III', FDT_PROP, len(self.bytes), strpos)
355        blob += pack('').join([pack('>b', byte) for byte in self.bytes])
356        if len(blob) % 4:
357            blob += pack('b', 0) * (4-(len(blob) % 4))
358        pos += len(blob)
359        return (blob, string_store, pos)
360
361    def json_represent(self, depth=0):
362        """Ouput JSON"""
363        result = '%s: ["bytes", "' % json.dumps(self.name)
364        result += '", "'.join(["%02x" % byte
365                                for byte in self.bytes])
366        result += '"]'
367        return result
368
369    def to_raw(self):
370        """Return RAW value representation"""
371        return ''.join([pack('>b', byte) for byte in self.bytes])
372
373    def __str__(self):
374        """String representation"""
375        return "Property(%s,Bytes:%s)" % (self.name, self.bytes)
376
377    def __getitem__(self, index):
378        """Get bytes, returns a byte"""
379        return self.bytes[index]
380
381    def __len__(self):
382        """Get strings count"""
383        return len(self.bytes)
384
385    def __eq__(self, node):
386        """Check node equality
387           check properties are the same (same values)
388        """
389        if not FdtProperty.__eq__(self, node):
390            return False
391        if self.__len__() != len(node):
392            return False
393        for index in range(self.__len__()):
394            if self.bytes[index] != node[index]:
395                return False
396        return True
397
398
399class FdtNop(object):  # pylint: disable-msg=R0903
400    """Nop child representation"""
401
402    def __init__(self):
403        """Init with nothing"""
404
405    def get_name(self):  # pylint: disable-msg=R0201
406        """Return name"""
407        return None
408
409    def __str__(self):
410        """String representation"""
411        return ''
412
413    def dts_represent(self, depth=0):  # pylint: disable-msg=R0201
414        """Get dts string representation"""
415        return INDENT*depth+'// [NOP]'
416
417    def dtb_represent(self, string_store, pos=0, version=17):
418        """Get blob representation"""
419        # print "%x:%s" % (pos, self)
420        pos += 4
421        return (pack('>I', FDT_NOP), string_store, pos)
422
423
424class FdtNode(object):
425    """Node representation"""
426
427    @staticmethod
428    def __validate_dt_name(name):
429        """Checks the name validity"""
430        return not any([True for char in name
431                        if char not in string.printable])
432
433    def __init__(self, name):
434        """Init node with name"""
435        self.name = name
436        self.subdata = []
437        self.parent = None
438        if not FdtNode.__validate_dt_name(self.name):
439            raise Exception("Invalid name '%s'" % self.name)
440
441    def get_name(self):
442        """Get property name"""
443        return self.name
444
445    def __check_name_duplicate(self, name):
446        """Checks if name is not in a subnode"""
447        for data in self.subdata:
448            if not isinstance(data, FdtNop) \
449               and data.get_name() == name:
450                   return True
451        return False
452
453    def add_subnode(self, node):
454        """Add child, deprecated use append()"""
455        self.append(node)
456
457    def add_raw_attribute(self, name, raw_value):
458        """Construct a raw attribute and add to child"""
459        self.append(FdtProperty.new_raw_property(name, raw_value))
460
461    def set_parent_node(self, node):
462        """Set parent node, None and FdtNode accepted"""
463        if node is not None and \
464           not isinstance(node, FdtNode):
465            raise Exception("Invalid object type")
466        self.parent = node
467
468    def get_parent_node(self):
469        """Get parent node"""
470        return self.parent
471
472    def __str__(self):
473        """String representation"""
474        return "Node(%s)" % self.name
475
476    def dts_represent(self, depth=0):
477        """Get dts string representation"""
478        result = ('\n').join([sub.dts_represent(depth+1)
479                                         for sub in self.subdata])
480        if len(result) > 0:
481            result += '\n'
482        return INDENT*depth + self.name + ' {\n' + \
483               result + INDENT*depth + "};"
484
485    def dtb_represent(self, strings_store, pos=0, version=17):
486        """Get blob representation
487           Pass string storage as strings_store, pos for current node start
488           and version as current dtb version
489        """
490        # print "%x:%s" % (pos, self)
491        strings = strings_store
492        if self.get_name() == '/':
493            blob = pack('>II', FDT_BEGIN_NODE, 0)
494        else:
495            blob = pack('>I', FDT_BEGIN_NODE)
496            blob += self.get_name().encode('ascii') + pack('b', 0)
497        if len(blob) % 4:
498            blob += pack('b', 0) * (4-(len(blob) % 4))
499        pos += len(blob)
500        for sub in self.subdata:
501            (data, strings, pos) = sub.dtb_represent(strings, pos, version)
502            blob += data
503        pos += 4
504        blob += pack('>I', FDT_END_NODE)
505        return (blob, strings, pos)
506
507    def json_represent(self, depth=0):
508        """Get dts string representation"""
509        result = (',\n'+ \
510                  INDENT*(depth+1)).join([sub.json_represent(depth+1)
511                                          for sub in self.subdata
512                                          if not isinstance(sub, FdtNop)])
513        if len(result) > 0:
514            result = INDENT + result + '\n'+INDENT*depth
515        if self.get_name() == '/':
516            return "{\n" + INDENT*(depth) + result + "}"
517        else:
518            return json.dumps(self.name) + ': {\n' + \
519                   INDENT*(depth) + result + "}"
520
521    def __getitem__(self, index):
522        """Get subnodes, returns either a Node, a Property or a Nop"""
523        return self.subdata[index]
524
525    def __setitem__(self, index, subnode):
526        """Set node at index, replacing previous subnode,
527           must not be a duplicate name
528        """
529        if self.subdata[index].get_name() != subnode.get_name() and \
530           self.__check_name_duplicate(subnode.get_name()):
531            raise Exception("%s : %s subnode already exists" % \
532                                        (self, subnode))
533        if not isinstance(subnode, (FdtNode, FdtProperty, FdtNop)):
534            raise Exception("Invalid object type")
535        self.subdata[index] = subnode
536
537    def __len__(self):
538        """Get strings count"""
539        return len(self.subdata)
540
541    def __ne__(self, node):
542        """Check node inequality
543           i.e. is subnodes are the same, in either order
544           and properties are the same (same values)
545           The FdtNop is excluded from the check
546        """
547        return not self.__eq__(node)
548
549    def __eq__(self, node):
550        """Check node equality
551           i.e. is subnodes are the same, in either order
552           and properties are the same (same values)
553           The FdtNop is excluded from the check
554        """
555        if not isinstance(node, FdtNode):
556            raise Exception("Invalid object type")
557        if self.name != node.get_name():
558            return False
559        curnames = set([subnode.get_name() for subnode in self.subdata
560                                    if not isinstance(subnode, FdtNop)])
561        cmpnames = set([subnode.get_name() for subnode in node
562                                    if not isinstance(subnode, FdtNop)])
563        if curnames != cmpnames:
564            return False
565        for subnode in [subnode for subnode in self.subdata
566                                    if not isinstance(subnode, FdtNop)]:
567            index = node.index(subnode.get_name())
568            if subnode != node[index]:
569                return False
570        return True
571
572    def append(self, subnode):
573        """Append subnode, same as add_subnode"""
574        if self.__check_name_duplicate(subnode.get_name()):
575            raise Exception("%s : %s subnode already exists" % \
576                                    (self, subnode))
577        if not isinstance(subnode, (FdtNode, FdtProperty, FdtNop)):
578            raise Exception("Invalid object type")
579        self.subdata.append(subnode)
580
581    def pop(self, index=-1):
582        """Remove and returns subnode at index, default the last"""
583        return self.subdata.pop(index)
584
585    def insert(self, index, subnode):
586        """Insert subnode before index, must not be a duplicate name"""
587        if self.__check_name_duplicate(subnode.get_name()):
588            raise Exception("%s : %s subnode already exists" % \
589                                (self, subnode))
590        if not isinstance(subnode, (FdtNode, FdtProperty, FdtNop)):
591            raise Exception("Invalid object type")
592        self.subdata.insert(index, subnode)
593
594    def _find(self, name):
595        """Find name in subnodes"""
596        for i in range(0, len(self.subdata)):
597            if not isinstance(self.subdata[i], FdtNop) and \
598               name == self.subdata[i].get_name():
599                return i
600        return None
601
602    def remove(self, name):
603        """Remove subnode with the name
604           Raises ValueError is not present
605        """
606        index = self._find(name)
607        if index is None:
608            raise ValueError("Not present")
609        return self.subdata.pop(index)
610
611    def index(self, name):
612        """Returns position of subnode with the name
613           Raises ValueError is not present
614        """
615        index = self._find(name)
616        if index is None:
617            raise ValueError("Not present")
618        return index
619
620    def merge(self, node):
621        """Merge two nodes and subnodes
622           Replace current properties with the given properties
623        """
624        if not isinstance(node, FdtNode):
625            raise Exception("Can only merge with a FdtNode")
626        for subnode in [obj for obj in node
627                        if isinstance(obj, (FdtNode, FdtProperty))]:
628            index = self._find(subnode.get_name())
629            if index is None:
630                dup = deepcopy(subnode)
631                if isinstance(subnode, FdtNode):
632                    dup.set_parent_node(self)
633                self.append(dup)
634            elif isinstance(subnode, FdtNode):
635                self.subdata[index].merge(subnode)
636            else:
637                self.subdata[index] = copy(subnode)
638
639    def walk(self):
640        """Walk into subnodes and yield paths and objects
641           Returns set with (path string, node object)
642        """
643        node = self
644        start = 0
645        hist = []
646        curpath = []
647
648        while True:
649            for index in range(start, len(node)):
650                if isinstance(node[index], (FdtNode, FdtProperty)):
651                    yield ('/' + '/'.join(curpath+[node[index].get_name()]),
652                            node[index])
653                if isinstance(node[index], FdtNode):
654                    if len(node[index]):
655                        hist.append((node, index+1))
656                        curpath.append(node[index].get_name())
657                        node = node[index]
658                        start = 0
659                        index = -1
660                        break
661            if index >= 0:
662                if len(hist):
663                    (node, start) = hist.pop()
664                    curpath.pop()
665                else:
666                    break
667
668
669class Fdt(object):
670    """Flattened Device Tree representation"""
671
672    def __init__(self, version=17, last_comp_version=16, boot_cpuid_phys=0):
673        """Init FDT object with version and boot values"""
674        self.header = {'magic': FDT_MAGIC,
675                       'totalsize': 0,
676                       'off_dt_struct': 0,
677                       'off_dt_strings': 0,
678                       'off_mem_rsvmap': 0,
679                       'version': version,
680                       'last_comp_version': last_comp_version,
681                       'boot_cpuid_phys': boot_cpuid_phys,
682                       'size_dt_strings': 0,
683                       'size_dt_struct': 0}
684        self.rootnode = None
685        self.prenops = None
686        self.postnops = None
687        self.reserve_entries = None
688
689    def add_rootnode(self, rootnode, prenops=None, postnops=None):
690        """Add root node"""
691        self.rootnode = rootnode
692        self.prenops = prenops
693        self.postnops = postnops
694
695    def get_rootnode(self):
696        """Get root node"""
697        return self.rootnode
698
699    def add_reserve_entries(self, reserve_entries):
700        """Add reserved entries as list of dict with
701           'address' and 'size' keys"""
702        self.reserve_entries = reserve_entries
703
704    def to_dts(self):
705        """Export to DTS representation in string format"""
706        result = "/dts-v1/;\n"
707        result += "// version:\t\t%d\n" % self.header['version']
708        result += "// last_comp_version:\t%d\n" % \
709                  self.header['last_comp_version']
710        if self.header['version'] >= 2:
711            result += "// boot_cpuid_phys:\t0x%x\n" % \
712                self.header['boot_cpuid_phys']
713        result += '\n'
714        if self.reserve_entries is not None:
715            for entry in self.reserve_entries:
716                result += "/memreserve/ "
717                if entry['address']:
718                    result += "%#x " % entry['address']
719                else:
720                    result += "0 "
721                if entry['size']:
722                    result += "%#x" % entry['size']
723                else:
724                    result += "0"
725                result += ";\n"
726        if self.prenops:
727            result += '\n'.join([nop.dts_represent() for nop in self.prenops])
728            result += '\n'
729        if self.rootnode is not None:
730            result += self.rootnode.dts_represent()
731        if self.postnops:
732            result += '\n'
733            result += '\n'.join([nop.dts_represent() for nop in self.postnops])
734        return result
735
736    def to_dtb(self):
737        """Export to Blob format"""
738        if self.rootnode is None:
739            return None
740        blob_reserve_entries = pack('')
741        if self.reserve_entries is not None:
742            for entry in self.reserve_entries:
743                blob_reserve_entries += pack('>QQ',
744                                             entry['address'],
745                                             entry['size'])
746        blob_reserve_entries += pack('>QQ', 0, 0)
747        header_size = 7 * 4
748        if self.header['version'] >= 2:
749            header_size += 4
750        if self.header['version'] >= 3:
751            header_size += 4
752        if self.header['version'] >= 17:
753            header_size += 4
754        header_adjust = pack('')
755        if header_size % 8 != 0:
756            header_adjust = pack('b', 0) * (8 - (header_size % 8))
757            header_size += len(header_adjust)
758        dt_start = header_size + len(blob_reserve_entries)
759        # print "dt_start %d" % dt_start
760        (blob_dt, blob_strings, dt_pos) = \
761            self.rootnode.dtb_represent('', dt_start, self.header['version'])
762        if self.prenops is not None:
763            blob_dt = pack('').join([nop.dtb_represent('')[0]
764                               for nop in self.prenops])\
765                      + blob_dt
766        if self.postnops is not None:
767            blob_dt += pack('').join([nop.dtb_represent('')[0]
768                                for nop in self.postnops])
769        blob_dt += pack('>I', FDT_END)
770        self.header['size_dt_strings'] = len(blob_strings)
771        self.header['size_dt_struct'] = len(blob_dt)
772        self.header['off_mem_rsvmap'] = header_size
773        self.header['off_dt_struct'] = dt_start
774        self.header['off_dt_strings'] = dt_start + len(blob_dt)
775        self.header['totalsize'] = dt_start + len(blob_dt) + len(blob_strings)
776        blob_header = pack('>IIIIIII', self.header['magic'],
777                           self.header['totalsize'],
778                           self.header['off_dt_struct'],
779                           self.header['off_dt_strings'],
780                           self.header['off_mem_rsvmap'],
781                           self.header['version'],
782                           self.header['last_comp_version'])
783        if self.header['version'] >= 2:
784            blob_header += pack('>I', self.header['boot_cpuid_phys'])
785        if self.header['version'] >= 3:
786            blob_header += pack('>I', self.header['size_dt_strings'])
787        if self.header['version'] >= 17:
788            blob_header += pack('>I', self.header['size_dt_struct'])
789        return blob_header + header_adjust + blob_reserve_entries + \
790            blob_dt + blob_strings.encode('ascii')
791
792    def to_json(self):
793        """Ouput JSON"""
794        if self.rootnode is None:
795            return None
796        return self.rootnode.json_represent()
797
798    def resolve_path(self, path):
799        """Resolve path like /memory/reg and return either a FdtNode,
800            a FdtProperty or None"""
801        if self.rootnode is None:
802            return None
803        if not path.startswith('/'):
804            return None
805        if len(path) > 1 and path.endswith('/'):
806            path = path[:-1]
807        if path == '/':
808            return self.rootnode
809        curnode = self.rootnode
810        for subpath in path[1:].split('/'):
811            found = None
812            if not isinstance(curnode, FdtNode):
813                return None
814            for node in curnode:
815                if subpath == node.get_name():
816                    found = node
817                    break
818            if found is None:
819                return None
820            curnode = found
821        return curnode
822
823def _add_json_to_fdtnode(node, subjson):
824    """Populate FdtNode with JSON dict items"""
825    for (key, value) in subjson.items():
826        if isinstance(value, dict):
827            subnode = FdtNode(key)
828            subnode.set_parent_node(node)
829            node.append(subnode)
830            _add_json_to_fdtnode(subnode, value)
831        elif isinstance(value, list):
832            if len(value) < 2:
833                raise Exception("Invalid list for %s" % key)
834            if value[0] == "words":
835                words = [int(word, 16) for word in value[1:]]
836                node.append(FdtPropertyWords(key, words))
837            elif value[0] == "bytes":
838                bytez = [int(byte, 16) for byte in value[1:]]
839                node.append(FdtPropertyBytes(key, bytez))
840            elif value[0] == "strings":
841                node.append(FdtPropertyStrings(key, \
842                            [s for s in value[1:]]))
843            else:
844                raise Exception("Invalid list for %s" % key)
845        elif value is None:
846            node.append(FdtProperty(key))
847        else:
848            raise Exception("Invalid value for %s" % key)
849
850def FdtJsonParse(buf):
851    """Import FDT from JSON representation, see JSONDeviceTree.md for
852       structure and encoding
853       Returns an Fdt object
854    """
855    tree = json.loads(buf)
856
857    root = FdtNode('/')
858
859    _add_json_to_fdtnode(root, tree)
860
861    fdt = Fdt()
862    fdt.add_rootnode(root)
863    return fdt
864
865def FdtFsParse(path):
866    """Parse device tree filesystem and return a Fdt instance
867       Should be /proc/device-tree on a device, or the fusemount.py
868       mount point.
869    """
870    root = FdtNode("/")
871
872    if path.endswith('/'):
873        path = path[:-1]
874
875    nodes = {path: root}
876
877    for subpath, subdirs, files in os.walk(path):
878        if subpath not in nodes.keys():
879            raise Exception("os.walk error")
880        cur = nodes[subpath]
881        for f in files:
882            with open(subpath+'/'+f, 'rb') as content_file:
883                content = content_file.read()
884            prop = FdtProperty.new_raw_property(f, content)
885            cur.add_subnode(prop)
886        for subdir in subdirs:
887            subnode = FdtNode(subdir)
888            cur.add_subnode(subnode)
889            subnode.set_parent_node(cur)
890            nodes[subpath+'/'+subdir] = subnode
891
892    fdt = Fdt()
893    fdt.add_rootnode(root)
894    return fdt
895
896class FdtBlobParse(object):  # pylint: disable-msg=R0903
897    """Parse from file input"""
898
899    __fdt_header_format = ">IIIIIII"
900    __fdt_header_names = ('magic', 'totalsize', 'off_dt_struct',
901                          'off_dt_strings', 'off_mem_rsvmap', 'version',
902                          'last_comp_version')
903
904    __fdt_reserve_entry_format = ">QQ"
905    __fdt_reserve_entry_names = ('address', 'size')
906
907    __fdt_dt_cell_format = ">I"
908    __fdt_dt_prop_format = ">II"
909    __fdt_dt_tag_name = {FDT_BEGIN_NODE: 'node_begin',
910                         FDT_END_NODE: 'node_end',
911                         FDT_PROP: 'prop',
912                         FDT_NOP: 'nop',
913                         FDT_END: 'end'}
914
915    def __extract_fdt_header(self):
916        """Extract DTB header"""
917        header = Struct(self.__fdt_header_format)
918        header_entry = Struct(">I")
919        data = self.infile.read(header.size)
920        result = dict(zip(self.__fdt_header_names, header.unpack_from(data)))
921        if result['version'] >= 2:
922            data = self.infile.read(header_entry.size)
923            result['boot_cpuid_phys'] = header_entry.unpack_from(data)[0]
924        if result['version'] >= 3:
925            data = self.infile.read(header_entry.size)
926            result['size_dt_strings'] = header_entry.unpack_from(data)[0]
927        if result['version'] >= 17:
928            data = self.infile.read(header_entry.size)
929            result['size_dt_struct'] = header_entry.unpack_from(data)[0]
930        return result
931
932    def __extract_fdt_reserve_entries(self):
933        """Extract reserved memory entries"""
934        header = Struct(self.__fdt_reserve_entry_format)
935        entries = []
936        self.infile.seek(self.fdt_header['off_mem_rsvmap'])
937        while True:
938            data = self.infile.read(header.size)
939            result = dict(zip(self.__fdt_reserve_entry_names,
940                              header.unpack_from(data)))
941            if result['address'] == 0 and result['size'] == 0:
942                return entries
943            entries.append(result)
944
945    def __extract_fdt_nodename(self):
946        """Extract node name"""
947        data = ''
948        pos = self.infile.tell()
949        while True:
950            byte = self.infile.read(1)
951            if ord(byte) == 0:
952                break
953            data += byte.decode('ascii')
954        align_pos = pos + len(data) + 1
955        align_pos = (((align_pos) + ((4) - 1)) & ~((4) - 1))
956        self.infile.seek(align_pos)
957        return data
958
959    def __extract_fdt_string(self, prop_string_pos):
960        """Extract string from string pool"""
961        data = ''
962        pos = self.infile.tell()
963        self.infile.seek(self.fdt_header['off_dt_strings']+prop_string_pos)
964        while True:
965            byte = self.infile.read(1)
966            if ord(byte) == 0:
967                break
968            data += byte.decode('ascii')
969        self.infile.seek(pos)
970        return data
971
972    def __extract_fdt_prop(self):
973        """Extract property"""
974        prop = Struct(self.__fdt_dt_prop_format)
975        pos = self.infile.tell()
976        data = self.infile.read(prop.size)
977        (prop_size, prop_string_pos,) = prop.unpack_from(data)
978
979        prop_start = pos + prop.size
980        if self.fdt_header['version'] < 16 and prop_size >= 8:
981            prop_start = (((prop_start) + ((8) - 1)) & ~((8) - 1))
982
983        self.infile.seek(prop_start)
984        value = self.infile.read(prop_size)
985
986        align_pos = self.infile.tell()
987        align_pos = (((align_pos) + ((4) - 1)) & ~((4) - 1))
988        self.infile.seek(align_pos)
989
990        return (self.__extract_fdt_string(prop_string_pos), value)
991
992    def __extract_fdt_dt(self):
993        """Extract tags"""
994        cell = Struct(self.__fdt_dt_cell_format)
995        tags = []
996        self.infile.seek(self.fdt_header['off_dt_struct'])
997        while True:
998            data = self.infile.read(cell.size)
999            if len(data) < cell.size:
1000                break
1001            tag, = cell.unpack_from(data)
1002            # print "*** %s" % self.__fdt_dt_tag_name.get(tag, '')
1003            if self.__fdt_dt_tag_name.get(tag, '') in 'node_begin':
1004                name = self.__extract_fdt_nodename()
1005                if len(name) == 0:
1006                    name = '/'
1007                tags.append((tag, name))
1008            elif self.__fdt_dt_tag_name.get(tag, '') in ('node_end', 'nop'):
1009                tags.append((tag, ''))
1010            elif self.__fdt_dt_tag_name.get(tag, '') in 'end':
1011                tags.append((tag, ''))
1012                break
1013            elif self.__fdt_dt_tag_name.get(tag, '') in 'prop':
1014                propdata = self.__extract_fdt_prop()
1015                tags.append((tag, propdata))
1016            else:
1017                print("Unknown Tag %d" % tag)
1018        return tags
1019
1020    def __init__(self, infile):
1021        """Init with file input"""
1022        self.infile = infile
1023        self.fdt_header = self.__extract_fdt_header()
1024        if self.fdt_header['magic'] != FDT_MAGIC:
1025            raise Exception('Invalid Magic')
1026        if self.fdt_header['version'] > FDT_MAX_VERSION:
1027            raise Exception('Invalid Version %d' % self.fdt_header['version'])
1028        if self.fdt_header['last_comp_version'] > FDT_MAX_VERSION-1:
1029            raise Exception('Invalid last compatible Version %d' %
1030                            self.fdt_header['last_comp_version'])
1031        self.fdt_reserve_entries = self.__extract_fdt_reserve_entries()
1032        self.fdt_dt_tags = self.__extract_fdt_dt()
1033
1034    def __to_nodes(self):
1035        """Represent fdt as Node and properties structure
1036           Returns a set with the pre-node Nops, the Root Node,
1037            and the post-node Nops.
1038        """
1039        prenops = []
1040        postnops = []
1041        rootnode = None
1042        curnode = None
1043        for tag in self.fdt_dt_tags:
1044            if self.__fdt_dt_tag_name.get(tag[0], '') in 'node_begin':
1045                newnode = FdtNode(tag[1])
1046                if rootnode is None:
1047                    rootnode = newnode
1048                if curnode is not None:
1049                    curnode.add_subnode(newnode)
1050                    newnode.set_parent_node(curnode)
1051                curnode = newnode
1052            elif self.__fdt_dt_tag_name.get(tag[0], '') in 'node_end':
1053                if curnode is not None:
1054                    curnode = curnode.get_parent_node()
1055            elif self.__fdt_dt_tag_name.get(tag[0], '') in 'nop':
1056                if curnode is not None:
1057                    curnode.add_subnode(FdtNop())
1058                elif rootnode is not None:
1059                    postnops.append(FdtNop())
1060                else:
1061                    prenops.append(FdtNop())
1062            elif self.__fdt_dt_tag_name.get(tag[0], '') in 'prop':
1063                if curnode is not None:
1064                    curnode.add_raw_attribute(tag[1][0], tag[1][1])
1065            elif self.__fdt_dt_tag_name.get(tag[0], '') in 'end':
1066                continue
1067        return (prenops, rootnode, postnops)
1068
1069    def to_fdt(self):
1070        """Create a fdt object
1071            Returns a Fdt object
1072        """
1073        if self.fdt_header['version'] >= 2:
1074            boot_cpuid_phys = self.fdt_header['boot_cpuid_phys']
1075        else:
1076            boot_cpuid_phys = 0
1077        fdt = Fdt(version=self.fdt_header['version'],
1078                  last_comp_version=self.fdt_header['last_comp_version'],
1079                  boot_cpuid_phys=boot_cpuid_phys)
1080        (prenops, rootnode, postnops) = self.__to_nodes()
1081        fdt.add_rootnode(rootnode, prenops=prenops, postnops=postnops)
1082        fdt.add_reserve_entries(self.fdt_reserve_entries)
1083        return fdt
1084