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
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):
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