Saving the location of Watchdog timeout

I am trying to save the address of program counter when a watchdog reset occurs. In order to do that I use gcc's __builtin_return_address(0) from within the SDK's nrf_drv_wdt.c:WDT_IRQHandler().

What this returns is 0xfffffff9. If I interpret the ARM spec correctly, this address is in the area reserved for the chip vendor.

  • What is at that address?

  • Can I extract the PC before WD reset using the gcc builtin? Another function?

  • What is the recommended way of getting the address when the watchdog timed out? I've seen people manually unpacking the stack, I'd be glad if I could do it in a less hacky way.

  • Hi,

    That the __builtin_return_address() function is returning 0xfffffff9 is here expected. See the chapter “Exception entry and return” in the ARM Cortex-M4 manual, and read about when the EXC_RETURN(0xFFFFFFF9) value is loaded into the program counter.

    One way to do this, is to dump the stack in the watchdog timeout handler, and read back the program counter when the program starts up again after the reset. Here is one way you can do it based on the watchdog example in the SDK:

    /**
     * Copyright (c) 2014 - 2017, Nordic Semiconductor ASA
     * 
     * All rights reserved.
     * 
     * Redistribution and use in source and binary forms, with or without modification,
     * are permitted provided that the following conditions are met:
     * 
     * 1. Redistributions of source code must retain the above copyright notice, this
     *    list of conditions and the following disclaimer.
     * 
     * 2. Redistributions in binary form, except as embedded into a Nordic
     *    Semiconductor ASA integrated circuit in a product or a software update for
     *    such product, must reproduce the above copyright notice, this list of
     *    conditions and the following disclaimer in the documentation and/or other
     *    materials provided with the distribution.
     * 
     * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
     *    contributors may be used to endorse or promote products derived from this
     *    software without specific prior written permission.
     * 
     * 4. This software, with or without modification, must only be used with a
     *    Nordic Semiconductor ASA integrated circuit.
     * 
     * 5. Any software provided in binary form under this license must not be reverse
     *    engineered, decompiled, modified and/or disassembled.
     * 
     * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
     * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
     * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
     * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
     * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     * 
     */
    /** @file
     * @defgroup nrf_dev_wdt_example_main main.c
     * @{
     * @ingroup nrf_dev_wdt_example
     * @brief WDT Example Application main file.
     *
     * This file contains the source code for a sample application using WDT.
     *
     */
    
    
    #include <stdbool.h>
    #include <stdint.h>
    
    #include "nrf.h"
    #include "bsp.h"
    #include "app_timer.h"
    #include "app_error.h"
    #include "nrf_drv_wdt.h"
    #include "nrf_drv_clock.h"
    #include "nrf_delay.h"
    #include "app_util_platform.h"
    
    #include <string.h>
    #include "sdk_common.h"
    #include "crc16.h"
    
    
    
    #define FEED_BUTTON_ID          0                           /**< Button for feeding the dog. */
    
    nrf_drv_wdt_channel_id m_channel_id;
    
    typedef struct stack
    {
        uint32_t r0;  ///< R0 register.
        uint32_t r1;  ///< R1 register.
        uint32_t r2;  ///< R2 register.
        uint32_t r3;  ///< R3 register.
        uint32_t r12; ///< R12 register.
        uint32_t lr;  ///< Link register.
        uint32_t pc;  ///< Program counter.
        uint32_t psr; ///< Program status register.
    } stack_t;
    
    static stack_t stack __attribute__((section(".non_init")));
    static uint16_t crc __attribute__((section(".non_init")));
    
    bool wdt_debug(stack_t * p_stack);
    void dump_stack(stack_t * p_stack);
    
    
    
    //NOTE: The max amount of time we can spend in WDT interrupt is two cycles of 32768[Hz] clock - after that, reset occurs
    void wdt_timeout_handler()
    {
     /* Needed to unwind original stack frame from WDT_IRQHandler when code optimization is disabled. 
        Try to remove "add	r0, #8" line if compiling with -O3*/
    __ASM volatile(        
                     "   mrs r0, msp                             \n"
                     "   add	r0, #8                       \n"
                     "   ldr r3, =dump_stack                     \n"
                     "   bx r3                                   \n"
                  ); 
    } 
    
    
    
    
    bool wdt_debug(stack_t * p_stack)
    {
    
        if ((NRF_POWER->RESETREAS & POWER_RESETREAS_DOG_Msk) == POWER_RESETREAS_DOG_Msk)
        {
            memcpy(p_stack, &stack, sizeof(stack_t));
            if (crc != crc16_compute((const uint8_t *)p_stack, sizeof(stack_t), NULL))
            {
                return false; //invalid data
            }
            crc = 0;
            NRF_POWER->RESETREAS = 0;
            return true;
        }
        else
        {
            return false;
        }
    }
    
    
    void dump_stack(stack_t * p_stack)
    {
        memcpy(&stack, p_stack, sizeof(stack_t));
        /*Note: RAM is not guaranteed to be retained retained through WD reset:
          http://infocenter.nordicsemi.com/topic/com.nordic.infocenter.nrf52832.ps.v1.1/power.html?cp=2_1_0_17_7#unique_832471788.
          CRC may be used to verify the data integrity*/
        crc = crc16_compute((const uint8_t *)p_stack, sizeof(stack_t), NULL);
    }
    
    
    
    /**
     * @brief Assert callback.
     *
     * @param[in] id    Fault identifier. See @ref NRF_FAULT_IDS.
     * @param[in] pc    The program counter of the instruction that triggered the fault, or 0 if
     *                  unavailable.
     * @param[in] info  Optional additional information regarding the fault. Refer to each fault
     *                  identifier for details.
     */
    void app_error_fault_handler(uint32_t id, uint32_t pc, uint32_t info)
    {
        bsp_board_leds_off();
        while (1);
    }
    
    /**
     * @brief BSP events callback.
     */
    void bsp_event_callback(bsp_event_t event)
    {
        switch (event)
        {
            case BSP_EVENT_KEY_0:
                nrf_drv_wdt_channel_feed(m_channel_id);
                break;
    
            default :
                //Do nothing.
                break;
        }
    }
    
    
    /**
     * @brief Function for main application entry.
     */
    int main(void)
    {
        uint32_t err_code = NRF_SUCCESS;
    
        //BSP configuration for button support: button pushing will feed the dog.
        err_code = nrf_drv_clock_init();
        APP_ERROR_CHECK(err_code);
        nrf_drv_clock_lfclk_request(NULL);
    
    
        stack_t old_stack;
    
        if(wdt_debug(&old_stack)){
        // program counter from when the watchdog reset occurred can now be found.
        uint32_t watchdog_reset_pc;
        watchdog_reset_pc = old_stack.pc;
    
        }
    
        err_code = app_timer_init();
        APP_ERROR_CHECK(err_code);
    
        err_code = bsp_init(BSP_INIT_BUTTONS, bsp_event_callback);
        APP_ERROR_CHECK(err_code);
    
        //Configure all LEDs on board.
        bsp_board_leds_init();
    
        //Configure WDT.
        nrf_drv_wdt_config_t config = NRF_DRV_WDT_DEAFULT_CONFIG;
        err_code = nrf_drv_wdt_init(&config, wdt_timeout_handler);
        APP_ERROR_CHECK(err_code);
        err_code = nrf_drv_wdt_channel_alloc(&m_channel_id);
        APP_ERROR_CHECK(err_code);
        nrf_drv_wdt_enable();
    
        //Indicate program start on LEDs.
        for (uint32_t i = 0; i < LEDS_NUMBER; i++)
        {   nrf_delay_ms(200);
            bsp_board_led_on(i);
        }
         err_code = bsp_buttons_enable();
         APP_ERROR_CHECK(err_code);
    
        while (1)
        {
            __SEV();
            __WFE();
            __WFE();
        }
    }
    
    /** @} */
    

    Note: I tested this with GCC/Segger Embedded Studio, and with Optimization Level set to None.