zephyr/scripts/build/gen_isr_tables_parser_local.py

376 lines
16 KiB
Python

#!/usr/bin/env python3
#
# Copyright (c) 2017 Intel Corporation
# Copyright (c) 2018 Foundries.io
# Copyright (c) 2023 Nordic Semiconductor NA
#
# SPDX-License-Identifier: Apache-2.0
#
import struct
class gen_isr_parser:
source_header = """
/* AUTO-GENERATED by gen_isr_tables.py, do not edit! */
#include <zephyr/toolchain.h>
#include <zephyr/linker/sections.h>
#include <zephyr/sw_isr_table.h>
#include <zephyr/arch/cpu.h>
"""
shared_isr_table_header = """
/* For this parser to work, we have to be sure that shared interrupts table entry
* and the normal isr table entry have exactly the same layout
*/
BUILD_ASSERT(sizeof(struct _isr_table_entry)
==
sizeof(struct z_shared_isr_table_entry),
"Shared ISR and ISR table entries layout do not match");
BUILD_ASSERT(offsetof(struct _isr_table_entry, arg)
==
offsetof(struct z_shared_isr_table_entry, arg),
"Shared ISR and ISR table entries layout do not match");
BUILD_ASSERT(offsetof(struct _isr_table_entry, isr)
==
offsetof(struct z_shared_isr_table_entry, isr),
"Shared ISR and ISR table entries layout do not match");
"""
def __init__(self, intlist_data, config, log):
"""Initialize the parser.
The function prepares parser to work.
Parameters:
- intlist_data: The binnary data from intlist section
- config: The configuration object
- log: The logging object, has to have error and debug methods
"""
self.__config = config
self.__log = log
intlist = self.__read_intlist(intlist_data)
self.__vt, self.__swt, self.__nv, header = self.__parse_intlist(intlist)
self.__swi_table_entry_size = header["swi_table_entry_size"]
self.__shared_isr_table_entry_size = header["shared_isr_table_entry_size"]
self.__shared_isr_client_num_offset = header["shared_isr_client_num_offset"]
def __read_intlist(self, intlist_data):
"""read an intList section from the elf file.
This is version 2 of a header created by include/zephyr/linker/intlist.ld:
struct {
uint32_t num_vectors; <- typically CONFIG_NUM_IRQS
uint8_t stream[]; <- the stream with the interrupt data
};
The stream is contained from variable length records in a form:
struct _isr_list_sname {
/** IRQ line number */
int32_t irq;
/** Flags for this IRQ, see ISR_FLAG_* definitions */
int32_t flags;
/** The section name */
const char sname[];
};
The flexible array member here (sname) contains the name of the section where the structure
with interrupt data is located.
It is always Null-terminated string thus we have to search through the input data for the
structure end.
"""
intlist = {}
prefix = self.__config.endian_prefix()
# Extract header and the rest of the data
intlist_header_fmt = prefix + "IIIII"
header_sz = struct.calcsize(intlist_header_fmt)
header_raw = struct.unpack_from(intlist_header_fmt, intlist_data, 0)
self.__log.debug(str(header_raw))
intlist["num_vectors"] = header_raw[0]
intlist["offset"] = header_raw[1]
intlist["swi_table_entry_size"] = header_raw[2]
intlist["shared_isr_table_entry_size"] = header_raw[3]
intlist["shared_isr_client_num_offset"] = header_raw[4]
intdata = intlist_data[header_sz:]
# Extract information about interrupts
intlist_entry_fmt = prefix + "ii"
entry_sz = struct.calcsize(intlist_entry_fmt)
intlist["interrupts"] = []
while len(intdata) > entry_sz:
entry_raw = struct.unpack_from(intlist_entry_fmt, intdata, 0)
intdata = intdata[entry_sz:]
null_idx = intdata.find(0)
if null_idx < 0:
self.__log.error("Cannot find sname null termination at IRQ{}".format(entry_raw[0]))
bname = intdata[:null_idx]
# Next structure starts with 4B alignment
next_idx = null_idx + 1
next_idx = (next_idx + 3) & ~3
intdata = intdata[next_idx:]
sname = bname.decode()
intlist["interrupts"].append([entry_raw[0], entry_raw[1], sname])
self.__log.debug("Unpacked IRQ{}, flags: {}, sname: \"{}\"\n".format(
entry_raw[0], entry_raw[1], sname))
# If any data left at the end - it has to be all the way 0 - this is just a check
if (len(intdata) and not all([d == 0 for d in intdata])):
self.__log.error("Non-zero data found at the end of the intList data.\n")
self.__log.debug("Configured interrupt routing with linker")
self.__log.debug("irq flags sname")
self.__log.debug("--------------------------")
for irq in intlist["interrupts"]:
self.__log.debug("{0:<3} {1:<5} {2}".format(
hex(irq[0]), irq[1], irq[2]))
return intlist
def __parse_intlist(self, intlist):
"""All the intlist data are parsed into swt and vt arrays.
The vt array is prepared for hardware interrupt table.
Every entry in the selected position would contain None or the name of the function pointer
(address or string).
The swt is a little more complex. At every position it would contain an array of parameter and
function pointer pairs. If CONFIG_SHARED_INTERRUPTS is enabled there may be more than 1 entry.
If empty array is placed on selected position - it means that the application does not implement
this interrupt.
Parameters:
- intlist: The preprocessed list of intlist section content (see read_intlist)
Return:
vt, swt - parsed vt and swt arrays (see function description above)
"""
nvec = intlist["num_vectors"]
offset = intlist["offset"]
header = {
"swi_table_entry_size": intlist["swi_table_entry_size"],
"shared_isr_table_entry_size": intlist["shared_isr_table_entry_size"],
"shared_isr_client_num_offset": intlist["shared_isr_client_num_offset"]
}
if nvec > pow(2, 15):
raise ValueError('nvec is too large, check endianness.')
self.__log.debug('offset is ' + str(offset))
self.__log.debug('num_vectors is ' + str(nvec))
# Set default entries in both tables
if not(self.__config.args.sw_isr_table or self.__config.args.vector_table):
self.__log.error("one or both of -s or -V needs to be specified on command line")
if self.__config.args.vector_table:
vt = [None for i in range(nvec)]
else:
vt = None
if self.__config.args.sw_isr_table:
swt = [[] for i in range(nvec)]
else:
swt = None
# Process intlist and write to the tables created
for irq, flags, sname in intlist["interrupts"]:
if self.__config.test_isr_direct(flags):
if not 0 <= irq - offset < len(vt):
self.__log.error("IRQ %d (offset=%d) exceeds the maximum of %d" %
(irq - offset, offset, len(vt) - 1))
vt[irq - offset] = sname
else:
# Regular interrupt
if not swt:
self.__log.error("Regular Interrupt %d declared with section name %s "
"but no SW ISR_TABLE in use"
% (irq, sname))
table_index = self.__config.get_swt_table_index(offset, irq)
if not 0 <= table_index < len(swt):
self.__log.error("IRQ %d (offset=%d) exceeds the maximum of %d" %
(table_index, offset, len(swt) - 1))
# Check if the given section name does not repeat outside of current interrupt
for i in range(nvec):
if i == irq:
continue
if sname in swt[i]:
self.__log.error(("Attempting to register the same section name \"{}\"for" +
"different interrupts: {} and {}").format(sname, i, irq))
if self.__config.check_shared_interrupts():
lst = swt[table_index]
if len(lst) >= self.__config.get_sym("CONFIG_SHARED_IRQ_MAX_NUM_CLIENTS"):
self.__log.error(f"Reached shared interrupt client limit. Maybe increase"
+ f" CONFIG_SHARED_IRQ_MAX_NUM_CLIENTS?")
else:
if len(swt[table_index]) > 0:
self.__log.error(f"multiple registrations at table_index {table_index} for irq {irq} (0x{irq:x})"
+ f"\nExisting section {swt[table_index]}, new section {sname}"
+ "\nHas IRQ_CONNECT or IRQ_DIRECT_CONNECT accidentally been invoked on the same irq multiple times?"
)
swt[table_index].append(sname)
return vt, swt, nvec, header
@staticmethod
def __irq_spurious_section(irq):
return '.irq_spurious.0x{:x}'.format(irq)
@staticmethod
def __isr_generated_section(irq):
return '.isr_generated.0x{:x}'.format(irq)
@staticmethod
def __shared_entry_section(irq, ent):
return '.isr_shared.0x{:x}_0x{:x}'.format(irq, ent)
@staticmethod
def __shared_client_num_section(irq):
return '.isr_shared.0x{:x}_client_num'.format(irq)
def __isr_spurious_entry(self, irq):
return '_Z_ISR_TABLE_ENTRY({irq}, {func}, NULL, "{sect}");'.format(
irq = irq,
func = self.__config.swt_spurious_handler,
sect = self.__isr_generated_section(irq)
)
def __isr_shared_entry(self, irq):
return '_Z_ISR_TABLE_ENTRY({irq}, {func}, {arg}, "{sect}");'.format(
irq = irq,
arg = '&{}[{}]'.format(self.__config.shared_array_name, irq),
func = self.__config.swt_shared_handler,
sect = self.__isr_generated_section(irq)
)
def __irq_spurious_entry(self, irq):
return '_Z_ISR_DIRECT_TABLE_ENTRY({irq}, {func}, "{sect}");'.format(
irq = irq,
func = self.__config.vt_default_handler,
sect = self.__irq_spurious_section(irq)
)
def __write_isr_handlers(self, fp):
for i in range(self.__nv):
if len(self.__swt[i]) <= 0:
fp.write(self.__isr_spurious_entry(i) + '\n')
elif len(self.__swt[i]) > 1:
# Connect to shared handlers
fp.write(self.__isr_shared_entry(i) + '\n')
else:
fp.write('/* ISR: {} implemented in app in "{}" section. */\n'.format(
i, self.__swt[i][0]))
def __write_irq_handlers(self, fp):
for i in range(self.__nv):
if self.__vt[i] is None:
fp.write(self.__irq_spurious_entry(i) + '\n')
else:
fp.write('/* ISR: {} implemented in app. */\n'.format(i))
def __write_shared_handlers(self, fp):
fp.write("extern struct z_shared_isr_table_entry "
"{}[{}];\n".format(self.__config.shared_array_name, self.__nv))
shared_cnt = self.__config.get_sym('CONFIG_SHARED_IRQ_MAX_NUM_CLIENTS')
for i in range(self.__nv):
swt_len = len(self.__swt[i])
for j in range(shared_cnt):
if (swt_len <= 1) or (swt_len <= j):
# Add all unused entry
fp.write('static Z_DECL_ALIGN(struct _isr_table_entry)\n' +
'\tZ_GENERIC_SECTION({})\n'.format(self.__shared_entry_section(i, j)) +
'\t__used isr_shared_empty_entry_0x{:x}_0x{:x} = {{\n'.format(i, j) +
'\t\t.arg = (const void *)NULL,\n' +
'\t\t.isr = (void (*)(const void *))(void *)0\n' +
'};\n'
)
else:
# Add information about entry implemented by application
fp.write('/* Shared isr {} entry {} implemented in "{}" section*/\n'.format(
i, j, self.__swt[i][j]))
# Add information about clients count
fp.write(('static size_t Z_GENERIC_SECTION({}) __used\n' +
'isr_shared_client_num_0x{:x} = {};\n\n').format(
self.__shared_client_num_section(i),
i,
0 if swt_len < 2 else swt_len)
)
def write_source(self, fp):
fp.write(self.source_header)
if self.__vt:
self.__write_irq_handlers(fp)
if not self.__swt:
return
if self.__config.check_shared_interrupts():
self.__write_shared_handlers(fp)
self.__write_isr_handlers(fp)
def __write_linker_irq(self, fp):
fp.write('{} = .;\n'.format(self.__config.irq_vector_array_name))
for i in range(self.__nv):
if self.__vt[i] is None:
sname = self.__irq_spurious_section(i)
else:
sname = self.__vt[i]
fp.write('KEEP(*("{}"))\n'.format(sname))
def __write_linker_shared(self, fp):
fp.write(". = ALIGN({});\n".format(self.__shared_isr_table_entry_size))
fp.write('{} = .;\n'.format(self.__config.shared_array_name))
shared_cnt = self.__config.get_sym('CONFIG_SHARED_IRQ_MAX_NUM_CLIENTS')
client_num_pads = self.__shared_isr_client_num_offset - \
shared_cnt * self.__swi_table_entry_size
if client_num_pads < 0:
self.__log.error("Invalid __shared_isr_client_num_offset header value")
for i in range(self.__nv):
swt_len = len(self.__swt[i])
# Add all entries
for j in range(shared_cnt):
if (swt_len <= 1) or (swt_len <= j):
fp.write('KEEP(*("{}"))\n'.format(self.__shared_entry_section(i, j)))
else:
sname = self.__swt[i][j]
if (j != 0) and (sname in self.__swt[i][0:j]):
fp.write('/* Repetition of "{}" section */\n'.format(sname))
else:
fp.write('KEEP(*("{}"))\n'.format(sname))
fp.write('. = . + {};\n'.format(client_num_pads))
fp.write('KEEP(*("{}"))\n'.format(self.__shared_client_num_section(i)))
fp.write(". = ALIGN({});\n".format(self.__shared_isr_table_entry_size))
def __write_linker_isr(self, fp):
fp.write(". = ALIGN({});\n".format(self.__swi_table_entry_size))
fp.write('{} = .;\n'.format(self.__config.sw_isr_array_name))
for i in range(self.__nv):
if (len(self.__swt[i])) == 1:
sname = self.__swt[i][0]
else:
sname = self.__isr_generated_section(i)
fp.write('KEEP(*("{}"))\n'.format(sname))
def write_linker_vt(self, fp):
if self.__vt:
self.__write_linker_irq(fp)
def write_linker_swi(self, fp):
if self.__swt:
self.__write_linker_isr(fp)
if self.__config.check_shared_interrupts():
self.__write_linker_shared(fp)