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 defaultdict 8from functools import lru_cache 9from typing import Dict, List 10 11import logging 12 13from hardware.config import Config 14from hardware.device import WrappedNode 15from hardware.fdt import FdtParser 16from hardware.memory import Region 17 18 19def get_macro_str(macro: str) -> str: 20 ''' Helper function that returns the appropriate C preprocessor line for a given macro ''' 21 if macro is None: 22 return '' 23 24 if macro[0] == '!': 25 return '#ifndef ' + macro[1:] 26 return '#ifdef ' + macro 27 28 29def get_endif(macro: str) -> str: 30 ''' Helper function that returns the appropriate endif line for a given macro ''' 31 if macro is None: 32 return '' 33 34 return '#endif /* {} */'.format(macro) 35 36 37class KernelRegionGroup: 38 ''' wraps a contiguous region of memory that is mapped into the kernel. ''' 39 40 def __init__(self, region: Region, kernel_name: str, page_bits: int, max_size: int, condition_macro: str = None, user_ok: bool = False): 41 self.macro = condition_macro 42 self.desc = region.owner.path if region.owner else 'dynamically generated region' 43 self.kernel_offset = -1 44 self.page_bits = page_bits 45 self.labels = {} # dict of label => offset within region. 46 self.user_ok = user_ok 47 48 region.size = min(max_size, region.size) 49 aligned = region.align_size(page_bits) 50 self.size = aligned.size 51 self.base = aligned.base 52 self.regions = aligned.make_chunks(1 << page_bits) 53 self.labels[kernel_name] = region.base - aligned.base 54 55 def has_macro(self): 56 ''' True if this group has a macro ''' 57 return self.macro is not None 58 59 def take_labels(self, other_group: 'KernelRegionGroup'): 60 ''' Take another group's labels and add them to our own ''' 61 if self != other_group: 62 raise ValueError('need to have equal size and base to take labels') 63 for (k, v) in other_group.labels.items(): 64 self.labels[k] = v 65 self.desc += ', ' + other_group.desc 66 67 def get_macro(self): 68 ''' Get the #ifdef line for this region group ''' 69 return get_macro_str(self.macro) 70 71 def get_endif(self): 72 ''' Get the #endif line for this region group ''' 73 return get_endif(self.macro) 74 75 def set_kernel_offset(self, offset): 76 ''' Set the base offset that this region is mapped at in the kernel. 77 Returns the next free address in the kernel (i.e. base offset + region size) ''' 78 self.kernel_offset = offset 79 return offset + self.size 80 81 def get_labelled_addresses(self) -> Dict: 82 ''' Get a dict of address -> label for the kernel ''' 83 ret = {} 84 for (k, v) in self.labels.items(): 85 ret[v + self.kernel_offset] = k 86 return ret 87 88 def get_map_offset(self, reg): 89 ''' Get the offset that the given region is mapped at. ''' 90 index = self.regions.index(reg) 91 return self.kernel_offset + (index * (1 << self.page_bits)) 92 93 def get_desc(self): 94 ''' Get this region group's description ''' 95 return self.desc 96 97 def __repr__(self): 98 return 'KernelRegion(reg={},labels={})'.format(self.regions, self.labels) 99 100 def __eq__(self, other): 101 return other.base == self.base and other.size == self.size 102 103 104class KernelInterrupt: 105 ''' Represents an interrupt that is used by the kernel. ''' 106 107 def __init__(self, label: str, irq: int, prio: int = 0, sel_macro: str = None, false_irq: int = -1, enable_macro: str = None, desc: str = None): 108 self.label = label 109 self.irq = irq 110 self.prio = prio 111 self.sel_macro = sel_macro 112 self.false_irq = false_irq 113 self.enable_macro = enable_macro 114 self.desc = desc 115 116 def get_enable_macro_str(self): 117 ''' Get the enable macro #ifdef line ''' 118 return get_macro_str(self.enable_macro) 119 120 def has_enable(self): 121 ''' True if this interrupt has an enable macro ''' 122 return self.enable_macro is not None 123 124 def get_enable_endif(self): 125 ''' Get the enable macro #endif line ''' 126 return get_endif(self.enable_macro) 127 128 def get_sel_macro_str(self): 129 ''' Get the select macro #ifdef line ''' 130 return get_macro_str(self.sel_macro) 131 132 def has_sel(self): 133 ''' True if this interrupt has a select macro ''' 134 return self.sel_macro is not None 135 136 def get_sel_endif(self): 137 ''' Get the select macro #endif line ''' 138 return get_endif(self.sel_macro) 139 140 def __repr__(self): 141 return 'KernelInterrupt(label={},irq={},sel_macro={},false_irq={})'.format(self.label, self.irq, self.sel_macro, self.false_irq) 142 143 144class DeviceRule: 145 ''' Represents a single rule in hardware.yml ''' 146 147 def __init__(self, rule: dict, config: Config): 148 self.rule = rule 149 self.regions: Dict[int, Dict] = {} 150 self.interrupts = rule.get('interrupts', {}) 151 self.config = config 152 153 for reg in rule.get('regions', []): 154 self.regions[reg['index']] = reg 155 156 @lru_cache() 157 def get_regions(self, node: WrappedNode) -> List[KernelRegionGroup]: 158 ''' Returns a list of KernelRegionGroups that this rule specifies should be mapped into the kernel for this device. ''' 159 ret = [] 160 regions = node.get_regions() 161 162 for (i, rule) in self.regions.items(): 163 if i >= len(regions): 164 # XXX: skip this rule silently 165 continue 166 reg = regions[i] 167 168 kernel_name = rule['kernel'] 169 user = rule.get('user', False) 170 macro = rule.get('macro', None) 171 max_size = 1 << self.config.get_device_page_bits() 172 if 'kernel_size' in rule: 173 max_size = rule['kernel_size'] 174 elif max_size < reg.size: 175 logging.warning( 176 "Only mapping {}/{} bytes from node {}, region {}. Set kernel_size in YAML to silence.".format(max_size, reg.size, node.path, i)) 177 ret.append(KernelRegionGroup(reg, kernel_name, 178 self.config.get_device_page_bits(), max_size, macro, user)) 179 180 return ret 181 182 @lru_cache() 183 def get_interrupts(self, tree: FdtParser, node: WrappedNode) -> List[KernelInterrupt]: 184 ''' Returns a list of KernelInterrupts that this rule says are used by the kernel for this device. ''' 185 ret = [] 186 interrupts = node.get_interrupts(tree) 187 188 for name, rule in self.interrupts.items(): 189 irq_desc = '{} generated from {}'.format(name, node.path) 190 if type(rule) == dict: 191 en_macro = rule.get('enable_macro', None) 192 if rule['index'] >= len(interrupts): 193 # XXX: skip this rule silently. 194 continue 195 defaultIrq = interrupts[rule['index']] 196 sel_macro = rule.get('sel_macro', None) 197 falseIrq = interrupts[rule['undef_index']] if 'undef_index' in rule else -1 198 prio = rule.get('priority', 0) 199 irq = KernelInterrupt(name, defaultIrq, prio, sel_macro, 200 falseIrq, en_macro, desc=irq_desc) 201 elif type(rule) == int: 202 if rule >= len(interrupts): 203 # XXX: skip this rule silently. 204 continue 205 irq = KernelInterrupt(name, interrupts[rule], desc=irq_desc) 206 else: # rule == 'boot-cpu' 207 affinities = node.get_interrupt_affinities() 208 boot_cpu = tree.get_boot_cpu() 209 idx = affinities.index(boot_cpu) 210 irq = KernelInterrupt(name, interrupts[idx]) 211 ret.append(irq) 212 return ret 213 214 215class HardwareYaml: 216 ''' Represents the hardware configuration file ''' 217 218 def __init__(self, yaml: dict, config: Config): 219 self.rules = {} 220 for dev in yaml['devices']: 221 rule = DeviceRule(dev, config) 222 for compat in dev['compatible']: 223 self.rules[compat] = rule 224 225 def get_rule(self, device: WrappedNode) -> DeviceRule: 226 ''' Returns the matching DeviceRule for this device. ''' 227 if not device.has_prop('compatible'): 228 raise ValueError( 229 'Not sure what to do with node {} with no compatible!'.format(device.path)) 230 231 for compat in device.get_prop('compatible').strings: 232 if compat in self.rules: 233 return self.rules[compat] 234 235 raise ValueError('Failed to match compatibles "{}" for node {}!'.format( 236 ', '.join(device.get_prop('compatible').strings), device.path)) 237 238 def get_matched_compatible(self, device: WrappedNode) -> str: 239 ''' Returns the best matching compatible string for this device ''' 240 if not device.has_prop('compatible'): 241 raise ValueError( 242 'Not sure what to do with node {} with no compatible!'.format(device.path)) 243 for compat in device.get_prop('compatible').strings: 244 if compat in self.rules: 245 return compat 246 return None 247