1#!/usr/bin/env python3
2#
3# Copyright (c) 2017 Intel Corporation
4# Copyright (c) 2018 Foundries.io
5# Copyright (c) 2023 Nordic Semiconductor NA
6#
7# SPDX-License-Identifier: Apache-2.0
8#
9
10import struct
11
12class gen_isr_parser:
13    source_header = """
14/* AUTO-GENERATED by gen_isr_tables.py, do not edit! */
15
16#include <zephyr/toolchain.h>
17#include <zephyr/linker/sections.h>
18#include <zephyr/sw_isr_table.h>
19#include <zephyr/arch/cpu.h>
20
21"""
22
23    shared_isr_table_header = """
24
25/* For this parser to work, we have to be sure that shared interrupts table entry
26 * and the normal isr table entry have exactly the same layout
27 */
28BUILD_ASSERT(sizeof(struct _isr_table_entry)
29             ==
30             sizeof(struct z_shared_isr_table_entry),
31             "Shared ISR and ISR table entries layout do not match");
32BUILD_ASSERT(offsetof(struct _isr_table_entry, arg)
33             ==
34             offsetof(struct z_shared_isr_table_entry, arg),
35             "Shared ISR and ISR table entries layout do not match");
36BUILD_ASSERT(offsetof(struct _isr_table_entry, isr)
37             ==
38             offsetof(struct z_shared_isr_table_entry, isr),
39             "Shared ISR and ISR table entries layout do not match");
40
41"""
42
43    def __init__(self, intlist_data, config, log):
44        """Initialize the parser.
45
46        The function prepares parser to work.
47        Parameters:
48        - intlist_data: The binnary data from intlist section
49        - config: The configuration object
50        - log: The logging object, has to have error and debug methods
51        """
52        self.__config = config
53        self.__log = log
54        intlist = self.__read_intlist(intlist_data)
55        self.__vt, self.__swt, self.__nv, header = self.__parse_intlist(intlist)
56        self.__swi_table_entry_size = header["swi_table_entry_size"]
57        self.__shared_isr_table_entry_size = header["shared_isr_table_entry_size"]
58        self.__shared_isr_client_num_offset = header["shared_isr_client_num_offset"]
59
60    def __read_intlist(self, intlist_data):
61        """read an intList section from the elf file.
62        This is version 2 of a header created by include/zephyr/linker/intlist.ld:
63
64        struct {
65            uint32_t num_vectors; <- typically CONFIG_NUM_IRQS
66            uint8_t stream[];     <- the stream with the interrupt data
67        };
68
69        The stream is contained from variable length records in a form:
70
71        struct _isr_list_sname {
72           /** IRQ line number */
73           int32_t irq;
74           /** Flags for this IRQ, see ISR_FLAG_* definitions */
75           int32_t flags;
76           /** The section name */
77           const char sname[];
78        };
79
80        The flexible array member here (sname) contains the name of the section where the structure
81        with interrupt data is located.
82        It is always Null-terminated string thus we have to search through the input data for the
83        structure end.
84
85        """
86        intlist = {}
87        prefix = self.__config.endian_prefix()
88
89         # Extract header and the rest of the data
90        intlist_header_fmt = prefix + "IIIII"
91        header_sz = struct.calcsize(intlist_header_fmt)
92        header_raw = struct.unpack_from(intlist_header_fmt, intlist_data, 0)
93        self.__log.debug(str(header_raw))
94
95        intlist["num_vectors"]                  = header_raw[0]
96        intlist["offset"]                       = header_raw[1]
97        intlist["swi_table_entry_size"]         = header_raw[2]
98        intlist["shared_isr_table_entry_size"]  = header_raw[3]
99        intlist["shared_isr_client_num_offset"] = header_raw[4]
100
101        intdata = intlist_data[header_sz:]
102
103        # Extract information about interrupts
104        intlist_entry_fmt = prefix + "ii"
105        entry_sz = struct.calcsize(intlist_entry_fmt)
106        intlist["interrupts"] = []
107
108        while len(intdata) > entry_sz:
109            entry_raw = struct.unpack_from(intlist_entry_fmt, intdata, 0)
110            intdata = intdata[entry_sz:]
111            null_idx = intdata.find(0)
112            if null_idx < 0:
113                self.__log.error("Cannot find sname null termination at IRQ{}".format(entry_raw[0]))
114            bname = intdata[:null_idx]
115            # Next structure starts with 4B alignment
116            next_idx = null_idx + 1
117            next_idx = (next_idx + 3) & ~3
118            intdata = intdata[next_idx:]
119            sname = bname.decode()
120            intlist["interrupts"].append([entry_raw[0], entry_raw[1], sname])
121            self.__log.debug("Unpacked IRQ{}, flags: {}, sname: \"{}\"\n".format(
122                entry_raw[0], entry_raw[1], sname))
123
124        # If any data left at the end - it has to be all the way 0 - this is just a check
125        if (len(intdata) and not all([d == 0 for d in intdata])):
126            self.__log.error("Non-zero data found at the end of the intList data.\n")
127
128        self.__log.debug("Configured interrupt routing with linker")
129        self.__log.debug("irq flags sname")
130        self.__log.debug("--------------------------")
131
132        for irq in intlist["interrupts"]:
133            self.__log.debug("{0:<3} {1:<5} {2}".format(
134                hex(irq[0]), irq[1], irq[2]))
135
136        return intlist
137
138    def __parse_intlist(self, intlist):
139        """All the intlist data are parsed into swt and vt arrays.
140
141        The vt array is prepared for hardware interrupt table.
142        Every entry in the selected position would contain None or the name of the function pointer
143        (address or string).
144
145        The swt is a little more complex. At every position it would contain an array of parameter and
146        function pointer pairs. If CONFIG_SHARED_INTERRUPTS is enabled there may be more than 1 entry.
147        If empty array is placed on selected position - it means that the application does not implement
148        this interrupt.
149
150        Parameters:
151        - intlist: The preprocessed list of intlist section content (see read_intlist)
152
153        Return:
154        vt, swt - parsed vt and swt arrays (see function description above)
155        """
156        nvec = intlist["num_vectors"]
157        offset = intlist["offset"]
158        header = {
159            "swi_table_entry_size":         intlist["swi_table_entry_size"],
160            "shared_isr_table_entry_size":  intlist["shared_isr_table_entry_size"],
161            "shared_isr_client_num_offset": intlist["shared_isr_client_num_offset"]
162        }
163
164        if nvec > pow(2, 15):
165            raise ValueError('nvec is too large, check endianness.')
166
167        self.__log.debug('offset is ' + str(offset))
168        self.__log.debug('num_vectors is ' + str(nvec))
169
170        # Set default entries in both tables
171        if not(self.__config.args.sw_isr_table or self.__config.args.vector_table):
172            self.__log.error("one or both of -s or -V needs to be specified on command line")
173        if self.__config.args.vector_table:
174            vt = [None for i in range(nvec)]
175        else:
176            vt = None
177        if self.__config.args.sw_isr_table:
178            swt = [[] for i in range(nvec)]
179        else:
180            swt = None
181
182        # Process intlist and write to the tables created
183        for irq, flags, sname in intlist["interrupts"]:
184            if self.__config.test_isr_direct(flags):
185                if not 0 <= irq - offset < len(vt):
186                    self.__log.error("IRQ %d (offset=%d) exceeds the maximum of %d" %
187                          (irq - offset, offset, len(vt) - 1))
188                vt[irq - offset] = sname
189            else:
190                # Regular interrupt
191                if not swt:
192                    self.__log.error("Regular Interrupt %d declared with section name %s "
193                                     "but no SW ISR_TABLE in use"
194                                     % (irq, sname))
195
196                table_index = self.__config.get_swt_table_index(offset, irq)
197
198                if not 0 <= table_index < len(swt):
199                    self.__log.error("IRQ %d (offset=%d) exceeds the maximum of %d" %
200                                     (table_index, offset, len(swt) - 1))
201                # Check if the given section name does not repeat outside of current interrupt
202                for i in range(nvec):
203                    if i == irq:
204                        continue
205                    if sname in swt[i]:
206                        self.__log.error(("Attempting to register the same section name \"{}\"for" +
207                                          "different interrupts: {} and {}").format(sname, i, irq))
208                if self.__config.check_shared_interrupts():
209                    lst = swt[table_index]
210                    if len(lst) >= self.__config.get_sym("CONFIG_SHARED_IRQ_MAX_NUM_CLIENTS"):
211                        self.__log.error(f"Reached shared interrupt client limit. Maybe increase"
212                                         + f" CONFIG_SHARED_IRQ_MAX_NUM_CLIENTS?")
213                else:
214                    if len(swt[table_index]) > 0:
215                        self.__log.error(f"multiple registrations at table_index {table_index} for irq {irq} (0x{irq:x})"
216                                         + f"\nExisting section {swt[table_index]}, new section {sname}"
217                                         + "\nHas IRQ_CONNECT or IRQ_DIRECT_CONNECT accidentally been invoked on the same irq multiple times?"
218                        )
219                swt[table_index].append(sname)
220
221        return vt, swt, nvec, header
222
223    @staticmethod
224    def __irq_spurious_section(irq):
225        return '.irq_spurious.0x{:x}'.format(irq)
226
227    @staticmethod
228    def __isr_generated_section(irq):
229        return '.isr_generated.0x{:x}'.format(irq)
230
231    @staticmethod
232    def __shared_entry_section(irq, ent):
233        return '.isr_shared.0x{:x}_0x{:x}'.format(irq, ent)
234
235    @staticmethod
236    def __shared_client_num_section(irq):
237        return '.isr_shared.0x{:x}_client_num'.format(irq)
238
239    def __isr_spurious_entry(self, irq):
240        return '_Z_ISR_TABLE_ENTRY({irq}, {func}, NULL, "{sect}");'.format(
241                irq = irq,
242                func = self.__config.swt_spurious_handler,
243                sect = self.__isr_generated_section(irq)
244            )
245
246    def __isr_shared_entry(self, irq):
247        return '_Z_ISR_TABLE_ENTRY({irq}, {func}, {arg}, "{sect}");'.format(
248                irq = irq,
249                arg = '&{}[{}]'.format(self.__config.shared_array_name, irq),
250                func = self.__config.swt_shared_handler,
251                sect = self.__isr_generated_section(irq)
252            )
253
254    def __irq_spurious_entry(self, irq):
255        return '_Z_ISR_DIRECT_TABLE_ENTRY({irq}, {func}, "{sect}");'.format(
256                irq = irq,
257                func = self.__config.vt_default_handler,
258                sect = self.__irq_spurious_section(irq)
259            )
260
261    def __write_isr_handlers(self, fp):
262        for i in range(self.__nv):
263            if len(self.__swt[i]) <= 0:
264                fp.write(self.__isr_spurious_entry(i) + '\n')
265            elif len(self.__swt[i]) > 1:
266                # Connect to shared handlers
267                fp.write(self.__isr_shared_entry(i) + '\n')
268            else:
269                fp.write('/* ISR: {} implemented in app in "{}" section. */\n'.format(
270                         i, self.__swt[i][0]))
271
272    def __write_irq_handlers(self, fp):
273        for i in range(self.__nv):
274            if self.__vt[i] is None:
275                fp.write(self.__irq_spurious_entry(i) + '\n')
276            else:
277                fp.write('/* ISR: {} implemented in app. */\n'.format(i))
278
279    def __write_shared_handlers(self, fp):
280        fp.write("extern struct z_shared_isr_table_entry "
281                "{}[{}];\n".format(self.__config.shared_array_name, self.__nv))
282
283        shared_cnt = self.__config.get_sym('CONFIG_SHARED_IRQ_MAX_NUM_CLIENTS')
284        for i in range(self.__nv):
285            swt_len = len(self.__swt[i])
286            for j in range(shared_cnt):
287                if (swt_len <= 1) or (swt_len <= j):
288                    # Add all unused entry
289                    fp.write('static Z_DECL_ALIGN(struct _isr_table_entry)\n' +
290                             '\tZ_GENERIC_SECTION({})\n'.format(self.__shared_entry_section(i, j)) +
291                             '\t__used isr_shared_empty_entry_0x{:x}_0x{:x} = {{\n'.format(i, j) +
292                             '\t\t.arg = (const void *)NULL,\n' +
293                             '\t\t.isr = (void (*)(const void *))(void *)0\n' +
294                             '};\n'
295                    )
296                else:
297                    # Add information about entry implemented by application
298                    fp.write('/* Shared isr {} entry {} implemented in "{}" section*/\n'.format(
299                             i, j, self.__swt[i][j]))
300
301            # Add information about clients count
302            fp.write(('static size_t Z_GENERIC_SECTION({}) __used\n' +
303                      'isr_shared_client_num_0x{:x} = {};\n\n').format(
304                         self.__shared_client_num_section(i),
305                         i,
306                         0 if swt_len < 2 else swt_len)
307            )
308
309    def write_source(self, fp):
310        fp.write(self.source_header)
311
312        if self.__vt:
313            self.__write_irq_handlers(fp)
314
315        if not self.__swt:
316            return
317
318        if self.__config.check_shared_interrupts():
319            self.__write_shared_handlers(fp)
320
321        self.__write_isr_handlers(fp)
322
323    def __write_linker_irq(self, fp):
324        fp.write('{} = .;\n'.format(self.__config.irq_vector_array_name))
325        for i in range(self.__nv):
326            if self.__vt[i] is None:
327                sname = self.__irq_spurious_section(i)
328            else:
329                sname = self.__vt[i]
330            fp.write('KEEP(*("{}"))\n'.format(sname))
331
332    def __write_linker_shared(self, fp):
333        fp.write(". = ALIGN({});\n".format(self.__shared_isr_table_entry_size))
334        fp.write('{} = .;\n'.format(self.__config.shared_array_name))
335        shared_cnt = self.__config.get_sym('CONFIG_SHARED_IRQ_MAX_NUM_CLIENTS')
336        client_num_pads = self.__shared_isr_client_num_offset - \
337            shared_cnt * self.__swi_table_entry_size
338        if client_num_pads < 0:
339            self.__log.error("Invalid __shared_isr_client_num_offset header value")
340        for i in range(self.__nv):
341            swt_len = len(self.__swt[i])
342            # Add all entries
343            for j in range(shared_cnt):
344                if (swt_len <= 1) or (swt_len <= j):
345                    fp.write('KEEP(*("{}"))\n'.format(self.__shared_entry_section(i, j)))
346                else:
347                    sname = self.__swt[i][j]
348                    if (j != 0) and (sname in self.__swt[i][0:j]):
349                        fp.write('/* Repetition of "{}" section */\n'.format(sname))
350                    else:
351                        fp.write('KEEP(*("{}"))\n'.format(sname))
352            fp.write('. = . + {};\n'.format(client_num_pads))
353            fp.write('KEEP(*("{}"))\n'.format(self.__shared_client_num_section(i)))
354            fp.write(". = ALIGN({});\n".format(self.__shared_isr_table_entry_size))
355
356    def __write_linker_isr(self, fp):
357        fp.write(". = ALIGN({});\n".format(self.__swi_table_entry_size))
358        fp.write('{} = .;\n'.format(self.__config.sw_isr_array_name))
359        for i in range(self.__nv):
360            if (len(self.__swt[i])) == 1:
361                sname = self.__swt[i][0]
362            else:
363                sname = self.__isr_generated_section(i)
364            fp.write('KEEP(*("{}"))\n'.format(sname))
365
366    def write_linker_vt(self, fp):
367        if self.__vt:
368            self.__write_linker_irq(fp)
369
370    def write_linker_swi(self, fp):
371        if self.__swt:
372            self.__write_linker_isr(fp)
373
374            if self.__config.check_shared_interrupts():
375                self.__write_linker_shared(fp)
376