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()