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