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