1#
2# Copyright 2020, Data61, CSIRO (ABN 41 687 119 230)
3#
4# SPDX-License-Identifier: GPL-2.0-only
5#
6
7from typing import Any, Dict, IO, List
8import pyfdt.pyfdt
9
10from hardware.device import WrappedNode
11from hardware.irq import create_irq_controller, IrqController
12
13
14class FdtParser:
15    ''' Parse a DTB into a python representation '''
16
17    def __init__(self, dtb_file: IO[bytes]):
18        self.fdt = pyfdt.pyfdt.FdtBlobParse(dtb_file).to_fdt()
19        self.wrapped_root: WrappedNode
20        self.by_phandle: Dict[int, WrappedNode] = {}
21        self.by_path: Dict[str, WrappedNode] = {}
22        self.irq_controllers: Dict[int, IrqController] = {}
23        # initialise the "nice" representation of the tree
24        self._walk()
25
26    def _walk(self):
27        ''' walk the underlying fdt, constructing wrapped nodes as we go '''
28        root = self.fdt.get_rootnode()
29        self.wrapped_root = WrappedNode(root, None, '/')
30
31        # for easier parent lookups
32        self.by_path = {'/': self.wrapped_root}
33        for (name, node) in root.walk():
34            if not isinstance(node, pyfdt.pyfdt.FdtNode):
35                continue
36            parent_path = name.rsplit('/', 1)[0]
37            if parent_path == '':
38                parent_path = '/'
39
40            parent = self.by_path[parent_path]
41            wrapped = WrappedNode(node, parent, name)
42            self.by_path[name] = wrapped
43
44            if not wrapped.get_phandle():
45                continue
46            self.by_phandle[wrapped.get_phandle()] = wrapped
47            # We only care about interrupt controllers with phandles -
48            # if an interrupt controller has no phandle, it isn't used anywhere
49            # and so we can safely ignore it.
50            if wrapped.has_prop('interrupt-controller') or wrapped.has_prop('interrupt-map'):
51                self.irq_controllers[wrapped.get_phandle()] = create_irq_controller(wrapped, self)
52
53    def get_phandle(self, phandle: int) -> WrappedNode:
54        ''' Look up a node by phandle '''
55        return self.by_phandle[phandle]
56
57    def get_path(self, path: str) -> WrappedNode:
58        ''' Look up a node by path '''
59        return self.by_path.get(path, None)
60
61    def get_irq_controller(self, phandle: int) -> IrqController:
62        ''' Get an IRQ controller by phandle '''
63        return self.irq_controllers[phandle]
64
65    def lookup_alias(self, alias: str) -> str:
66        ''' Look up a node by its alias '''
67        alias = alias.split(':')[0]
68        aliases = self.get_path('/aliases')
69
70        if aliases is None or not aliases.has_prop(alias):
71            raise KeyError('Unmatched alias {}'.format(alias))
72
73        prop = aliases.get_prop(alias)
74        return prop.strings[0]
75
76    def get_kernel_devices(self) -> List[WrappedNode]:
77        return self.get_devices_list('seL4,kernel-devices')
78
79    def get_elfloader_devices(self) -> List[WrappedNode]:
80        return self.get_devices_list('seL4,elfloader-devices')
81
82    def get_devices_list(self, prop) -> List[WrappedNode]:
83        ''' Returns a list of devices that are used by the kernel '''
84        chosen = self.get_path('/chosen')
85        if not chosen.has_prop(prop):
86            return []
87
88        ret = []
89        paths = chosen.get_prop(prop).strings
90
91        for path in paths:
92            if path[0] != '/':
93                path = self.lookup_alias(path)
94            ret.append(self.get_path(path))
95        return ret
96
97    def get_boot_cpu(self) -> int:
98        ''' Returns phandle of the cpu node specified by the seL4,boot-cpu property '''
99        chosen = self.get_path('/chosen')
100        if not chosen.has_prop('seL4,boot-cpu'):
101            raise KeyError('must specify seL4,boot-cpu in /chosen to get boot cpu')
102
103        prop = chosen.get_prop('seL4,boot-cpu')
104        return prop.words[0]
105
106    def visit(self, visitor: Any):
107        ''' Walk the tree, calling the given visitor function on each node '''
108        return self.wrapped_root.visit(visitor)
109