seL4/tools/hardware/device.py
Simon Shields 75f2c54b42 tools: rewrite hardware_gen.py
This is almost a complete rewrite from the old hardware_gen.py.

It separates the 'parse DT' stage from the 'generate output'
devices more strictly, and is hopefully easier to understand and
easier to extend.

We also no longer generate the 'devices' list (in YAML)
or the dev_p_regs array (in C), as the kernel will implicitly
expose all non-RAM untypeds as devices.
2019-11-12 16:18:33 +11:00

226 lines
8.3 KiB
Python

#
# Copyright 2019, Data61
# Commonwealth Scientific and Industrial Research Organisation (CSIRO)
# ABN 41 687 119 230.
#
# This software may be distributed and modified according to the terms of
# the GNU General Public License version 2. Note that NO WARRANTY is provided.
# See "LICENSE_GPLv2.txt" for details.
#
# @TAG(DATA61_GPL)
#
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 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 '''
visitor(self)
for child in self.children.values():
child.visit(visitor)
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 = []