irq_test: Add IRQ testing tool
Add python scripts for debuggers to test IRQ handling in TF-M.
Signed-off-by: Mate Toth-Pal <mate.toth-pal@arm.com>
Change-Id: I6c5c0b920e3a0c38b3a0c867c93dd5851c66ff8b
diff --git a/irq_test_tool/irq_test_gdb_debugger.py b/irq_test_tool/irq_test_gdb_debugger.py
new file mode 100644
index 0000000..930783d
--- /dev/null
+++ b/irq_test_tool/irq_test_gdb_debugger.py
@@ -0,0 +1,238 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2020, Arm Limited. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#-------------------------------------------------------------------------------
+
+""" This module implements the debugger control interface for the GDB debugger
+"""
+
+import traceback
+import logging
+import re
+# pylint: disable=import-error
+import gdb
+# pylint: enable=import-error
+
+from irq_test_abstract_debugger import AbstractDebugger
+from irq_test_executor import TestExecutor
+
+class Breakpoint:
+ def __init__(self, number, hit_count):
+ self.number = number
+ self.hit_count = hit_count
+
+ def __str__(self):
+ return "(#" + str(self.number) + ": hit_count=" + str(self.hit_count) + ")"
+
+class GDBDebugger(AbstractDebugger):
+ """This class is the implementation for the debugger control interface for GDB
+ """
+ def __init__(self, use_sw_breakpoints):
+ super(GDBDebugger, self).__init__()
+ self.last_breakpoint_list = []
+ self.breakpoint_names = {}
+ self.use_sw_breakpoints = use_sw_breakpoints
+ self.last_breakpoint_num = 0
+ self.execute_output = None
+
+ def parse_breakpoint_line(self, lines):
+
+ number = 0
+ hit_count = 0
+
+ # look for a first line: starts with a number
+ m = re.match(r'^\s*(\d+)\s+', lines[0])
+ if m:
+ number = int(m.group(1))
+ lines.pop(0)
+
+ else:
+ logging.error('unexpected line in "info breakpoints": %s', lines[0])
+ exit (0)
+
+ # look for additional lines
+ if lines:
+ m = re.match(r'^\s*stop only', lines[0])
+ if m:
+ lines.pop(0)
+
+ if lines:
+ m = re.match(r'^\s*breakpoint already hit (\d+) time', lines[0])
+ if m:
+ hit_count = int(m.group(1))
+ lines.pop(0)
+
+ return Breakpoint(number, hit_count)
+
+ #TODO: remove this as it is unnecessary
+ def get_list_of_breakpoints(self):
+ breakpoints_output = gdb.execute('info breakpoints', to_string=True)
+
+ if breakpoints_output.find("No breakpoints or watchpoints.") == 0:
+ return []
+
+ breakpoints = []
+
+ m = re.match(r'^Num\s+Type\s+Disp\s+Enb\s+Address\s+What', breakpoints_output)
+ if m:
+ lines = breakpoints_output.splitlines()
+ lines.pop(0) # skip the header
+
+ while lines:
+ breakpoints.append(self.parse_breakpoint_line(lines))
+
+ return breakpoints
+
+ logging.error('unexpected output from "info breakpoints"')
+ exit(0)
+
+ def set_breakpoint(self, name, location):
+ # Using the gdb.Breakpoint class available in the gdb scripting
+ # environment is not used here, as it looks like the python API has no
+ # knowledge of the Hardware breakpoints.
+ # This means that the Breakpoint.__init__() be called with
+ # gdb.BP_HARDWARE_BREAKPOINT as nothing similar exists. Also the
+ # gdb.breakpoints() function seems to be returning the list of software
+ # breakpoints only (i.e. only breakpoints created with the 'break'
+ # command are returned, while breakpoints created with 'hbreak' command
+ # are not)
+ # So instead of using the python API for manipulating breakpoints,
+ # 'gdb.execute' is used to issue 'break', 'hbreak' and
+ # 'info breakpoints' commands, and the output is parsed.
+ logging.info("Add breakpoint for location '%s'", str(location))
+
+ if logging.getLogger().isEnabledFor(logging.DEBUG):
+ logging.debug("List the breakpoints BEFORE adding")
+ #gdb.execute("info breakpoints")
+ for b in self.get_list_of_breakpoints():
+ logging.debug(" " + str(b))
+ logging.debug("End of breakpoint list")
+
+ if self.use_sw_breakpoints:
+ keyword = "break"
+ else:
+ keyword = "hbreak"
+
+ if location.symbol:
+ if (location.offset != 0):
+ argument = '*(((unsigned char*)' + location.symbol + ') + ' + location.offset + ')'
+ else:
+ argument = location.symbol
+ else:
+ argument = location.filename + ":" + str(location.line)
+
+ command = keyword + " " + argument
+ logging.debug("Setting breakpoint with command '" + command + "'")
+
+ add_breakpoint_output = gdb.execute(command, to_string=True)
+
+ print add_breakpoint_output
+ m = re.search(r'reakpoint\s+(\d+)\s+at', str(add_breakpoint_output))
+ if (m):
+ self.last_breakpoint_num = int(m.group(1))
+ else:
+ logging.error("matching breakpoint command's output failed")
+ exit(1)
+
+ if logging.getLogger().isEnabledFor(logging.DEBUG):
+ logging.debug("List the breakpoints AFTER adding")
+ #gdb.execute("info breakpoints")
+ for b in self.get_list_of_breakpoints():
+ logging.debug(" " + str(b))
+ logging.debug("End of breakpoint list")
+
+ self.breakpoint_names[self.last_breakpoint_num] = name
+
+ def __triger_interrupt_using_STIR_address(self, line):
+ logging.debug("writing to STIR address %s", hex(line))
+
+ command = "set *((unsigned int *) " + hex(0xE000EF00) + ") = " + str(line)
+ logging.debug("calling '%s'", command)
+ gdb.execute(command)
+
+ def __triger_interrupt_using_NVIC_ISPR_address(self, line):
+ # write ISPR register directly
+ register_id = line//32
+
+ # straight
+ #register_offset = line%32
+ # reversed endianness
+ register_offset = ((3-(line%32)/8)*8)+(line%8)
+
+ # TODO: remove magic numbers
+ NVIC_ISPR_address = 0xE000E200
+ NVIC_ISPR_n_address = NVIC_ISPR_address + register_id * 4
+
+ value = 1 << register_offset # 0 bits are ignored on write
+
+ logging.debug("Writing to address 0x{:08x} register 0x{:08x}".
+ format(NVIC_ISPR_n_address, value))
+
+ command = "set *((unsigned int *) " + hex(NVIC_ISPR_n_address) + ") = " + hex(value)
+ logging.debug("calling '%s'", command)
+ gdb.execute(command)
+
+ def trigger_interrupt(self, interrupt_line):
+ logging.info("triggering interrupt for line %s", str(interrupt_line))
+
+ line = int(interrupt_line)
+
+ # self.__triger_interrupt_using_NVIC_ISPR_address(line)
+ self.__triger_interrupt_using_STIR_address(line)
+
+ def continue_execution(self):
+ logging.info("Continuing execution ")
+ # save the list of breakpoints before continuing
+ # self.last_breakpoint_list = gdb.breakpoints()
+ self.execute_output = gdb.execute("continue", to_string=True)
+
+ def clear_breakpoints(self):
+ logging.info("Remove all breakpoints")
+ gdb.execute("delete breakpoint")
+
+ def get_triggered_breakpoint(self):
+ logging.info("getting the triggered breakpoints")
+
+ if not self.execute_output:
+ logging.error("Execute was not called yet.")
+ exit(1)
+
+ m = re.search(r'Breakpoint\s+(\d+)\s*,', self.execute_output)
+ if m:
+ print "self.breakpoint_names: " + str(self.breakpoint_names)
+ return self.breakpoint_names[int(m.group(1))]
+ else:
+ logging.error("Unexpected output from execution.")
+ exit(1)
+
+class TestIRQsCommand(gdb.Command):
+ """This class represents the new command to be registered in GDB
+ """
+ def __init__(self, arg_parser):
+ # This registers our class as "test_irqs"
+ super(TestIRQsCommand, self).__init__("test_irqs", gdb.COMMAND_DATA)
+ self.arg_parser = arg_parser
+
+ def print_usage(self):
+ """Print usage of the custom command
+ """
+ print self.arg_parser.print_help()
+
+ def invoke(self, arg, from_tty):
+ """This is the entry point of the command
+
+ This function is called by GDB when the command is called from the debugger
+ """
+ args = self.arg_parser.parse_args(arg.split())
+
+ debugger = GDBDebugger(args.sw_break)
+ test_executor = TestExecutor(debugger)
+
+ try:
+ test_executor.execute(args.irqs, args.breakpoints, args.testcase)
+ except:
+ pass
+
+ traceback.print_exc()