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