blob: 28d40b892b249548a697bab5aea4d721716beb66 [file] [log] [blame]
#############
IRQ test tool
#############
************
Introduction
************
This tool is to test interrupt handling in TF-M. Testing different interrupt
scenarios is important as the ARMv8-M architecture does complex operations when
interrupt happens, especially when security boundary crossing happens. These
operations need to be considered by the TF-M implementation, as in a typical use
case there is a separate scheduler on the Non-Secure and the secure side as
well, and the SPM needs to maintain a consistent state, which might not be
trivial.
The aim of the tool is to be able to test scenarios, that are identified to be
problematic, in a reproducible way, and do this in an automated way, so regular
regression testing can have a low cost.
******************
How the tool works
******************
The tool is a set of Python scripts which need to be run **inside** a debugger.
Currently Arm Development Studio and GDB are supported. During the test run, the
script interacts with the debugger, sets breakpoints, triggers interrupts by
writing into system registers, starts the target, and when the target is
stopped, it examines the target's state.
A typical execution scenario looks like this:
.. uml::
@startuml
participant CPU
participant Debugger
CPU -> CPU: start_from_reset_handler
activate CPU
Debugger -> CPU: Attach & pause
deactivate CPU
Debugger-> Debugger: start_script
Activate Debugger
note right
Read config files ...
execute step 1
end note
Debugger -> CPU: set_breakpoint
Debugger -> CPU: Continue
deactivate Debugger
activate CPU
... executing ...
loop for all the remaining steps
CPU->Debugger: bkpt hit
deactivate CPU
activate Debugger
note right
Sanity check on the triggered breakpoint
(is this the breakpoint expected)
If so, continue the sequence
end note
Debugger -> CPU: set_breakpoint
alt if required by step
Debugger -> CPU: set interrupt pending
end alt
Debugger -> CPU: Continue
deactivate Debugger
activate CPU
... executing ...
end loop
CPU->Debugger: bkpt hit
deactivate CPU
activate Debugger
Debugger->Debugger: End of script
Deactivate Debugger
@enduml
Once started inside the debugger, the script automatically deduces the debugger
it is running in, by trying to import the support libraries for a specific
debugger. The order the debuggers are tried in the following order:
#. Arm Development studio
#. GDB
If both check fails, the script falls back to 'dummy' mode which means that the
calls to the debugger log the call, and returns successfully.
.. note::
This 'dummy' mode can be used out of a debugger environment as well.
.. important::
The script assumes that the symbols for the software being debugged/tested
are loaded in the debugger.
The available parameters are:
+----------------------+---------------------------------+--------------------------------------------------+
| short option | long option | meaning |
+======================+=================================+==================================================+
| ``-w`` | ``--sw-break`` | Use sw breakpoint (the default is HW breakpoint) |
+----------------------+---------------------------------+--------------------------------------------------+
| ``-q <IRQS>`` | ``--irqs <IRQS>`` | The name of the IRQs json |
+----------------------+---------------------------------+--------------------------------------------------+
| ``-t <TESTCASE>`` | ``--testcase <TESTCASE>`` | The name of the file containing the testcase |
+----------------------+---------------------------------+--------------------------------------------------+
| ``-b <BREAKPOINTS>`` | ``--breakpoints <BREAKPOINTS>`` | The name of the breakpoints json file |
+----------------------+---------------------------------+--------------------------------------------------+
***********
Input files
***********
Breakpoints
===========
below is a sample file for breakpoints:
.. code:: json
{
"breakpoints": {
"irq_test_iteration_before_service_calls": {
"file": "core_ns_positive_testsuite.c",
"line": 692
},
"irq_test_service1_high_handler": {
"symbol": "SPM_CORE_IRQ_TEST_1_SIGNAL_HIGH_isr"
},
"irq_test_service2_prepare_veneer": {
"offset": "4",
"symbol": "tfm_spm_irq_test_2_prepare_test_scenario_veneer"
}
}
}
Each point where a breakpoint is to be set by the tool should be enumerated in
this file, in the "breakpoints" object. For each breakpoint an object needs to
be created. The name of the object can be used in the testcase description. The
possible fields for a breakpoint object can be seen in the example above.
tools/generate_breakpoints.py
-----------------------------
This script helps to automate the generation of the breakpoints from source files.
Each code location that is to be used in a testcase, should be annotated with
one of the following macro in the source files:
.. code:: c
/* Put breakpoint on the address of the symbol */
#define IRQ_TEST_TOOL_SYMBOL(name, symbol)
/* Put a breakpoint on the address symbol + offset */
#define IRQ_TEST_TOOL_SYMBOL_OFFSET(name, symbol, offset)
/* Put a breakpoint at the specific location in the code where the macro is
* called. This creates a file + line type breakpoint
*/
#define IRQ_TEST_TOOL_CODE_LOCATION(name)
Usage of the script:
.. code::
$ python3 generate_breakpoints.py --help
usage: generate_breakpoints.py [-h] tfm_source outfile
positional arguments:
tfm_source path to the TF-M source code
outfile The output json file with the breakpoints
optional arguments:
-h, --help show this help message and exit
IRQs
====
.. code:: json
{
"irqs": {
"test_service1_low": {
"line_num" : 51
},
"ns_irq_low": {
"line_num" : 40
}
}
}
Each IRQ that is to be triggered should have an object created inside the "irqs"
object. The name of these objects is the name that could be used in a testcase
description. The only valid field of the IRQ objects is "line_num" which refers
to the number of the interrupt line.
Testcase
========
.. code:: json
{
"description" : ["Trigger Non-Secure interrupt during SPM execution in",
"privileged mode"],
"steps": [
{
"wait_for" : "irq_test_iteration_start"
},
{
"wait_for" : "spm_partition_start"
},
{
"description" : ["Trigger the interrupt, but expect the operation",
"to be finished before the handler is called"],
"expect" : "spm_partition_start_ret_success",
"trigger" : "ns_irq_low"
},
{
"wait_for" : "ns_irq_low_handler"
},
{
"wait_for" : "irq_test_service2_prepare"
}
]
}
The test is executed by the script on a step by step basis. When the script is
started, it processes the first step, then starts the target. After a breakpoint
is hit, it processes the next target, and continues. This iteration is repeated
until all the steps are processed
For each step, the following activities are executed:
#. All the breakpoints are cleared in the debugger
#. If there is a 'wait_for' field, a breakpoint is set for the location
specified.
#. If there is a 'trigger' field, an IRQ is pended by writing to NVIC
registers.
#. If there is an 'expect' field, a breakpoint is set for the location
specified. Then the testcase file is scanned starting with the next step,
and a breakpoint is set at the first location specified with a 'wait_for'
field. Next time, when the execution is stopped, the breakpoint that was hit
is compared to the expected breakpoint.
Each object can have a description field to add comments.
**********************
How to run the example
**********************
Before running the example, the breakpoints.json needs to be generated from the
TF-M source tree:
.. code-block:: console
$ cd tools/irq_test/
$ python3 tools/generate_breakpoints.py ../.. example/breakpoints.json
The example also require the regression test suite being present in the TF-M
binary, so either ``ConfigRegressionIPC.cmake`` or ``ConfigRegression.cmake``
have to be used to compile TF-M. Also ``-DCMAKE_BUILD_TYPE=Debug`` config option
have to be used in the cmake generation command, to be sure that the debug
information is generated in the axf files.
The sequence of running the testcase in the ``example`` folder looks like the
following:
#. Check out a version of TF-M that contains the ``IRQ_TEST_TOOL_*`` macros for
the testcase
#. Generate breakpoints.json using the TF-M working copy above
#. Build TF-M checked out above
#. Start the debugger, connect to the target, and stop the target. (Make sure
that the target is stopped before the IRQ testcase of the positive core test
suite in TF-M starts executing, as the IRQ test tool's testcase uses the
entry of that TF-M test as a trigger to start.)
#. Execute the script. The script automatically sets the breakpoint for the
first step of the testcase, and continues the target execution.
#. Examine the output of the script. Successful execution is signalled by the
following output line:
.. code::
===== INFO: All the steps in the test file are executed successfully with the expected result.
Arm Development Studio
======================
The script can be called directly from the debugger's command window:
.. note::
In the command absolute path have to be used both for the ``irq_test.py``
and for the parameters.
.. code:: shell
source irq_test.py -q example/irqs_AN521.json -b example/breakpoints.json -t example/testcase.json
GDB
===
The script should be sourced inside GDB, without passing any arguments to
it.
.. code:: shell
(gdb) source irq_test.py
That registers a custom command ``test_irq``. ``test_irq`` should be called
with three parameters: breakpoints, irqs, and the test file. This command will
actually execute the tests.
.. note::
This indirection in case of GDB is necessary because it is not possible to
pass parameters to the script when it is sourced.
.. important::
The script needs to be run from the <TF-M root>/tools/irq_test directory
as the 'current working dir' is added as module search path.
A typical execution of the script in GDB would look like the following:
.. code::
(gdb) target remote localhost: 2331
(gdb) add-symbol-file /path/to/binaries/tfm_s.axf 0x10000000
(gdb) add-symbol-file /path/to/binaries/tfm_ns.axf 0x00040000
(gdb) source /path/to/script/irq_test.py
(gdb) test_irq -q example/irqs_LPC55S69.json -b example/breakpoints.json -t example/testcase.json
.. note::
``add-symbol-file`` command is used above as other commands like ``file``
and ``symbol-file`` seem to be dropping the previously loaded symbols. The
addresses the axf files are loaded at are depending on the platform they
are built to. The address needs to be specified is the start of the code
section
**********************
Implementation details
**********************
Class hierarchy:
.. uml::
@startuml
class gdb.Command
note right: Library provided by GDB
class TestIRQsCommand
note right: Only used in case debugger is GDB
gdb.Command <|.. TestIRQsCommand : implements
TestIRQsCommand o-- TestExecutor : Creates >
"<Main>" o-- TestExecutor : Creates >
note right on link
Only if running in Arm DS
end note
TestExecutor o-- AbstractDebugger : has a concrete >
AbstractDebugger <|.. GDBDebugger : implements
AbstractDebugger <|.. DummyDebugger : implements
AbstractDebugger <|.. ArmDSDebugger : implements
GDBDebugger o-- Breakpoint : has multiple >
GDBDebugger o-- Location : has multiple >
DummyDebugger o-- Location : has multiple >
ArmDSDebugger o-- Location : has multiple >
@enduml
*****************************
Possible further improvements
*****************************
- Add priority property to the IRQs data file
- Add possibility to run randomized scenarios, to realise stress testing.
--------------
*Copyright (c) 2020, Arm Limited. All rights reserved.*