seL4/tools/hardware/device.py
Gerwin Klein 79da079239 Convert license tags to SPDX identifiers
This commit also converts our own copyright headers to directly use
SPDX, but leaves all other copyright header intact, only adding the
SPDX ident. As far as possible this commit also merges multiple
Data61 copyright statements/headers into one for consistency.
2020-03-09 13:21:49 +08:00

229 lines
8.4 KiB
Python

#
# Copyright 2020, Data61, CSIRO (ABN 41 687 119 230)
#
# SPDX-License-Identifier: GPL-2.0-only
#
from collections import OrderedDict
from typing import Any, Dict, Generator, List, Tuple, cast
import logging
import pyfdt.pyfdt
from hardware.memory import Region
class WrappedNode:
''' A wrapper around an underlying pyfdt node '''
# TODO: Python 3.7 with 'from __future__ import annotations' will remove the need
# to put 'WrappedNode' in a string.
def __init__(self, node: pyfdt.pyfdt.FdtNode, parent: 'WrappedNode', path: str):
self.node = node
self.parent = parent
self.depth = 0
self.path = path
self.children: Dict[str, 'WrappedNode'] = OrderedDict()
self.props: Dict[str, pyfdt.pyfdt.FdtProperty] = {}
for prop in node:
if not isinstance(prop, pyfdt.pyfdt.FdtProperty):
continue
self.props[prop.get_name()] = prop
if parent is not None:
parent.add_child(self)
self.depth = parent.depth + 1
self.is_cpu_addressable: bool = parent.is_cpu_addressable and \
'ranges' in self.props
else:
self.is_cpu_addressable = True # root node is always cpu addressable
def add_child(self, child: 'WrappedNode'):
''' Add a child to this node '''
self.children[child.node.get_name()] = child
def get_prop(self, name: str) -> pyfdt.pyfdt.FdtProperty:
''' Returns prop with given name, or throws KeyError if not found '''
return self.props[name]
def has_prop(self, name: str) -> bool:
''' Returns True if prop with given name exists, otherwise False. '''
return name in self.props
def get_phandle(self) -> int:
''' Return this node's phandle '''
if 'phandle' not in self.props:
return False
return self.props['phandle'].words[0]
def get_interrupt_parent(self) -> int:
''' Return this node's interrupt parent's phandle '''
if 'interrupt-parent' not in self.props:
return self.parent.get_interrupt_parent()
return self.props['interrupt-parent'].words[0]
def is_mmio_device(self) -> bool:
''' Returns True if this node is an MMIO device '''
return self.is_cpu_addressable
def recursive_get_addr_cells(self) -> int:
''' This returns the #address-cells of this node, or otherwise
the parent. Note that this contravenes the spec in that
the default is meant to be 2 if unspecified, rather than the parent's value.
This is used by the IRQ parsing code to match Linux's behaviour. '''
if '#address-cells' in self.props:
return self.props['#address-cells'].words[0]
if self.parent is None:
return 2
return self.parent.recursive_get_addr_cells()
def get_addr_cells(self) -> int:
''' Return the number of 32-bit cells that children of this node
use to specify addresses '''
if '#address-cells' in self.props:
return self.props['#address-cells'].words[0]
# see devicetree spec v0.2, 2.3.5 "#address-cells and #size-cells"
return 2
def get_size_cells(self) -> int:
''' Return the number of 32-bit cells that children of this node
use to specify sizes. '''
if '#size-cells' in self.props:
return self.props['#size-cells'].words[0]
# see devicetree spec v0.2, 2.3.5 "#address-cells and #size-cells"
return 1
def get_regions(self) -> List[Region]:
if 'reg' not in self.props:
return []
reg = []
prop = list(self.props['reg'].words)
sizes = (self.parent.get_addr_cells(), self.parent.get_size_cells())
for r in Utils.intarray_iter(prop, sizes):
reg.append(Region(self.parent._translate_child_address(r[0]), r[1], self))
return reg
def parse_address(self, array) -> int:
''' parse a single address from the array. will pop values from the array '''
size = self.parent.get_addr_cells()
return Utils.make_number(size, array)
def get_interrupts(self, tree: 'FdtParser') -> List[int]:
irqs = []
if 'interrupts-extended' in self.props:
data = list(self.props['interrupts-extended'].words)
while len(data) > 0:
phandle = data.pop(0)
interrupt_parent = tree.get_irq_controller(phandle)
irqs.append(interrupt_parent.parse_irq(self, data))
elif 'interrupts' in self.props:
data = list(self.props['interrupts'].words)
interrupt_parent = tree.get_irq_controller(self.get_interrupt_parent())
while len(data) > 0:
irqs.append(interrupt_parent.parse_irq(self, data))
return irqs
def get_interrupt_affinities(self) -> List[int]:
if not self.has_prop('interrupt-affinity'):
return []
return list(self.get_prop('interrupt-affinity').words)
def visit(self, visitor: Any):
''' Visit this node and all its children '''
ret = [visitor(self)]
if ret[0] is None:
ret = []
for child in self.children.values():
ret += child.visit(visitor)
return ret
def __iter__(self) -> Generator['WrappedNode', None, None]:
''' Iterate over all immediate children of this node '''
for child in self.children.values():
yield child
def _translate_child_address(self, addr: int) -> int:
''' translate an address in this node's address space
to the parent's address space. '''
# see devicetree spec v0.2, 2.3.8 "ranges"
if self.parent is None:
# the root node does not need to do any translation of addresses
return addr
if 'ranges' not in self.props:
logging.warning('cannot translate address through node ' + self.path)
return -1
if not isinstance(self.props['ranges'], pyfdt.pyfdt.FdtPropertyWords):
return self.parent._translate_child_address(addr)
addr = Utils.translate_address(self, addr)
return self.parent._translate_child_address(addr)
def __hash__(self):
return hash(self.path)
class Utils:
@staticmethod
def translate_address(node: WrappedNode, addr: int) -> int:
ranges_prop = node.props['ranges']
data = list(ranges_prop.words)
device_type = cast(pyfdt.pyfdt.FdtPropertyStrings, node.get_prop(
'device_type')).strings[0] if node.has_prop('device_type') else None
is_pci = device_type in ('pci', 'pciex')
# ranges is a list of tuples with the following format:
# <child-addr> <parent-addr> <length>
# child-addr is self.get_addr_cells() long
# parent-addr is self.parent.get_addr_cells() long
# length is self.get_size_cells() long
# the address 'child-addr' is at 'parent-addr' in the parent node.
child_addr_cells = node.get_addr_cells()
parent_addr_cells = node.parent.get_addr_cells()
size_cells = node.get_size_cells()
if is_pci:
# PCI needs to be handled specially, see
# https://www.devicetree.org/open-firmware/bindings/pci/pci2_1.pdf
child_addr_cells -= 1
# the top 32 bits for PCI devices contains configuration flags.
# we ignore it when doing this check.
addr &= (1 << (4 * child_addr_cells)) - 1
while len(data) > 0:
child_addr = Utils.make_number(child_addr_cells, data)
parent_addr = Utils.make_number(parent_addr_cells, data)
length = Utils.make_number(size_cells, data)
if child_addr <= addr < (child_addr + length):
return addr - child_addr + parent_addr
logging.warning("Could not translate 0x{:x} at {}, not translating".format(addr, node.path))
return addr
@staticmethod
def make_number(cells: int, array: List[int]) -> int:
ret = 0
for i in range(cells):
ret = (ret << 32)
ret |= array.pop(0)
return ret
@staticmethod
def intarray_iter(array: List[int], sizes: Tuple[int, ...]) -> Generator[List[int], None, None]:
i = 0
res = []
while len(array) > 0:
res.append(Utils.make_number(sizes[i], array))
i += 1
if i >= len(sizes):
yield res
i = 0
res = []