1#
2# Copyright 2020, Data61, CSIRO (ABN 41 687 119 230)
3#
4# SPDX-License-Identifier: GPL-2.0-only
5#
6
7from collections import OrderedDict
8from typing import Any, Dict, Generator, List, Tuple, cast
9
10import logging
11import pyfdt.pyfdt
12
13from hardware.memory import Region
14
15
16class WrappedNode:
17    ''' A wrapper around an underlying pyfdt node '''
18
19    # TODO: Python 3.7 with 'from __future__ import annotations' will remove the need
20    # to put 'WrappedNode' in a string.
21    def __init__(self, node: pyfdt.pyfdt.FdtNode, parent: 'WrappedNode', path: str):
22        self.node = node
23        self.parent = parent
24        self.depth = 0
25        self.path = path
26        self.children: Dict[str, 'WrappedNode'] = OrderedDict()
27        self.props: Dict[str, pyfdt.pyfdt.FdtProperty] = {}
28        for prop in node:
29            if not isinstance(prop, pyfdt.pyfdt.FdtProperty):
30                continue
31            self.props[prop.get_name()] = prop
32
33        if parent is not None:
34            parent.add_child(self)
35            self.depth = parent.depth + 1
36            self.is_cpu_addressable: bool = parent.is_cpu_addressable and \
37                'ranges' in self.props
38        else:
39            self.is_cpu_addressable = True  # root node is always cpu addressable
40
41    def add_child(self, child: 'WrappedNode'):
42        ''' Add a child to this node '''
43        self.children[child.node.get_name()] = child
44
45    def get_prop(self, name: str) -> pyfdt.pyfdt.FdtProperty:
46        ''' Returns prop with given name, or throws KeyError if not found '''
47        return self.props[name]
48
49    def has_prop(self, name: str) -> bool:
50        ''' Returns True if prop with given name exists, otherwise False. '''
51        return name in self.props
52
53    def get_phandle(self) -> int:
54        ''' Return this node's phandle '''
55        if 'phandle' not in self.props:
56            return False
57
58        return self.props['phandle'].words[0]
59
60    def get_interrupt_parent(self) -> int:
61        ''' Return this node's interrupt parent's phandle '''
62        if 'interrupt-parent' not in self.props:
63            return self.parent.get_interrupt_parent()
64        return self.props['interrupt-parent'].words[0]
65
66    def is_mmio_device(self) -> bool:
67        ''' Returns True if this node is an MMIO device '''
68        return self.is_cpu_addressable
69
70    def recursive_get_addr_cells(self) -> int:
71        ''' This returns the #address-cells of this node, or otherwise
72        the parent. Note that this contravenes the spec in that
73        the default is meant to be 2 if unspecified, rather than the parent's value.
74        This is used by the IRQ parsing code to match Linux's behaviour. '''
75        if '#address-cells' in self.props:
76            return self.props['#address-cells'].words[0]
77        if self.parent is None:
78            return 2
79
80        return self.parent.recursive_get_addr_cells()
81
82    def get_addr_cells(self) -> int:
83        ''' Return the number of 32-bit cells that children of this node
84        use to specify addresses '''
85        if '#address-cells' in self.props:
86            return self.props['#address-cells'].words[0]
87
88        # see devicetree spec v0.2, 2.3.5 "#address-cells and #size-cells"
89        return 2
90
91    def get_size_cells(self) -> int:
92        ''' Return the number of 32-bit cells that children of this node
93        use to specify sizes. '''
94        if '#size-cells' in self.props:
95            return self.props['#size-cells'].words[0]
96
97        # see devicetree spec v0.2, 2.3.5 "#address-cells and #size-cells"
98        return 1
99
100    def get_regions(self) -> List[Region]:
101        if 'reg' not in self.props:
102            return []
103
104        reg = []
105        prop = list(self.props['reg'].words)
106        sizes = (self.parent.get_addr_cells(), self.parent.get_size_cells())
107        for r in Utils.intarray_iter(prop, sizes):
108            reg.append(Region(self.parent._translate_child_address(r[0]), r[1], self))
109        return reg
110
111    def parse_address(self, array) -> int:
112        ''' parse a single address from the array. will pop values from the array '''
113        size = self.parent.get_addr_cells()
114        return Utils.make_number(size, array)
115
116    def get_interrupts(self, tree: 'FdtParser') -> List[int]:
117        irqs = []
118        if 'interrupts-extended' in self.props:
119            data = list(self.props['interrupts-extended'].words)
120            while len(data) > 0:
121                phandle = data.pop(0)
122                interrupt_parent = tree.get_irq_controller(phandle)
123                irqs.append(interrupt_parent.parse_irq(self, data))
124        elif 'interrupts' in self.props:
125            data = list(self.props['interrupts'].words)
126            interrupt_parent = tree.get_irq_controller(self.get_interrupt_parent())
127            while len(data) > 0:
128                irqs.append(interrupt_parent.parse_irq(self, data))
129        return irqs
130
131    def get_interrupt_affinities(self) -> List[int]:
132        if not self.has_prop('interrupt-affinity'):
133            return []
134        return list(self.get_prop('interrupt-affinity').words)
135
136    def visit(self, visitor: Any):
137        ''' Visit this node and all its children '''
138        ret = [visitor(self)]
139        if ret[0] is None:
140            ret = []
141        for child in self.children.values():
142            ret += child.visit(visitor)
143        return ret
144
145    def __iter__(self) -> Generator['WrappedNode', None, None]:
146        ''' Iterate over all immediate children of this node '''
147        for child in self.children.values():
148            yield child
149
150    def _translate_child_address(self, addr: int) -> int:
151        ''' translate an address in this node's address space
152            to the parent's address space. '''
153        # see devicetree spec v0.2, 2.3.8 "ranges"
154        if self.parent is None:
155            # the root node does not need to do any translation of addresses
156            return addr
157
158        if 'ranges' not in self.props:
159            logging.warning('cannot translate address through node ' + self.path)
160            return -1
161
162        if not isinstance(self.props['ranges'], pyfdt.pyfdt.FdtPropertyWords):
163            return self.parent._translate_child_address(addr)
164
165        addr = Utils.translate_address(self, addr)
166        return self.parent._translate_child_address(addr)
167
168    def __hash__(self):
169        return hash(self.path)
170
171
172class Utils:
173    @staticmethod
174    def translate_address(node: WrappedNode, addr: int) -> int:
175        ranges_prop = node.props['ranges']
176        data = list(ranges_prop.words)
177
178        device_type = cast(pyfdt.pyfdt.FdtPropertyStrings, node.get_prop(
179            'device_type')).strings[0] if node.has_prop('device_type') else None
180
181        is_pci = device_type in ('pci', 'pciex')
182        # ranges is a list of tuples with the following format:
183        # <child-addr> <parent-addr> <length>
184        # child-addr is self.get_addr_cells() long
185        # parent-addr is self.parent.get_addr_cells() long
186        # length is self.get_size_cells() long
187        # the address 'child-addr' is at 'parent-addr' in the parent node.
188        child_addr_cells = node.get_addr_cells()
189        parent_addr_cells = node.parent.get_addr_cells()
190        size_cells = node.get_size_cells()
191
192        if is_pci:
193            # PCI needs to be handled specially, see
194            # https://www.devicetree.org/open-firmware/bindings/pci/pci2_1.pdf
195            child_addr_cells -= 1
196            # the top 32 bits for PCI devices contains configuration flags.
197            # we ignore it when doing this check.
198            addr &= (1 << (4 * child_addr_cells)) - 1
199
200        while len(data) > 0:
201            child_addr = Utils.make_number(child_addr_cells, data)
202            parent_addr = Utils.make_number(parent_addr_cells, data)
203            length = Utils.make_number(size_cells, data)
204
205            if child_addr <= addr < (child_addr + length):
206                return addr - child_addr + parent_addr
207        logging.warning("Could not translate 0x{:x} at {}, not translating".format(addr, node.path))
208        return addr
209
210    @staticmethod
211    def make_number(cells: int, array: List[int]) -> int:
212        ret = 0
213        for i in range(cells):
214            ret = (ret << 32)
215            ret |= array.pop(0)
216        return ret
217
218    @staticmethod
219    def intarray_iter(array: List[int], sizes: Tuple[int, ...]) -> Generator[List[int], None, None]:
220        i = 0
221        res = []
222        while len(array) > 0:
223            res.append(Utils.make_number(sizes[i], array))
224
225            i += 1
226            if i >= len(sizes):
227                yield res
228                i = 0
229                res = []
230