1# Copyright (C) 2021-2022 Intel Corporation.
2#
3# SPDX-License-Identifier: BSD-3-Clause
4#
5
6import logging
7from copy import copy
8from math import floor
9
10from .exception import *
11from .stream import Stream
12
13class NamedDecl:
14    @staticmethod
15    def object_type():
16        return 0
17
18    def __init__(self, name, tree):
19        self.tree = tree
20        self.name = name
21
22    def dump(self):
23        print(f"{self.name}: {self.__class__.__name__}")
24
25class FieldDecl(NamedDecl):
26    def __init__(self, name, length, tree):
27        super().__init__(name, tree)
28        self.length = length
29
30    def dump(self):
31        print(f"{self.name}: {self.__class__.__name__}, {self.length} bits")
32
33class OperationRegionDecl(NamedDecl):
34    @staticmethod
35    def object_type():
36        return 10
37
38    def __init__(self, name, tree):
39        super().__init__(name, tree)
40
41class OperationFieldDecl(NamedDecl):
42    def __init__(self, name, length, tree):
43        super().__init__(name, tree)
44        self.region = None
45        self.offset = None
46        self.length = length
47        self.access_width = None
48        self.parent_tree = None
49
50    def set_location(self, region, offset, access_width):
51        self.region = region
52        self.offset = offset
53        self.access_width = access_width
54
55    def set_indexed_location(self, index_register, data_register, index, access_width):
56        self.region = (index_register, data_register)
57        self.offset = index
58        self.access_width = access_width
59
60    def dump(self):
61        if self.region and self.offset:
62            bit_index = self.offset
63            byte_index = floor(bit_index / 8)
64            offset_in_byte = bit_index % 8
65            if isinstance(self.region, str):
66                print(f"{self.name}: {self.__class__.__name__}, {self.region}: bit {hex(bit_index)} (byte {hex(byte_index)}.{offset_in_byte}), {self.length} bits")
67            else:
68                print(f"{self.name}: {self.__class__.__name__}, ({self.region[0]}, {self.region[1]}): bit {hex(bit_index)} (byte {hex(byte_index)}.{offset_in_byte}), {self.length} bits")
69        else:
70            print(f"{self.name}: {self.__class__.__name__}, {self.length} bits")
71
72class AliasDecl(NamedDecl):
73    def __init__(self, name, source, tree):
74        super().__init__(name, tree)
75        self.name = name
76        self.source = source
77
78    def dump(self):
79        print(f"{self.name}: {self.__class__.__name__}, aliasing {self.source}")
80
81class MethodDecl(NamedDecl):
82    @staticmethod
83    def object_type():
84        return 8
85
86    def __init__(self, name, nargs, tree):
87        super().__init__(name, tree)
88        self.nargs = nargs
89
90    def dump(self):
91        print(f"{self.name}: {self.__class__.__name__}, {self.nargs} args")
92
93class PredefinedMethodDecl(NamedDecl):
94    @staticmethod
95    def object_type():
96        return 8
97
98    def __init__(self, name, nargs, fn):
99        super().__init__(name, None)
100        self.nargs = nargs
101        self.fn = fn
102
103    def dump(self):
104        print(f"{self.name}: {self.__class__.__name__}, {self.nargs} args")
105
106class DeviceDecl(NamedDecl):
107    @staticmethod
108    def object_type():
109        return 6
110
111    def __init__(self, name, tree):
112        super().__init__(name, tree)
113
114def predefined_osi(args):
115    feature = args[0].get()
116    if feature.startswith("Linux"):
117        return 0xffffffff
118    elif feature.startswith("Windows") or \
119         feature.startswith("FreeBSD") or \
120         feature.startswith("HP-UX") or \
121         feature.startswith("OpenVMS"):
122        return 0
123    return 0xffffffff
124
125class Context:
126    @staticmethod
127    def realpath(scope, name):
128        if name and name.startswith("\\"):
129            return name
130
131        if name and name.startswith("^"):
132            parent_count = name.count("^")
133            assert parent_count <= len(scope)
134            scope = scope[:-parent_count]
135            name = name[parent_count:]
136
137        if scope:
138            if isinstance(scope, list):
139                if name:
140                    return f"\\{'.'.join(scope)}.{name}"
141                else:
142                    return f"\\{'.'.join(scope)}"
143            elif isinstance(scope, str):
144                if scope == "\\":
145                    return f"\\{name}"
146                else:
147                    return f"{scope}.{name}"
148            else:
149                raise NotImplementedError
150        else:
151            if name:
152                return f"\\{name}"
153            else:
154                return f"\\"
155
156    @staticmethod
157    def parent(scope):
158        if scope == "\\":
159            return "\\"
160        else:
161            parent = scope[:-4]
162            if parent.endswith("."):
163                return parent[:-1]
164            else:
165                return parent
166
167    @staticmethod
168    def normalize_namepath(namepath):
169        path = namepath.lstrip("\\^")
170        prefix = namepath[:(len(namepath) - len(path))]
171        parts = '.'.join(map(lambda x: x[:4].ljust(4, '_'), path.split(".")))
172        return prefix + parts
173
174    def __init__(self):
175        self.streams = {}
176        self.current_stream = None
177        self.trees = {}
178
179        # Loaded namespace
180        self.__symbol_table = {}
181        self.__devices = []
182
183        # Context during parsing
184        self.__current_scope = []
185        self.__scope_history = []
186        self.__deferred_mode_depth = 0
187
188        # Context during interpretation
189        self.__binding_table = {}
190        self.__op_regions = {}
191
192        # Register predefined objects per section 5.7, ACPI 6.4
193        self.register_symbol(NamedDecl("_GL_", None))
194        self.register_symbol(PredefinedMethodDecl("_OSI", 1, predefined_osi))
195        self.register_symbol(NamedDecl("_OS_", None))
196        self.register_symbol(NamedDecl("_REV", None))
197        self.register_symbol(NamedDecl("_DLM", None))
198
199        # Mode switches
200        self.__skip_external_on_lookup = False
201
202    def switch_stream(self, val):
203        if isinstance(val, str):
204            if not val in self.streams.keys():
205                with open(val, "rb") as f:
206                    stream = Stream(f.read())
207                    self.streams[val] = stream
208                    self.current_stream = stream
209            else:
210                self.current_stream = self.streams[val]
211                self.current_stream.reset()
212        elif isinstance(val, (bytes, bytearray)):
213            self.current_stream = Stream(val)
214        else:
215            raise NotImplementedError(f"Cannot use {val} as a stream.")
216
217    def get_scope(self):
218        return self.realpath(self.__current_scope, "")
219
220    def change_scope(self, new_scope):
221        self.__scope_history.append(copy(self.__current_scope))
222        if isinstance(new_scope, list):
223            self.__current_scope = new_scope
224        elif isinstance(new_scope, str):
225            if new_scope.startswith("\\"):
226                self.__current_scope = [i for i in new_scope[1:].split(".") if i]
227            elif new_scope.startswith("^"):
228                parent_count = new_scope.count("^")
229                assert parent_count <= len(self.__current_scope)
230                self.__current_scope = self.__current_scope[:-parent_count].extend(new_scope.split("."))
231            else:
232                self.__current_scope.extend(new_scope.split("."))
233        else:
234            raise InvalidPath(new_scope)
235
236    def pop_scope(self):
237        assert(self.__scope_history)
238        self.__current_scope = self.__scope_history.pop()
239
240    def __register_symbol(self, symbol):
241        self.__symbol_table[symbol.name] = symbol
242        if isinstance(symbol, DeviceDecl):
243            self.__devices.append(symbol)
244
245    def register_symbol(self, symbol):
246        symbol.name = self.realpath(self.__current_scope, symbol.name)
247        if symbol.name in self.__symbol_table.keys():
248            old_tree = self.__symbol_table[symbol.name].tree
249            new_tree = symbol.tree
250            if old_tree.label != new_tree.label:
251                if old_tree.label == "DefExternal":
252                    self.__register_symbol(symbol)
253                elif new_tree.label == "DefExternal":
254                    pass
255                else:
256                    logging.debug(f"{symbol.name} is redefined as {new_tree.label} (previously was {old_tree.label})")
257                    self.__register_symbol(symbol)
258        else:
259            self.__register_symbol(symbol)
260
261    def unregister_object(self, realpath):
262        sym = self.__symbol_table.pop(realpath, None)
263        if isinstance(sym, DeviceDecl):
264            self.__devices.remove(sym)
265
266    def __lookup_symbol_in_parents(self, table, name):
267        prefix_len = len(self.__current_scope)
268        while prefix_len >= 0:
269            path = self.realpath(self.__current_scope[:prefix_len], name)
270            if path in table:
271                sym = table[path]
272                # External object declarations are only for parsing. At
273                # interpretation time such declarations should not be looked up.
274                if (not self.__skip_external_on_lookup) or \
275                   isinstance(sym, PredefinedMethodDecl) or \
276                   (sym.tree and sym.tree.label != "DefExternal"):
277                    return sym
278            prefix_len -= 1
279        raise KeyError(name)
280
281    def lookup_symbol(self, name, scope=None):
282        if scope:
283            self.change_scope(scope)
284        try:
285            if name.startswith("\\"):
286                ret = self.__symbol_table[name]
287            elif name.startswith("^") or name.find(".") >= 0:
288                realpath = self.realpath(self.__current_scope, name)
289                ret = self.__symbol_table[realpath]
290            else:
291                ret = self.__lookup_symbol_in_parents(self.__symbol_table, name)
292        except KeyError:
293            ret = None
294
295        if scope:
296            self.pop_scope()
297        if not ret:
298            raise UndefinedSymbol(name, scope if scope else self.get_scope())
299        return ret
300
301    def has_symbol(self, name):
302        try:
303            self.lookup_symbol(name)
304            return True
305        except UndefinedSymbol:
306            return False
307
308    def lookup_symbol_by_tree(self, tree):
309        result = filter(lambda x: x[1].tree is tree, self.__symbol_table.items())
310        try:
311            return next(result)[1]
312        except StopIteration:
313            return None
314
315    def get_fresh_name(self):
316        current_scope = self.get_scope()
317        for i in range(0, 10):
318            name = self.realpath(current_scope, f"_T_{i}")
319            if not self.lookup_symbol(name):
320                return name
321        raise NotImplementedError("Cannot find a proper fresh name")
322
323    @property
324    def devices(self):
325        return self.__devices
326
327    def dump_symbols(self):
328        for k,v in sorted(self.__symbol_table.items()):
329            v.dump()
330
331    def enter_deferred_mode(self):
332        self.__deferred_mode_depth += 1
333
334    def exit_deferred_mode(self):
335        assert self.__deferred_mode_depth > 0
336        self.__deferred_mode_depth -= 1
337
338    def in_deferred_mode(self):
339        return (self.__deferred_mode_depth > 0)
340
341    def skip_external_on_lookup(self):
342        self.__skip_external_on_lookup = True
343
344    def register_operation_region(self, name, op_region):
345        self.__op_regions[name] = op_region
346
347    def lookup_operation_region(self, name):
348        try:
349            if name.startswith("\\"):
350                return self.__op_regions[name]
351            elif name.startswith("^") or name.find(".") >= 0:
352                realpath = self.realpath(self.__current_scope, name)
353                return self.__op_regions[realpath]
354            else:
355                return self.__lookup_symbol_in_parents(self.__op_regions, name)
356        except KeyError:
357            return None
358
359    def register_binding(self, name, value):
360        sym = self.lookup_symbol(name)
361        logging.debug(f"Bind {sym.name} to {value}")
362        self.__binding_table[sym.name] = value
363
364    def lookup_binding(self, name):
365        sym = self.lookup_symbol(name)
366        try:
367            return self.__binding_table[sym.name]
368        except KeyError:
369            return None
370
371    def dump_bindings(self):
372        for k in sorted(self.__binding_table.keys()):
373            v = self.__binding_table[k]
374            if v:
375                try:
376                    val = v.get()
377                    if isinstance(val, int):
378                        val = hex(val)
379                    print(k, val)
380                except NotImplementedError:
381                    print(k, f"({v.__class__.__name__})")
382                except AttributeError:
383                    print(k, f"(wrong type: {v})")
384            else:
385                print(k, "(None)")
386