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