1# Copyright (c) 2016,2019 ARM Limited
2# All rights reserved.
3#
4# The license below extends only to copyright in the software and shall
5# not be construed as granting a license to any other intellectual
6# property including but not limited to intellectual property relating
7# to a hardware implementation of the functionality of the software
8# licensed hereunder.  You may use the software subject to the license
9# terms below provided that you ensure that this notice is replicated
10# unmodified and in its entirety in all distributions of the software,
11# modified or unmodified, in source code or in binary form.
12#
13# Redistribution and use in source and binary forms, with or without
14# modification, are permitted provided that the following conditions are
15# met: redistributions of source code must retain the above copyright
16# notice, this list of conditions and the following disclaimer;
17# redistributions in binary form must reproduce the above copyright
18# notice, this list of conditions and the following disclaimer in the
19# documentation and/or other materials provided with the distribution;
20# neither the name of the copyright holders nor the names of its
21# contributors may be used to endorse or promote products derived from
22# this software without specific prior written permission.
23#
24# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
27# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
28# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
30# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
31# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
32# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
33# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35#
36# Author: Glenn Bergmans
37
38import six
39if six.PY3:
40    long = int
41
42from m5.ext.pyfdt import pyfdt
43import re
44import os
45from m5.SimObject import SimObject
46
47class FdtProperty(pyfdt.FdtProperty):
48    """Create a property without values."""
49    pass
50
51class FdtPropertyWords(pyfdt.FdtPropertyWords):
52    """Create a property with word (32-bit unsigned) values."""
53    def __init__(self, name, words):
54        if type(words) != list:
55            words = [words]
56        # Make sure all values are ints (use automatic base detection if the
57        # type is str)
58        words = [long(w, base=0) if type(w) == str else long(w) for w in words]
59        super(FdtPropertyWords, self).__init__(name, words)
60
61class FdtPropertyStrings(pyfdt.FdtPropertyStrings):
62    """Create a property with string values."""
63
64    def __init__(self, name, strings):
65        if type(strings) == str:
66            strings = [strings]
67        strings = [str(string) for string in strings] # Make all values strings
68        super(FdtPropertyStrings, self).__init__(name, strings)
69
70class FdtPropertyBytes(pyfdt.FdtPropertyBytes):
71    """Create a property with integer (8-bit signed) values."""
72
73    def __init__(self, name, values):
74        if type(values) != list:
75            values = [values]
76        # Make sure all values are ints (use automatic base detection if the
77        # type is str)
78        values = [int(v, base=0)
79                   if isinstance(v, str) else int(v) for v in values]
80        super(FdtPropertyBytes, self).__init__(name, values)
81
82class FdtState(object):
83    """Class for maintaining state while recursively generating a flattened
84    device tree. The state tracks address, size and CPU address cell sizes, and
85    maintains a dictionary of allocated phandles."""
86
87    phandle_counter = 0
88    phandles = dict()
89
90    def __init__(self, **kwargs):
91        """Instantiate values of this state. The state can only be initialized
92        once."""
93
94        self.addr_cells = kwargs.pop('addr_cells', 0)
95        self.size_cells = kwargs.pop('size_cells', 0)
96        self.cpu_cells = kwargs.pop('cpu_cells', 0)
97        self.interrupt_cells = kwargs.pop('interrupt_cells', 0)
98
99    def phandle(self, obj):
100        """Return a unique phandle number for a key. The key can be a SimObject
101        or any value that is castable to a string. If the phandle doesn't exist
102        a new one is created, otherwise the existing one is returned."""
103
104        if isinstance(obj, SimObject):
105            key = str(id(obj))
106        else:
107            try:
108                key = str(obj)
109            except ValueError:
110                raise ValueError('Phandle keys must be castable to str')
111
112        if not key in FdtState.phandles:
113            FdtState.phandle_counter += 1
114
115        return FdtState.phandles.setdefault(key, FdtState.phandle_counter)
116
117    def resetPhandles(self):
118        FdtState.phandle_counter = 0
119        FdtState.phandles = dict()
120
121    def int_to_cells(self, value, cells):
122        """Helper function for: generates a list of 32 bit cells from an int,
123        used to split up addresses in appropriate 32 bit chunks."""
124        value = long(value)
125
126        if (value >> (32 * cells)) != 0:
127            fatal("Value %d doesn't fit in %d cells" % (value, cells))
128
129        return [(value >> 32*(x-1)) & 0xFFFFFFFF for x in range(cells, 0, -1)]
130
131    def addrCells(self, addr):
132        """Format an integer type according to the address_cells value of this
133        state."""
134        return self.int_to_cells(addr, self.addr_cells)
135
136    def CPUAddrCells(self, addr):
137        """Format an integer type according to the cpu_cells value of this
138        state."""
139        return self.int_to_cells(addr, self.cpu_cells)
140
141    def sizeCells(self, size):
142        """Format an integer type according to the size_cells value of this
143        state."""
144        return self.int_to_cells(size, self.size_cells)
145
146    def interruptCells(self, interrupt):
147        """Format an integer type according to the interrupt_cells value
148        of this state."""
149        return self.int_to_cells(interrupt, self.interrupt_cells)
150
151    def addrCellsProperty(self):
152        """Return an #address-cells property with the value of this state."""
153        return FdtPropertyWords("#address-cells", self.addr_cells)
154
155    def sizeCellsProperty(self):
156        """Return an #size-cells property with the value of this state."""
157        return FdtPropertyWords("#size-cells", self.size_cells)
158
159    def CPUCellsProperty(self):
160        """Return an #address-cells property for cpu nodes with the value
161        of this state."""
162        return FdtPropertyWords("#address-cells", self.cpu_cells)
163
164    def interruptCellsProperty(self):
165        """Return an #interrupt-cells property for cpu nodes with the value
166        of this state."""
167        return FdtPropertyWords("#interrupt-cells", self.interrupt_cells)
168
169
170class FdtNop(pyfdt.FdtNop):
171    """Create an empty node."""
172    pass
173
174class FdtNode(pyfdt.FdtNode):
175    def __init__(self, name, obj=None):
176        """Create a new node and immediately set the phandle property, if obj
177        is supplied"""
178        super(FdtNode, self).__init__(name)
179        if obj != None:
180            self.appendPhandle(obj)
181
182    def append(self, subnodes):
183        """Change the behavior of the normal append to override if a node with
184        the same name already exists or merge if the name exists and is a node
185        type. Can also take a list of subnodes, that each get appended."""
186        if not hasattr(subnodes, '__iter__'):
187            subnodes = [subnodes]
188
189        for subnode in subnodes:
190            try:
191                if not issubclass(type(subnode), pyfdt.FdtNop):
192                    index = self.index(subnode.name)
193                    item = self.pop(index)
194                else:
195                    item = None
196            except ValueError:
197                item = None
198
199            if isinstance(item,  pyfdt.FdtNode) and \
200               isinstance(subnode,  pyfdt.FdtNode):
201                item.merge(subnode)
202                subnode = item
203
204            super(FdtNode, self).append(subnode)
205
206    def appendList(self, subnode_list):
207        """Append all properties/nodes in the iterable."""
208        for subnode in subnode_list:
209            self.append(subnode)
210
211    def appendCompatible(self, compatible):
212        """Append a compatible property with the supplied compatibility
213        strings."""
214        if isinstance(compatible, str):
215            compatible = [compatible]
216        self.append(FdtPropertyStrings('compatible', compatible))
217
218    def appendPhandle(self, obj):
219        """Append a phandle property to this node with the phandle of the
220        supplied object."""
221        # Create a bogus state because we only need the Phandle dictionary
222        state = FdtState(addr_cells=1, size_cells=1, cpu_cells=1)
223
224        phandle = state.phandle(obj)
225        self.append(FdtPropertyWords("phandle", [phandle]))
226
227class Fdt(pyfdt.Fdt):
228    def sortNodes(self, node):
229        """Move all properties to the beginning and subnodes to the end
230        while maintaining the order of the subnodes. DTB files require the
231        properties to go before the nodes, but the PyFdt doesn't account for
232        defining nodes and properties in a random order."""
233        properties = FdtNode(node.name)
234        subnodes = FdtNode(node.name)
235
236        while len(node):
237            subnode = node.pop(0)
238            if issubclass(type(subnode), pyfdt.FdtNode):
239                subnode = self.sortNodes(subnode)
240                subnodes.append(subnode)
241            else:
242                properties.append(subnode)
243
244        properties.merge(subnodes)
245
246        return properties
247
248    def add_rootnode(self, rootnode, prenops=None, postnops=None):
249        """First sort the device tree, so that properties are before nodes."""
250        rootnode = self.sortNodes(rootnode)
251        super(Fdt, self).add_rootnode(rootnode, prenops, postnops)
252
253    def writeDtbFile(self, filename):
254        """Convert the device tree to DTB and write to a file."""
255        filename = os.path.realpath(filename)
256        try:
257            with open(filename, 'wb') as f:
258                f.write(self.to_dtb())
259            return filename
260        except IOError:
261            raise RuntimeError("Failed to open DTB output file")
262
263    def writeDtsFile(self, filename):
264        """Convert the device tree to DTS and write to a file."""
265        filename = os.path.realpath(filename)
266        try:
267            with open(filename, 'w') as f:
268                f.write(self.to_dts())
269            return filename
270        except IOError:
271            raise RuntimeError("Failed to open DTS output file")
272