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