.. _hardfaults: ========== Hardfaults ========== ARMv7 ===== Cortex-M3 and Cortex-M4 ----------------------- The most popular CPUs in current MCU designs are the Cortex-M3 (ARMv7-M) and the Cortex-M4 (ARMv7E-M). Handling of these two architectures is almost identical in NuttX (unless hardware floating point is enabled). SVCALL ------ NuttX uses the SVCALL software interrupt in order to perform certain steps in the context switching for the Cortex-M3 and Cortext-M4. This sequence of logic appears in several places: * Create a short critical section by disabling exceptions, * Perform some set-up, * Initiate the software exception / SVCALL, and * When the software exception processing returns, re-enable exceptions. .. note:: There is a technical difference between interrupts and exceptions. In this section the term exception will be used. It is probably more accurate since interrupts are really exceptions that result from device interrupt lines. Furthermore, when we refer to exceptions in this section, we are referring specifically to ARMv7-M configurable exceptions. Disabling Interrupts via the PRIMASK register --------------------------------------------- The ARMv7-M architecture supports a register called the ``PRIMASK`` register. The ``PRIMASK`` register contains a single valid bit. If that bit is set to one, then exceptions are disabled. If that bit is zero, exceptions are enabled. More correctly, when this bit is set to one, it prevents the activation of all exceptions with configurable priority. The original NuttX implementation used this ``PRIMASK`` register to enable and disable exceptions. Things now get interesting in the sequence of logic listed above because the ``PRIMASK`` bit also disables the SVCALL exception! So, instead of taking the ``SVCALL`` exception vector, the Cortex-M3/4 generates a hardfault exception (see ARM.com's discussion of `Activation Levels `_). These hardfaults are not really a problem. The design of the NuttX hardfault handler expects these exceptions and does the right thing. However, the occurrence of hardfaults may come as a surprise to many people and especially to some debuggers. Hardfaults and Debuggers ------------------------ These hardfaults only become a technical issue when dealing with a debugger. What does the debugger do when the hardfault occurs? We need to back off and think about some system philosophy here. The deep philosophical question here is: Who is in charge of system integrity? The debugger or the RTOS? If you are running a primitive NoOS program (like the famous blinky test program), then you are running a barebones system and you need all of the help you can get. So having the debugger make decisions about what is the proper behavior of the blinky program and what is not is a good thing for you. But if you are using an advanced RTOS, then the RTOS will want to take responsibility of the health of your system and now there is the possibility of inconsistencies between the decisions that the RTOS makes and the decisions that your debugger makes this hardfault handling is a perfect example here. In NuttX, the hardfaults are controlled by the RTOS, but some debuggers will break when the hardfault occurs and make debugging impossible. Some people might take issue with this. Breaking on hardfault can make debugging easier, since the break happens in the throwing context and you have a hope of obtaining a backtrace and debugging the throwing side callstack. However, I would suggest that putting a break point on ``up_assert()`` would accomplish the same thing without being so intrusive. How did the debugger know that the hardfault occurred? It knew because of settings in the "ARM's Debug Exception and Monitor Control Register" or ``DEMCR``. Proper settings of the ``DEMCR`` register will allow debugger to get break exceptions when a hardfault occrs. So one workaround is to just reconfigure the ``DEMCR`` register so that break exceptions are no longer generated when hardfaults occur. Here is an example of such logic for the LPC43xx MCU. Decoupling the hardfault from the break exception in this way does not work with all debuggers, however. Presumably because some debuggers re-enable break exceptions on hardfaults. Disabling Interrupts via the BASEPRI register --------------------------------------------- The ARMv7-M architecture supports another way to disable exceptions using a register called the ``BASEPRI`` register. ARMv7-M exceptions are prioritized. Each exception can be assigned an 8-bit priority. If the ``BASEPRI`` register is set to a non-zero priority value, then it will filter exceptions in this sense: * Exceptions with priority lower than or equal to the ``BASEPRI`` register will be disabled. * Exceptions with priority higher than the ``BASEPRI`` register will still be enabled. Normally this interrupt prioritization is used to support nested interrupt handling, but it can also be used for disabling of all exceptions if configured properly. .. note:: In the ARMv7-M, higher values correspond to lower priority. This can be really confusing! NuttX supports a configuration option called ``CONFIG_ARMV7M_USEBASEPRI``. If this option is selected, then the exception prioritization and control logic will be configured to use the ``BASEPRI`` register instead of the ``PRIMASK`` register to disable exceptions. This configuration includes the following changes in the behavior: * Normal interrupts and exceptions are restricted to the range ``{ lowest priority ... (highest priority - 1) }``. * The priority of the ``SVCALL`` exception is set to highest priority. * When exceptions are enabled, the ``BASEPRI`` register is set to zero, enabling exceptions of all priorities. * When exceptions are disabled, the ``BASEPRI`` register is set to ``(highest priority - 1)``, disabling all exceptions except for the ``SVCALL`` exception. In this way, the ``SVCALL`` exception remains enabled when exceptions are disabled and no hardfault occurs. .. note:: The above is inaccurate on several counts. It was simplified to make the discussion sane. Not only do higher values correspond to lower priorities, but the increment between consecutive 8-bit priority values is probably not one. The supported maximum and minimum priority values (as well as the step in each priority value) may be different for each MCU. To handle this, these values are exported in NuttX for each ARMv7-M architecture by header files at ``nuttx/arch/arm/include//chip.h``.