1#!/usr/bin/env python3
2#
3# Copyright (c) 2021 Intel Corporation
4#
5# SPDX-License-Identifier: Apache-2.0
6
7"""
8Class for Dictionary-based Logging Database
9"""
10
11import base64
12import copy
13import json
14
15from .mipi_syst import gen_syst_xml_file
16from .utils import extract_one_string_in_section, find_string_in_mappings
17
18ARCHS = {
19    "arc" : {
20        "kconfig": "CONFIG_ARC",
21    },
22    "arm" : {
23        "kconfig": "CONFIG_ARM",
24    },
25    "arm64" : {
26        "kconfig": "CONFIG_ARM64",
27    },
28    "mips" : {
29        "kconfig": "CONFIG_MIPS",
30    },
31    "sparc" : {
32        "kconfig": "CONFIG_SPARC",
33    },
34    "x86" : {
35        "kconfig": "CONFIG_X86",
36    },
37    "posix" : {
38        "kconfig": "CONFIG_ARCH_POSIX",
39    },
40    "riscv32e" : {
41        "kconfig": "CONFIG_RISCV_ISA_RV32E",
42    },
43    "riscv" : {
44        "kconfig": "CONFIG_RISCV",
45    },
46    "rx" : {
47        "kconfig": "CONFIG_RX",
48    },
49    "xtensa" : {
50        "kconfig": "CONFIG_XTENSA",
51    },
52}
53
54
55class LogDatabase:
56    """Class of log database"""
57    # Update this if database format of dictionary based logging
58    # has changed
59    ZEPHYR_DICT_LOG_VER = 3
60
61    LITTLE_ENDIAN = True
62    BIG_ENDIAN = False
63
64    def __init__(self):
65        new_db = {}
66
67        new_db['version'] = self.ZEPHYR_DICT_LOG_VER
68        new_db['target'] = {}
69        new_db['log_subsys'] = {}
70        new_db['log_subsys']['log_instances'] = {}
71        new_db['build_id'] = None
72        new_db['arch'] = None
73        new_db['kconfigs'] = {}
74
75        self.database = new_db
76
77
78    def get_version(self):
79        """Get Database Version"""
80        return self.database['version']
81
82
83    def get_build_id(self):
84        """Get Build ID"""
85        return self.database['build_id']
86
87
88    def set_build_id(self, build_id):
89        """Set Build ID in Database"""
90        self.database['build_id'] = build_id
91
92
93    def get_arch(self):
94        """Get the Target Architecture"""
95        return self.database['arch']
96
97
98    def set_arch(self, arch):
99        """Set the Target Architecture"""
100        self.database['arch'] = arch
101
102
103    def get_tgt_bits(self):
104        """Get Target Bitness: 32 or 64"""
105        if 'bits' in self.database['target']:
106            return self.database['target']['bits']
107
108        return None
109
110
111    def set_tgt_bits(self, bits):
112        """Set Target Bitness: 32 or 64"""
113        self.database['target']['bits'] = bits
114
115
116    def is_tgt_64bit(self):
117        """Return True if target is 64-bit, False if 32-bit.
118        None if error."""
119        if 'bits' not in self.database['target']:
120            return None
121
122        if self.database['target']['bits'] == 32:
123            return False
124
125        if self.database['target']['bits'] == 64:
126            return True
127
128        return None
129
130
131    def get_tgt_endianness(self):
132        """
133        Get Target Endianness.
134
135        Return True if little endian, False if big.
136        """
137        if 'little_endianness' in self.database['target']:
138            return self.database['target']['little_endianness']
139
140        return None
141
142
143    def set_tgt_endianness(self, endianness):
144        """
145        Set Target Endianness
146
147        True if little endian, False if big.
148        """
149        self.database['target']['little_endianness'] = endianness
150
151
152    def is_tgt_little_endian(self):
153        """Return True if target is little endian"""
154        if 'little_endianness' not in self.database['target']:
155            return None
156
157        return self.database['target']['little_endianness'] == self.LITTLE_ENDIAN
158
159
160    def get_string_mappings(self):
161        """Get string mappings to database"""
162        return self.database['string_mappings']
163
164
165    def set_string_mappings(self, database):
166        """Add string mappings to database"""
167        self.database['string_mappings'] = database
168
169
170    def has_string_mappings(self):
171        """Return True if there are string mappings in database"""
172        return 'string_mappings' in self.database
173
174
175    def has_string_sections(self):
176        """Return True if there are any static string sections"""
177        if 'sections' not in self.database:
178            return False
179
180        return len(self.database['sections']) != 0
181
182
183    def __find_string_in_mappings(self, string_ptr):
184        """
185        Find string pointed by string_ptr in the string mapping
186        list. Return None if not found.
187        """
188        return find_string_in_mappings(self.database['string_mappings'], string_ptr)
189
190
191    def __find_string_in_sections(self, string_ptr):
192        """
193        Find string pointed by string_ptr in the binary data
194        sections. Return None if not found.
195        """
196        for _, sect in self.database['sections'].items():
197            one_str = extract_one_string_in_section(sect, string_ptr)
198
199            if one_str is not None:
200                return one_str
201
202        return None
203
204
205    def find_string(self, string_ptr):
206        """Find string pointed by string_ptr in the database.
207        Return None if not found."""
208        one_str = None
209
210        if self.has_string_mappings():
211            one_str = self.__find_string_in_mappings(string_ptr)
212
213        if one_str is None and self.has_string_sections():
214            one_str = self.__find_string_in_sections(string_ptr)
215
216        return one_str
217
218
219    def add_log_instance(self, source_id, name, level, address):
220        """Add one log instance into database"""
221        self.database['log_subsys']['log_instances'][source_id] = {
222            'source_id' : source_id,
223            'name'      : name,
224            'level'     : level,
225            'addr'      : address,
226        }
227
228
229    def get_log_source_string(self, domain_id, source_id):
230        """Get the source string based on source ID"""
231        # JSON stores key as string, so we need to convert
232        src_id = str(source_id)
233
234        if src_id in self.database['log_subsys']['log_instances']:
235            return self.database['log_subsys']['log_instances'][src_id]['name']
236
237        return f"unknown<{domain_id}:{source_id}>"
238
239
240    def add_kconfig(self, name, val):
241        """Add a kconfig name-value pair into database"""
242        self.database['kconfigs'][name] = val
243
244
245    def get_kconfigs(self):
246        """Return kconfig name-value pairs"""
247        return self.database['kconfigs']
248
249
250    @staticmethod
251    def read_json_database(db_file_name):
252        """Read database from file and return a LogDatabase object"""
253        try:
254            with open(db_file_name, encoding="iso-8859-1") as db_fd:
255                json_db = json.load(db_fd)
256        except (OSError, json.JSONDecodeError):
257            return None
258
259        # Decode data in JSON back into binary data
260        if 'sections' in json_db:
261            for _, sect in json_db['sections'].items():
262                sect['data'] = base64.b64decode(sect['data_b64'])
263
264        database = LogDatabase()
265        database.database = json_db
266
267        # JSON encodes the addresses in string mappings as literal strings.
268        # So convert them back to integers, as this is needed for partial
269        # matchings.
270        if database.has_string_mappings():
271            new_str_map = {}
272
273            for addr, one_str in database.get_string_mappings().items():
274                new_str_map[int(addr)] = one_str
275
276            database.set_string_mappings(new_str_map)
277
278        return database
279
280
281    @staticmethod
282    def write_json_database(db_file_name, database):
283        """Write the database into file"""
284        json_db = copy.deepcopy(database.database)
285
286        # Make database object into something JSON can dump
287        if 'sections' in json_db:
288            for _, sect in json_db['sections'].items():
289                encoded = base64.b64encode(sect['data'])
290                sect['data_b64'] = encoded.decode('ascii')
291                del sect['data']
292
293        try:
294            with open(db_file_name, "w", encoding="iso-8859-1") as db_fd:
295                db_fd.write(json.dumps(json_db))
296        except OSError:
297            return False
298
299        return True
300
301    @staticmethod
302    def write_syst_database(db_file_name, database):
303        """
304        Write the database into MIPI Sys-T Collateral XML file
305        """
306
307        try:
308            with open(db_file_name, "w", encoding="iso-8859-1") as db_fd:
309                xml = gen_syst_xml_file(database)
310                db_fd.write(xml)
311        except OSError:
312            return False
313
314        return True
315