From f9fb0f212c60f2bfb1400e8b6adea74ae39bf72a Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 10 Mar 2016 12:28:38 +1100 Subject: [PATCH 01/10] Add stack memory dump to fatal exception handler --- core/app_main.c | 23 ++++++++++++++++++++++- core/exception_vectors.S | 7 ++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/core/app_main.c b/core/app_main.c index 19bf73e..f3cbff4 100644 --- a/core/app_main.c +++ b/core/app_main.c @@ -85,6 +85,7 @@ static void zero_bss(void); static void init_networking(uint8_t *phy_info, uint8_t *mac_addr); static void init_g_ic(void); static void dump_excinfo(void); +static void dump_stack(uint32_t *sp); static void user_start_phase2(void); static void dump_flash_sector(uint32_t start_sector, uint32_t length); static void dump_flash_config_sectors(uint32_t start_sector); @@ -147,7 +148,7 @@ static void IRAM set_spi0_divisor(uint32_t divisor) { } // .text+0x148 -void IRAM sdk_user_fatal_exception_handler(void) { +void IRAM sdk_user_fatal_exception_handler(uint32_t *sp) { if (!sdk_NMIIrqIsOn) { vPortEnterCritical(); do { @@ -157,6 +158,8 @@ void IRAM sdk_user_fatal_exception_handler(void) { Cache_Read_Disable(); Cache_Read_Enable(0, 0, 1); dump_excinfo(); + if (sp) + dump_stack(sp); uart_flush_txfifo(0); uart_flush_txfifo(1); sdk_system_restart_in_nmi(); @@ -363,6 +366,24 @@ static void dump_excinfo(void) { sdk_system_rtc_mem_write(0, excinfo, 32); } +/* There's a lot of smart stuff we could do while dumping stack + but for now we just dump a likely looking section of stack + memory +*/ +static void dump_stack(uint32_t *sp) { + printf("\nStack: SP=%p\n", sp); + for(uint32_t *p = sp; p < sp + 32; p += 4) { + if((intptr_t)p >= 0x3fffc000) { + break; /* approximate end of RAM */ + } + printf("%p: %08x %08x %08x %08x\n", p, p[0], p[1], p[2], p[3]); + if(p[0] == 0xa5a5a5a5 && p[1] == 0xa5a5a5a5 + && p[2] == 0xa5a5a5a5 && p[3] == 0xa5a5a5a5) { + break; /* FreeRTOS uses this pattern to mark untouched stack space */ + } + } +} + // .irom0.text+0x398 void sdk_wdt_init(void) { WDT.CTRL &= ~WDT_CTRL_ENABLE; diff --git a/core/exception_vectors.S b/core/exception_vectors.S index c1f1761..1980a0a 100644 --- a/core/exception_vectors.S +++ b/core/exception_vectors.S @@ -68,6 +68,7 @@ DebugExceptionVector: .type DebugExceptionVector, @function wsr a0, excsave2 + mov a2, a1 call0 sdk_user_fatal_exception_handler rfi 2 @@ -81,6 +82,7 @@ KernelExceptionVector: .type KernelExceptionVector, @function break 1, 0 + mov a2, a1 call0 sdk_user_fatal_exception_handler rfe @@ -98,6 +100,7 @@ DoubleExceptionVector: .type DoubleExceptionVector, @function break 1, 4 + mov a2, a1 call0 sdk_user_fatal_exception_handler /* Reset vector at offset 0x80 is unused, as vecbase gets reset to mask ROM @@ -251,10 +254,11 @@ LoadStoreErrorHandler: * will have correct values */ wsr a0, sar l32i a0, sp, 0 - l32i a2, sp, 0x08 + /*l32i a2, sp, 0x08*/ l32i a3, sp, 0x0c l32i a4, sp, 0x10 rsr a1, excsave1 + mov a2, a1 call0 sdk_user_fatal_exception_handler .balign 4 @@ -515,6 +519,7 @@ UserExceptionHandler: .literal_position .LUserFailOtherExceptionCause: break 1, 1 + addi a2, a1, 0x50 /* UserExceptionHandler pushes stack down 0x50 */ call0 sdk_user_fatal_exception_handler /* _xt_user_exit is pushed onto the stack as part of the user exception handler, From b414e0b946fb874b2cee74539cc343be90bee886 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 10 Mar 2016 12:28:53 +1100 Subject: [PATCH 02/10] Add 'filteroutput.py' tool to automatically do addr2line lookups on likely hex values --- common.mk | 5 +- utils/filteroutput.py | 105 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 1 deletion(-) create mode 100755 utils/filteroutput.py diff --git a/common.mk b/common.mk index 8da70cd..c8fee7a 100644 --- a/common.mk +++ b/common.mk @@ -75,6 +75,9 @@ FLAVOR ?= release # or debug # Compiler names, etc. assume gdb CROSS ?= xtensa-lx106-elf- +# Path to the filteroutput.py tool +FILTEROUTPUT ?= $(ROOT)/utils/filteroutput.py + AR = $(CROSS)ar CC = $(CROSS)gcc CPP = $(CROSS)cpp @@ -385,7 +388,7 @@ size: $(PROGRAM_OUT) $(Q) $(CROSS)size --format=sysv $(PROGRAM_OUT) test: flash - screen $(ESPPORT) 115200 + $(FILTEROUTPUT) --port $(ESPPORT) --baud 115200 --elf $(PROGRAM_OUT) # the rebuild target is written like this so it can be run in a parallel build # environment without causing weird side effects diff --git a/utils/filteroutput.py b/utils/filteroutput.py new file mode 100755 index 0000000..5e9b1cf --- /dev/null +++ b/utils/filteroutput.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python +# +# A thin Python wrapper around addr2line, can monitor esp-open-rtos +# output and uses gdb to convert any suitable looking hex numbers +# found in the output into function and line numbers. +# +# Works with a serial port if the --port option is supplied. +# Otherwise waits for input on stdin. +# +import serial +import argparse +import re +import os +import os.path +import subprocess +import termios +import sys +import time + +# Try looking up anything in the executable address space +RE_EXECADDR = r"(0x)?40([0-9]|[a-z]){6}" + +def find_elf_file(): + out_files = [] + for top,_,files in os.walk('.', followlinks=False): + for f in files: + if f.endswith(".out"): + out_files.append(os.path.join(top,f)) + if len(out_files) == 1: + return out_files[0] + elif len(out_files) > 1: + print("Found multiple .out files: %s. Please specify one with the --elf option." % out_files) + else: + print("No .out file found under current directory. Please specify one with the --elf option.") + sys.exit(1) + +def main(): + parser = argparse.ArgumentParser(description='esp-open-rtos output filter tool', prog='filteroutput') + parser.add_argument( + '--elf', '-e', + help="ELF file (*.out file) to load symbols from (if not supplied, will search for one)"), + parser.add_argument( + '--port', '-p', + help='Serial port to monitor (will monitor stdin if None)', + default=None) + parser.add_argument( + '--baud', '-b', + help='Baud rate for serial port', + type=int, + default=74880) + parser.add_argument( + '--reset-on-connect', '-r', + help='Reset ESP8266 (via DTR) on serial connect. (Linux resets even if not set, except when using NodeMCU-style auto-reset circuit.)', + action='store_true') + + args = parser.parse_args() + + if args.elf is None: + args.elf = find_elf_file() + elif not os.path.exists(args.elf): + print("ELF file '%s' not found" % args.elf) + sys.exit(1) + + if args.port is not None: + print("Opening %s at %dbps..." % (args.port, args.baud)) + port = serial.Serial(args.port, baudrate=args.baud) + if args.reset_on_connect: + print("Resetting...") + port.setDTR(False) + time.sleep(0.1) + port.setDTR(True) + + else: + print("Reading from stdin...") + port = sys.stdin + # disable echo + try: + old_attr = termios.tcgetattr(sys.stdin.fileno()) + attr = termios.tcgetattr(sys.stdin.fileno()) + attr[3] = attr[3] & ~termios.ECHO + termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, attr) + except termios.error: + pass + + try: + while True: + line = port.readline() + if line == '': + break + print(line.strip()) + for match in re.finditer(RE_EXECADDR, line, re.IGNORECASE): + addr = match.group(0) + if not addr.startswith("0x"): + addr = "0x"+addr + # keeping addr2line and feeding it addresses on stdin didn't seem to work smoothly + addr2line = subprocess.check_output(["xtensa-lx106-elf-addr2line","-pfia","-e","%s" % args.elf, addr], cwd=".").strip() + if not addr2line.endswith(": ?? ??:0"): + print("\n%s\n" % addr2line.strip()) + finally: + if args.port is None: + # restore echo + termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, old_attr) + +if __name__ == "__main__": + main() From 0caab973a50e483e462b65a95d6c5e8ccbec48dd Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 20 Apr 2016 14:59:23 +1000 Subject: [PATCH 03/10] Recompile libc with malloc locking enabled newlib-xtensa revision cbe80794ed0083 This fixes a crash caused by heap operations occuring inside ISRs. Particularly noticeable when sending a lot of network traffic. Probably fixes #119, maybe other crashing bugs. Configure/compile steps same as previous: ../configure --with-newlib --enable-multilib --disable-newlib-io-c99-formats --enable-newlib-supplied-syscalls --enable-target-optspace --program-transform-name="s&^&xtensa-lx106-elf-&" --disable-option-checking --with-target-subdir=xtensa-lx106-elf --target=xtensa-lx106-elf --prefix=/home/gus/dev/esp/rtos/open-rtos/libc/ --enable-newlib-nano-malloc --enable-newlib-nano-formatted-io --enable-newlib-reent-small --prefix=path_to/esp-open-rtos/libc CROSS_CFLAGS="-DSIGNAL_PROVIDED -DABORT_PROVIDED" make make install --- libc/xtensa-lx106-elf/lib/libc.a | Bin 5247078 -> 5248602 bytes libc/xtensa-lx106-elf/lib/libg.a | Bin 5247078 -> 5248602 bytes libc/xtensa-lx106-elf/lib/libm.a | Bin 2241082 -> 2241082 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/libc/xtensa-lx106-elf/lib/libc.a b/libc/xtensa-lx106-elf/lib/libc.a index 6a256cd364b4f9ccc2543bbd8b39d6f82f075433..a43b5edcb88291fd09c3cbe78c9979d511f37603 100644 GIT binary patch delta 27568 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

oDX}A3g`&vJLth8(97_JNaW*wJC|ENS^OmU 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 delta 26064 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 delta 26064 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 Date: Wed, 20 Apr 2016 15:04:29 +1000 Subject: [PATCH 04/10] lwip sys_arch: Add functional xInsideISR implementation Relies on global flags set when the user ISR is executing. Unclear if this fixes any bugs as ISR code may not have been calling into LWIP, but the previous implementation was broken. --- core/esp_interrupts.c | 6 + lwip/sys_arch.c | 1177 +++++++++++++++++++++-------------------- 2 files changed, 598 insertions(+), 585 deletions(-) diff --git a/core/esp_interrupts.c b/core/esp_interrupts.c index 4fd68b7..b3aefff 100644 --- a/core/esp_interrupts.c +++ b/core/esp_interrupts.c @@ -9,6 +9,8 @@ _xt_isr isr[16]; +bool esp_in_isr; + void IRAM _xt_isr_attach(uint8_t i, _xt_isr func) { isr[i] = func; @@ -20,6 +22,8 @@ void IRAM _xt_isr_attach(uint8_t i, _xt_isr func) */ uint16_t IRAM _xt_isr_handler(uint16_t intset) { + esp_in_isr = true; + /* WDT has highest priority (occasional WDT resets otherwise) */ if(intset & BIT(INUM_WDT)) { _xt_clear_ints(BIT(INUM_WDT)); @@ -35,5 +39,7 @@ uint16_t IRAM _xt_isr_handler(uint16_t intset) intset -= mask; } + esp_in_isr = false; + return 0; } diff --git a/lwip/sys_arch.c b/lwip/sys_arch.c index f625fc6..ad936e4 100644 --- a/lwip/sys_arch.c +++ b/lwip/sys_arch.c @@ -1,585 +1,592 @@ -/* - * Copyright (c) 2001-2003 Swedish Institute of Computer Science. - * 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 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. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT - * SHALL THE AUTHOR 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. - * - * This file is part of the lwIP TCP/IP stack. - * - * Author: Adam Dunkels - * - */ - -//***************************************************************************** -// -// Include OS functionality. -// -//***************************************************************************** - -/* ------------------------ System architecture includes ----------------------------- */ -#include "arch/sys_arch.h" - -/* ------------------------ lwIP includes --------------------------------- */ -#include "lwip/opt.h" - -#include "lwip/err.h" -#include "lwip/debug.h" -#include "lwip/def.h" -#include "lwip/sys.h" -#include "lwip/mem.h" -#include "lwip/stats.h" - -/* Very crude mechanism used to determine if the critical section handling -functions are being called from an interrupt context or not. This relies on -the interrupt handler setting this variable manually. */ -portBASE_TYPE xInsideISR = pdFALSE; - -/*---------------------------------------------------------------------------* - * Routine: sys_mbox_new - *---------------------------------------------------------------------------* - * Description: - * Creates a new mailbox - * Inputs: - * int size -- Size of elements in the mailbox - * Outputs: - * sys_mbox_t -- Handle to new mailbox - *---------------------------------------------------------------------------*/ -err_t sys_mbox_new( sys_mbox_t *pxMailBox, int iSize ) -{ -err_t xReturn = ERR_MEM; - - *pxMailBox = xQueueCreate( iSize, sizeof( void * ) ); - - if( *pxMailBox != NULL ) - { - xReturn = ERR_OK; - SYS_STATS_INC_USED( mbox ); - } - - return xReturn; -} - - -/*---------------------------------------------------------------------------* - * Routine: sys_mbox_free - *---------------------------------------------------------------------------* - * Description: - * Deallocates a mailbox. If there are messages still present in the - * mailbox when the mailbox is deallocated, it is an indication of a - * programming error in lwIP and the developer should be notified. - * Inputs: - * sys_mbox_t mbox -- Handle of mailbox - * Outputs: - * sys_mbox_t -- Handle to new mailbox - *---------------------------------------------------------------------------*/ -void sys_mbox_free( sys_mbox_t *pxMailBox ) -{ -unsigned long ulMessagesWaiting; - - ulMessagesWaiting = uxQueueMessagesWaiting( *pxMailBox ); - configASSERT( ( ulMessagesWaiting == 0 ) ); - - #if SYS_STATS - { - if( ulMessagesWaiting != 0UL ) - { - SYS_STATS_INC( mbox.err ); - } - - SYS_STATS_DEC( mbox.used ); - } - #endif /* SYS_STATS */ - - vQueueDelete( *pxMailBox ); -} - -/*---------------------------------------------------------------------------* - * Routine: sys_mbox_post - *---------------------------------------------------------------------------* - * Description: - * Post the "msg" to the mailbox. - * Inputs: - * sys_mbox_t mbox -- Handle of mailbox - * void *data -- Pointer to data to post - *---------------------------------------------------------------------------*/ -void sys_mbox_post( sys_mbox_t *pxMailBox, void *pxMessageToPost ) -{ - while( xQueueSendToBack( *pxMailBox, &pxMessageToPost, portMAX_DELAY ) != pdTRUE ); -} - -/*---------------------------------------------------------------------------* - * Routine: sys_mbox_trypost - *---------------------------------------------------------------------------* - * Description: - * Try to post the "msg" to the mailbox. Returns immediately with - * error if cannot. - * Inputs: - * sys_mbox_t mbox -- Handle of mailbox - * void *msg -- Pointer to data to post - * Outputs: - * err_t -- ERR_OK if message posted, else ERR_MEM - * if not. - *---------------------------------------------------------------------------*/ -err_t sys_mbox_trypost( sys_mbox_t *pxMailBox, void *pxMessageToPost ) -{ -err_t xReturn; -portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE; - - if( xInsideISR != pdFALSE ) - { - xReturn = xQueueSendFromISR( *pxMailBox, &pxMessageToPost, &xHigherPriorityTaskWoken ); - } - else - { - xReturn = xQueueSend( *pxMailBox, &pxMessageToPost, ( portTickType ) 0 ); - } - - if( xReturn == pdPASS ) - { - xReturn = ERR_OK; - } - else - { - /* The queue was already full. */ - xReturn = ERR_MEM; - SYS_STATS_INC( mbox.err ); - } - - return xReturn; -} - -/*---------------------------------------------------------------------------* - * Routine: sys_arch_mbox_fetch - *---------------------------------------------------------------------------* - * Description: - * Blocks the thread until a message arrives in the mailbox, but does - * not block the thread longer than "timeout" milliseconds (similar to - * the sys_arch_sem_wait() function). The "msg" argument is a result - * parameter that is set by the function (i.e., by doing "*msg = - * ptr"). The "msg" parameter maybe NULL to indicate that the message - * should be dropped. - * - * The return values are the same as for the sys_arch_sem_wait() function: - * Number of milliseconds spent waiting or SYS_ARCH_TIMEOUT if there was a - * timeout. - * - * Note that a function with a similar name, sys_mbox_fetch(), is - * implemented by lwIP. - * Inputs: - * sys_mbox_t mbox -- Handle of mailbox - * void **msg -- Pointer to pointer to msg received - * u32_t timeout -- Number of milliseconds until timeout - * Outputs: - * u32_t -- SYS_ARCH_TIMEOUT if timeout, else number - * of milliseconds until received. - *---------------------------------------------------------------------------*/ -u32_t sys_arch_mbox_fetch( sys_mbox_t *pxMailBox, void **ppvBuffer, u32_t ulTimeOut ) -{ -void *pvDummy; -portTickType xStartTime, xEndTime, xElapsed; -unsigned long ulReturn; - - xStartTime = xTaskGetTickCount(); - - if( NULL == ppvBuffer ) - { - ppvBuffer = &pvDummy; - } - - if( ulTimeOut != 0UL ) - { - configASSERT( xInsideISR == ( portBASE_TYPE ) 0 ); - - if( pdTRUE == xQueueReceive( *pxMailBox, &( *ppvBuffer ), ulTimeOut/ portTICK_RATE_MS ) ) - { - xEndTime = xTaskGetTickCount(); - xElapsed = ( xEndTime - xStartTime ) * portTICK_RATE_MS; - - ulReturn = xElapsed; - } - else - { - /* Timed out. */ - *ppvBuffer = NULL; - ulReturn = SYS_ARCH_TIMEOUT; - } - } - else - { - while( pdTRUE != xQueueReceive( *pxMailBox, &( *ppvBuffer ), portMAX_DELAY ) ); - xEndTime = xTaskGetTickCount(); - xElapsed = ( xEndTime - xStartTime ) * portTICK_RATE_MS; - - if( xElapsed == 0UL ) - { - xElapsed = 1UL; - } - - ulReturn = xElapsed; - } - - return ulReturn; -} - -/*---------------------------------------------------------------------------* - * Routine: sys_arch_mbox_tryfetch - *---------------------------------------------------------------------------* - * Description: - * Similar to sys_arch_mbox_fetch, but if message is not ready - * immediately, we'll return with SYS_MBOX_EMPTY. On success, 0 is - * returned. - * Inputs: - * sys_mbox_t mbox -- Handle of mailbox - * void **msg -- Pointer to pointer to msg received - * Outputs: - * u32_t -- SYS_MBOX_EMPTY if no messages. Otherwise, - * return ERR_OK. - *---------------------------------------------------------------------------*/ -u32_t sys_arch_mbox_tryfetch( sys_mbox_t *pxMailBox, void **ppvBuffer ) -{ -void *pvDummy; -unsigned long ulReturn; -long lResult; -portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE; - - if( ppvBuffer== NULL ) - { - ppvBuffer = &pvDummy; - } - - if( xInsideISR != pdFALSE ) - { - lResult = xQueueReceiveFromISR( *pxMailBox, &( *ppvBuffer ), &xHigherPriorityTaskWoken ); - } - else - { - lResult = xQueueReceive( *pxMailBox, &( *ppvBuffer ), 0UL ); - } - - if( lResult == pdPASS ) - { - ulReturn = ERR_OK; - } - else - { - ulReturn = SYS_MBOX_EMPTY; - } - - return ulReturn; -} - -/*---------------------------------------------------------------------------* - * Routine: sys_sem_new - *---------------------------------------------------------------------------* - * Description: - * Creates and returns a new semaphore. The "ucCount" argument specifies - * the initial state of the semaphore. - * NOTE: Currently this routine only creates counts of 1 or 0 - * Inputs: - * sys_mbox_t mbox -- Handle of mailbox - * u8_t ucCount -- Initial ucCount of semaphore (1 or 0) - * Outputs: - * sys_sem_t -- Created semaphore or 0 if could not create. - *---------------------------------------------------------------------------*/ -err_t sys_sem_new( sys_sem_t *pxSemaphore, u8_t ucCount ) -{ -err_t xReturn = ERR_MEM; - - vSemaphoreCreateBinary( ( *pxSemaphore ) ); - - if( *pxSemaphore != NULL ) - { - if( ucCount == 0U ) - { - xSemaphoreTake( *pxSemaphore, 1UL ); - } - - xReturn = ERR_OK; - SYS_STATS_INC_USED( sem ); - } - else - { - SYS_STATS_INC( sem.err ); - } - - return xReturn; -} - -/*---------------------------------------------------------------------------* - * Routine: sys_arch_sem_wait - *---------------------------------------------------------------------------* - * Description: - * Blocks the thread while waiting for the semaphore to be - * signaled. If the "timeout" argument is non-zero, the thread should - * only be blocked for the specified time (measured in - * milliseconds). - * - * If the timeout argument is non-zero, the return value is the number of - * milliseconds spent waiting for the semaphore to be signaled. If the - * semaphore wasn't signaled within the specified time, the return value is - * SYS_ARCH_TIMEOUT. If the thread didn't have to wait for the semaphore - * (i.e., it was already signaled), the function may return zero. - * - * Notice that lwIP implements a function with a similar name, - * sys_sem_wait(), that uses the sys_arch_sem_wait() function. - * Inputs: - * sys_sem_t sem -- Semaphore to wait on - * u32_t timeout -- Number of milliseconds until timeout - * Outputs: - * u32_t -- Time elapsed or SYS_ARCH_TIMEOUT. - *---------------------------------------------------------------------------*/ -u32_t sys_arch_sem_wait( sys_sem_t *pxSemaphore, u32_t ulTimeout ) -{ -portTickType xStartTime, xEndTime, xElapsed; -unsigned long ulReturn; - - xStartTime = xTaskGetTickCount(); - - if( ulTimeout != 0UL ) - { - if( xSemaphoreTake( *pxSemaphore, ulTimeout / portTICK_RATE_MS ) == pdTRUE ) - { - xEndTime = xTaskGetTickCount(); - xElapsed = (xEndTime - xStartTime) * portTICK_RATE_MS; - ulReturn = xElapsed; - } - else - { - ulReturn = SYS_ARCH_TIMEOUT; - } - } - else - { - while( xSemaphoreTake( *pxSemaphore, portMAX_DELAY ) != pdTRUE ); - xEndTime = xTaskGetTickCount(); - xElapsed = ( xEndTime - xStartTime ) * portTICK_RATE_MS; - - if( xElapsed == 0UL ) - { - xElapsed = 1UL; - } - - ulReturn = xElapsed; - } - - return ulReturn; -} - -/** Create a new mutex - * @param mutex pointer to the mutex to create - * @return a new mutex */ -err_t sys_mutex_new( sys_mutex_t *pxMutex ) -{ -err_t xReturn = ERR_MEM; - - *pxMutex = xSemaphoreCreateMutex(); - - if( *pxMutex != NULL ) - { - xReturn = ERR_OK; - SYS_STATS_INC_USED( mutex ); - } - else - { - SYS_STATS_INC( mutex.err ); - } - - return xReturn; -} - -/** Lock a mutex - * @param mutex the mutex to lock */ -void sys_mutex_lock( sys_mutex_t *pxMutex ) -{ - while( xSemaphoreTake( *pxMutex, portMAX_DELAY ) != pdPASS ); -} - -/** Unlock a mutex - * @param mutex the mutex to unlock */ -void sys_mutex_unlock(sys_mutex_t *pxMutex ) -{ - xSemaphoreGive( *pxMutex ); -} - - -/** Delete a semaphore - * @param mutex the mutex to delete */ -void sys_mutex_free( sys_mutex_t *pxMutex ) -{ - SYS_STATS_DEC( mutex.used ); - vQueueDelete( *pxMutex ); -} - - -/*---------------------------------------------------------------------------* - * Routine: sys_sem_signal - *---------------------------------------------------------------------------* - * Description: - * Signals (releases) a semaphore - * Inputs: - * sys_sem_t sem -- Semaphore to signal - *---------------------------------------------------------------------------*/ -void sys_sem_signal( sys_sem_t *pxSemaphore ) -{ -portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE; - - if( xInsideISR != pdFALSE ) - { - xSemaphoreGiveFromISR( *pxSemaphore, &xHigherPriorityTaskWoken ); - } - else - { - xSemaphoreGive( *pxSemaphore ); - } -} - -/*---------------------------------------------------------------------------* - * Routine: sys_sem_free - *---------------------------------------------------------------------------* - * Description: - * Deallocates a semaphore - * Inputs: - * sys_sem_t sem -- Semaphore to free - *---------------------------------------------------------------------------*/ -void sys_sem_free( sys_sem_t *pxSemaphore ) -{ - SYS_STATS_DEC(sem.used); - vQueueDelete( *pxSemaphore ); -} - -/*---------------------------------------------------------------------------* - * Routine: sys_init - *---------------------------------------------------------------------------* - * Description: - * Initialize sys arch - *---------------------------------------------------------------------------*/ -void sys_init(void) -{ -} - -u32_t sys_now(void) -{ - return xTaskGetTickCount(); -} - -/*---------------------------------------------------------------------------* - * Routine: sys_thread_new - *---------------------------------------------------------------------------* - * Description: - * Starts a new thread with priority "prio" that will begin its - * execution in the function "thread()". The "arg" argument will be - * passed as an argument to the thread() function. The id of the new - * thread is returned. Both the id and the priority are system - * dependent. - * Inputs: - * char *name -- Name of thread - * void (* thread)(void *arg) -- Pointer to function to run. - * void *arg -- Argument passed into function - * int stacksize -- Required stack amount in bytes - * int prio -- Thread priority - * Outputs: - * sys_thread_t -- Pointer to per-thread timeouts. - *---------------------------------------------------------------------------*/ -sys_thread_t sys_thread_new( const char *pcName, void( *pxThread )( void *pvParameters ), void *pvArg, int iStackSize, int iPriority ) -{ -xTaskHandle xCreatedTask; -portBASE_TYPE xResult; -sys_thread_t xReturn; - - xResult = xTaskCreate( pxThread, ( signed char * ) pcName, iStackSize, pvArg, iPriority, &xCreatedTask ); - - if( xResult == pdPASS ) - { - xReturn = xCreatedTask; - } - else - { - xReturn = NULL; - } - - return xReturn; -} - -/*---------------------------------------------------------------------------* - * Routine: sys_arch_protect - *---------------------------------------------------------------------------* - * Description: - * This optional function does a "fast" critical region protection and - * returns the previous protection level. This function is only called - * during very short critical regions. An embedded system which supports - * ISR-based drivers might want to implement this function by disabling - * interrupts. Task-based systems might want to implement this by using - * a mutex or disabling tasking. This function should support recursive - * calls from the same task or interrupt. In other words, - * sys_arch_protect() could be called while already protected. In - * that case the return value indicates that it is already protected. - * - * sys_arch_protect() is only required if your port is supporting an - * operating system. - * Outputs: - * sys_prot_t -- Previous protection level (not used here) - *---------------------------------------------------------------------------*/ -sys_prot_t sys_arch_protect( void ) -{ - if( xInsideISR == pdFALSE ) - { - taskENTER_CRITICAL(); - } - return ( sys_prot_t ) 1; -} - -/*---------------------------------------------------------------------------* - * Routine: sys_arch_unprotect - *---------------------------------------------------------------------------* - * Description: - * This optional function does a "fast" set of critical region - * protection to the value specified by pval. See the documentation for - * sys_arch_protect() for more information. This function is only - * required if your port is supporting an operating system. - * Inputs: - * sys_prot_t -- Previous protection level (not used here) - *---------------------------------------------------------------------------*/ -void sys_arch_unprotect( sys_prot_t xValue ) -{ - (void) xValue; - if( xInsideISR == pdFALSE ) - { - taskEXIT_CRITICAL(); - } -} - -/* - * Prints an assertion messages and aborts execution. - */ -void sys_assert( const char *pcMessage ) -{ - (void) pcMessage; - - for (;;) - { - } -} -/*-------------------------------------------------------------------------* - * End of File: sys_arch.c - *-------------------------------------------------------------------------*/ - +/* + * Copyright (c) 2001-2003 Swedish Institute of Computer Science. + * 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 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. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE AUTHOR 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. + * + * This file is part of the lwIP TCP/IP stack. + * + * Author: Adam Dunkels + * + */ + +//***************************************************************************** +// +// Include OS functionality. +// +//***************************************************************************** + +/* ------------------------ System architecture includes ----------------------------- */ +#include "arch/sys_arch.h" + +/* ------------------------ lwIP includes --------------------------------- */ +#include "lwip/opt.h" + +#include "lwip/err.h" +#include "lwip/debug.h" +#include "lwip/def.h" +#include "lwip/sys.h" +#include "lwip/mem.h" +#include "lwip/stats.h" + +extern bool esp_in_isr; + +/* Based on the default xInsideISR mechanism to determine + if an ISR is running. + + Doesn't support the possibility that LWIP functions are called from the NMI + handler (none are called from NMI when using current/SDK implementation.) +*/ +static inline bool is_inside_isr() +{ + return esp_in_isr; +} + +/*---------------------------------------------------------------------------* + * Routine: sys_mbox_new + *---------------------------------------------------------------------------* + * Description: + * Creates a new mailbox + * Inputs: + * int size -- Size of elements in the mailbox + * Outputs: + * sys_mbox_t -- Handle to new mailbox + *---------------------------------------------------------------------------*/ +err_t sys_mbox_new( sys_mbox_t *pxMailBox, int iSize ) +{ + err_t xReturn = ERR_MEM; + + *pxMailBox = xQueueCreate( iSize, sizeof( void * ) ); + + if( *pxMailBox != NULL ) + { + xReturn = ERR_OK; + SYS_STATS_INC_USED( mbox ); + } + + return xReturn; +} + + +/*---------------------------------------------------------------------------* + * Routine: sys_mbox_free + *---------------------------------------------------------------------------* + * Description: + * Deallocates a mailbox. If there are messages still present in the + * mailbox when the mailbox is deallocated, it is an indication of a + * programming error in lwIP and the developer should be notified. + * Inputs: + * sys_mbox_t mbox -- Handle of mailbox + * Outputs: + * sys_mbox_t -- Handle to new mailbox + *---------------------------------------------------------------------------*/ +void sys_mbox_free( sys_mbox_t *pxMailBox ) +{ +unsigned long ulMessagesWaiting; + + ulMessagesWaiting = uxQueueMessagesWaiting( *pxMailBox ); + configASSERT( ( ulMessagesWaiting == 0 ) ); + + #if SYS_STATS + { + if( ulMessagesWaiting != 0UL ) + { + SYS_STATS_INC( mbox.err ); + } + + SYS_STATS_DEC( mbox.used ); + } + #endif /* SYS_STATS */ + + vQueueDelete( *pxMailBox ); +} + +/*---------------------------------------------------------------------------* + * Routine: sys_mbox_post + *---------------------------------------------------------------------------* + * Description: + * Post the "msg" to the mailbox. + * Inputs: + * sys_mbox_t mbox -- Handle of mailbox + * void *data -- Pointer to data to post + *---------------------------------------------------------------------------*/ +void sys_mbox_post( sys_mbox_t *pxMailBox, void *pxMessageToPost ) +{ + while( xQueueSendToBack( *pxMailBox, &pxMessageToPost, portMAX_DELAY ) != pdTRUE ); +} + +/*---------------------------------------------------------------------------* + * Routine: sys_mbox_trypost + *---------------------------------------------------------------------------* + * Description: + * Try to post the "msg" to the mailbox. Returns immediately with + * error if cannot. + * Inputs: + * sys_mbox_t mbox -- Handle of mailbox + * void *msg -- Pointer to data to post + * Outputs: + * err_t -- ERR_OK if message posted, else ERR_MEM + * if not. + *---------------------------------------------------------------------------*/ +err_t sys_mbox_trypost( sys_mbox_t *pxMailBox, void *pxMessageToPost ) +{ +err_t xReturn; +portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE; + + if( is_inside_isr() != pdFALSE ) + { + xReturn = xQueueSendFromISR( *pxMailBox, &pxMessageToPost, &xHigherPriorityTaskWoken ); + } + else + { + xReturn = xQueueSend( *pxMailBox, &pxMessageToPost, ( portTickType ) 0 ); + } + + if( xReturn == pdPASS ) + { + xReturn = ERR_OK; + } + else + { + /* The queue was already full. */ + xReturn = ERR_MEM; + SYS_STATS_INC( mbox.err ); + } + + return xReturn; +} + +/*---------------------------------------------------------------------------* + * Routine: sys_arch_mbox_fetch + *---------------------------------------------------------------------------* + * Description: + * Blocks the thread until a message arrives in the mailbox, but does + * not block the thread longer than "timeout" milliseconds (similar to + * the sys_arch_sem_wait() function). The "msg" argument is a result + * parameter that is set by the function (i.e., by doing "*msg = + * ptr"). The "msg" parameter maybe NULL to indicate that the message + * should be dropped. + * + * The return values are the same as for the sys_arch_sem_wait() function: + * Number of milliseconds spent waiting or SYS_ARCH_TIMEOUT if there was a + * timeout. + * + * Note that a function with a similar name, sys_mbox_fetch(), is + * implemented by lwIP. + * Inputs: + * sys_mbox_t mbox -- Handle of mailbox + * void **msg -- Pointer to pointer to msg received + * u32_t timeout -- Number of milliseconds until timeout + * Outputs: + * u32_t -- SYS_ARCH_TIMEOUT if timeout, else number + * of milliseconds until received. + *---------------------------------------------------------------------------*/ +u32_t sys_arch_mbox_fetch( sys_mbox_t *pxMailBox, void **ppvBuffer, u32_t ulTimeOut ) +{ +void *pvDummy; +portTickType xStartTime, xEndTime, xElapsed; +unsigned long ulReturn; + + xStartTime = xTaskGetTickCount(); + + if( NULL == ppvBuffer ) + { + ppvBuffer = &pvDummy; + } + + if( ulTimeOut != 0UL ) + { + configASSERT( is_inside_isr() == ( portBASE_TYPE ) 0 ); + + if( pdTRUE == xQueueReceive( *pxMailBox, &( *ppvBuffer ), ulTimeOut/ portTICK_RATE_MS ) ) + { + xEndTime = xTaskGetTickCount(); + xElapsed = ( xEndTime - xStartTime ) * portTICK_RATE_MS; + + ulReturn = xElapsed; + } + else + { + /* Timed out. */ + *ppvBuffer = NULL; + ulReturn = SYS_ARCH_TIMEOUT; + } + } + else + { + while( pdTRUE != xQueueReceive( *pxMailBox, &( *ppvBuffer ), portMAX_DELAY ) ); + xEndTime = xTaskGetTickCount(); + xElapsed = ( xEndTime - xStartTime ) * portTICK_RATE_MS; + + if( xElapsed == 0UL ) + { + xElapsed = 1UL; + } + + ulReturn = xElapsed; + } + + return ulReturn; +} + +/*---------------------------------------------------------------------------* + * Routine: sys_arch_mbox_tryfetch + *---------------------------------------------------------------------------* + * Description: + * Similar to sys_arch_mbox_fetch, but if message is not ready + * immediately, we'll return with SYS_MBOX_EMPTY. On success, 0 is + * returned. + * Inputs: + * sys_mbox_t mbox -- Handle of mailbox + * void **msg -- Pointer to pointer to msg received + * Outputs: + * u32_t -- SYS_MBOX_EMPTY if no messages. Otherwise, + * return ERR_OK. + *---------------------------------------------------------------------------*/ +u32_t sys_arch_mbox_tryfetch( sys_mbox_t *pxMailBox, void **ppvBuffer ) +{ +void *pvDummy; +unsigned long ulReturn; +long lResult; +portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE; + + if( ppvBuffer== NULL ) + { + ppvBuffer = &pvDummy; + } + + if( is_inside_isr() != pdFALSE ) + { + lResult = xQueueReceiveFromISR( *pxMailBox, &( *ppvBuffer ), &xHigherPriorityTaskWoken ); + } + else + { + lResult = xQueueReceive( *pxMailBox, &( *ppvBuffer ), 0UL ); + } + + if( lResult == pdPASS ) + { + ulReturn = ERR_OK; + } + else + { + ulReturn = SYS_MBOX_EMPTY; + } + + return ulReturn; +} + +/*---------------------------------------------------------------------------* + * Routine: sys_sem_new + *---------------------------------------------------------------------------* + * Description: + * Creates and returns a new semaphore. The "ucCount" argument specifies + * the initial state of the semaphore. + * NOTE: Currently this routine only creates counts of 1 or 0 + * Inputs: + * sys_mbox_t mbox -- Handle of mailbox + * u8_t ucCount -- Initial ucCount of semaphore (1 or 0) + * Outputs: + * sys_sem_t -- Created semaphore or 0 if could not create. + *---------------------------------------------------------------------------*/ +err_t sys_sem_new( sys_sem_t *pxSemaphore, u8_t ucCount ) +{ +err_t xReturn = ERR_MEM; + + vSemaphoreCreateBinary( ( *pxSemaphore ) ); + + if( *pxSemaphore != NULL ) + { + if( ucCount == 0U ) + { + xSemaphoreTake( *pxSemaphore, 1UL ); + } + + xReturn = ERR_OK; + SYS_STATS_INC_USED( sem ); + } + else + { + SYS_STATS_INC( sem.err ); + } + + return xReturn; +} + +/*---------------------------------------------------------------------------* + * Routine: sys_arch_sem_wait + *---------------------------------------------------------------------------* + * Description: + * Blocks the thread while waiting for the semaphore to be + * signaled. If the "timeout" argument is non-zero, the thread should + * only be blocked for the specified time (measured in + * milliseconds). + * + * If the timeout argument is non-zero, the return value is the number of + * milliseconds spent waiting for the semaphore to be signaled. If the + * semaphore wasn't signaled within the specified time, the return value is + * SYS_ARCH_TIMEOUT. If the thread didn't have to wait for the semaphore + * (i.e., it was already signaled), the function may return zero. + * + * Notice that lwIP implements a function with a similar name, + * sys_sem_wait(), that uses the sys_arch_sem_wait() function. + * Inputs: + * sys_sem_t sem -- Semaphore to wait on + * u32_t timeout -- Number of milliseconds until timeout + * Outputs: + * u32_t -- Time elapsed or SYS_ARCH_TIMEOUT. + *---------------------------------------------------------------------------*/ +u32_t sys_arch_sem_wait( sys_sem_t *pxSemaphore, u32_t ulTimeout ) +{ +portTickType xStartTime, xEndTime, xElapsed; +unsigned long ulReturn; + + xStartTime = xTaskGetTickCount(); + + if( ulTimeout != 0UL ) + { + if( xSemaphoreTake( *pxSemaphore, ulTimeout / portTICK_RATE_MS ) == pdTRUE ) + { + xEndTime = xTaskGetTickCount(); + xElapsed = (xEndTime - xStartTime) * portTICK_RATE_MS; + ulReturn = xElapsed; + } + else + { + ulReturn = SYS_ARCH_TIMEOUT; + } + } + else + { + while( xSemaphoreTake( *pxSemaphore, portMAX_DELAY ) != pdTRUE ); + xEndTime = xTaskGetTickCount(); + xElapsed = ( xEndTime - xStartTime ) * portTICK_RATE_MS; + + if( xElapsed == 0UL ) + { + xElapsed = 1UL; + } + + ulReturn = xElapsed; + } + + return ulReturn; +} + +/** Create a new mutex + * @param mutex pointer to the mutex to create + * @return a new mutex */ +err_t sys_mutex_new( sys_mutex_t *pxMutex ) +{ +err_t xReturn = ERR_MEM; + + *pxMutex = xSemaphoreCreateMutex(); + + if( *pxMutex != NULL ) + { + xReturn = ERR_OK; + SYS_STATS_INC_USED( mutex ); + } + else + { + SYS_STATS_INC( mutex.err ); + } + + return xReturn; +} + +/** Lock a mutex + * @param mutex the mutex to lock */ +void sys_mutex_lock( sys_mutex_t *pxMutex ) +{ + while( xSemaphoreTake( *pxMutex, portMAX_DELAY ) != pdPASS ); +} + +/** Unlock a mutex + * @param mutex the mutex to unlock */ +void sys_mutex_unlock(sys_mutex_t *pxMutex ) +{ + xSemaphoreGive( *pxMutex ); +} + + +/** Delete a semaphore + * @param mutex the mutex to delete */ +void sys_mutex_free( sys_mutex_t *pxMutex ) +{ + SYS_STATS_DEC( mutex.used ); + vQueueDelete( *pxMutex ); +} + + +/*---------------------------------------------------------------------------* + * Routine: sys_sem_signal + *---------------------------------------------------------------------------* + * Description: + * Signals (releases) a semaphore + * Inputs: + * sys_sem_t sem -- Semaphore to signal + *---------------------------------------------------------------------------*/ +void sys_sem_signal( sys_sem_t *pxSemaphore ) +{ +portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE; + + if( is_inside_isr() != pdFALSE ) + { + xSemaphoreGiveFromISR( *pxSemaphore, &xHigherPriorityTaskWoken ); + } + else + { + xSemaphoreGive( *pxSemaphore ); + } +} + +/*---------------------------------------------------------------------------* + * Routine: sys_sem_free + *---------------------------------------------------------------------------* + * Description: + * Deallocates a semaphore + * Inputs: + * sys_sem_t sem -- Semaphore to free + *---------------------------------------------------------------------------*/ +void sys_sem_free( sys_sem_t *pxSemaphore ) +{ + SYS_STATS_DEC(sem.used); + vQueueDelete( *pxSemaphore ); +} + +/*---------------------------------------------------------------------------* + * Routine: sys_init + *---------------------------------------------------------------------------* + * Description: + * Initialize sys arch + *---------------------------------------------------------------------------*/ +void sys_init(void) +{ +} + +u32_t sys_now(void) +{ + return xTaskGetTickCount(); +} + +/*---------------------------------------------------------------------------* + * Routine: sys_thread_new + *---------------------------------------------------------------------------* + * Description: + * Starts a new thread with priority "prio" that will begin its + * execution in the function "thread()". The "arg" argument will be + * passed as an argument to the thread() function. The id of the new + * thread is returned. Both the id and the priority are system + * dependent. + * Inputs: + * char *name -- Name of thread + * void (* thread)(void *arg) -- Pointer to function to run. + * void *arg -- Argument passed into function + * int stacksize -- Required stack amount in bytes + * int prio -- Thread priority + * Outputs: + * sys_thread_t -- Pointer to per-thread timeouts. + *---------------------------------------------------------------------------*/ +sys_thread_t sys_thread_new( const char *pcName, void( *pxThread )( void *pvParameters ), void *pvArg, int iStackSize, int iPriority ) +{ +xTaskHandle xCreatedTask; +portBASE_TYPE xResult; +sys_thread_t xReturn; + + xResult = xTaskCreate( pxThread, ( signed char * ) pcName, iStackSize, pvArg, iPriority, &xCreatedTask ); + + if( xResult == pdPASS ) + { + xReturn = xCreatedTask; + } + else + { + xReturn = NULL; + } + + return xReturn; +} + +/*---------------------------------------------------------------------------* + * Routine: sys_arch_protect + *---------------------------------------------------------------------------* + * Description: + * This optional function does a "fast" critical region protection and + * returns the previous protection level. This function is only called + * during very short critical regions. An embedded system which supports + * ISR-based drivers might want to implement this function by disabling + * interrupts. Task-based systems might want to implement this by using + * a mutex or disabling tasking. This function should support recursive + * calls from the same task or interrupt. In other words, + * sys_arch_protect() could be called while already protected. In + * that case the return value indicates that it is already protected. + * + * sys_arch_protect() is only required if your port is supporting an + * operating system. + * Outputs: + * sys_prot_t -- Previous protection level (not used here) + *---------------------------------------------------------------------------*/ +sys_prot_t sys_arch_protect( void ) +{ + if( is_inside_isr() == pdFALSE ) + { + taskENTER_CRITICAL(); + } + return ( sys_prot_t ) 1; +} + +/*---------------------------------------------------------------------------* + * Routine: sys_arch_unprotect + *---------------------------------------------------------------------------* + * Description: + * This optional function does a "fast" set of critical region + * protection to the value specified by pval. See the documentation for + * sys_arch_protect() for more information. This function is only + * required if your port is supporting an operating system. + * Inputs: + * sys_prot_t -- Previous protection level (not used here) + *---------------------------------------------------------------------------*/ +void sys_arch_unprotect( sys_prot_t xValue ) +{ + (void) xValue; + if( is_inside_isr() == pdFALSE ) + { + taskEXIT_CRITICAL(); + } +} + +/* + * Prints an assertion messages and aborts execution. + */ +void sys_assert( const char *pcMessage ) +{ + (void) pcMessage; + + for (;;) + { + } +} +/*-------------------------------------------------------------------------* + * End of File: sys_arch.c + *-------------------------------------------------------------------------*/ From 52f9b13faf10fee6711c73f195a3346320060304 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 3 May 2016 15:10:46 +1000 Subject: [PATCH 05/10] Break out debug dump functions into their own compilation unit --- core/app_main.c | 70 ------------------------------ core/debug_dumps.c | 88 ++++++++++++++++++++++++++++++++++++++ core/exception_vectors.S | 10 ++--- core/include/debug_dumps.h | 29 +++++++++++++ 4 files changed, 122 insertions(+), 75 deletions(-) create mode 100644 core/debug_dumps.c create mode 100644 core/include/debug_dumps.h diff --git a/core/app_main.c b/core/app_main.c index f3cbff4..6eb8195 100644 --- a/core/app_main.c +++ b/core/app_main.c @@ -84,8 +84,6 @@ static void IRAM set_spi0_divisor(uint32_t divisor); static void zero_bss(void); static void init_networking(uint8_t *phy_info, uint8_t *mac_addr); static void init_g_ic(void); -static void dump_excinfo(void); -static void dump_stack(uint32_t *sp); static void user_start_phase2(void); static void dump_flash_sector(uint32_t start_sector, uint32_t length); static void dump_flash_config_sectors(uint32_t start_sector); @@ -147,25 +145,6 @@ static void IRAM set_spi0_divisor(uint32_t divisor) { SPI(0).CTRL0 = SET_FIELD(SPI(0).CTRL0, SPI_CTRL0_CLOCK, clkdiv); } -// .text+0x148 -void IRAM sdk_user_fatal_exception_handler(uint32_t *sp) { - if (!sdk_NMIIrqIsOn) { - vPortEnterCritical(); - do { - DPORT.DPORT0 &= 0xffffffe0; - } while (DPORT.DPORT0 & 0x00000001); - } - Cache_Read_Disable(); - Cache_Read_Enable(0, 0, 1); - dump_excinfo(); - if (sp) - dump_stack(sp); - uart_flush_txfifo(0); - uart_flush_txfifo(1); - sdk_system_restart_in_nmi(); - halt(); -} - static void IRAM default_putc(char c) { uart_putc(0, c); @@ -335,55 +314,6 @@ static void init_g_ic(void) { } } -// .Lfunc008 -- .irom0.text+0x2a0 -static void dump_excinfo(void) { - uint32_t exccause, epc1, epc2, epc3, excvaddr, depc, excsave1; - uint32_t excinfo[8]; - - RSR(exccause, exccause); - printf("Fatal exception (%d): \n", (int)exccause); - RSR(epc1, epc1); - RSR(epc2, epc2); - RSR(epc3, epc3); - RSR(excvaddr, excvaddr); - RSR(depc, depc); - RSR(excsave1, excsave1); - printf("%s=0x%08x\n", "epc1", epc1); - printf("%s=0x%08x\n", "epc2", epc2); - printf("%s=0x%08x\n", "epc3", epc3); - printf("%s=0x%08x\n", "excvaddr", excvaddr); - printf("%s=0x%08x\n", "depc", depc); - printf("%s=0x%08x\n", "excsave1", excsave1); - sdk_system_rtc_mem_read(0, excinfo, 32); // Why? - excinfo[0] = 2; - excinfo[1] = exccause; - excinfo[2] = epc1; - excinfo[3] = epc2; - excinfo[4] = epc3; - excinfo[5] = excvaddr; - excinfo[6] = depc; - excinfo[7] = excsave1; - sdk_system_rtc_mem_write(0, excinfo, 32); -} - -/* There's a lot of smart stuff we could do while dumping stack - but for now we just dump a likely looking section of stack - memory -*/ -static void dump_stack(uint32_t *sp) { - printf("\nStack: SP=%p\n", sp); - for(uint32_t *p = sp; p < sp + 32; p += 4) { - if((intptr_t)p >= 0x3fffc000) { - break; /* approximate end of RAM */ - } - printf("%p: %08x %08x %08x %08x\n", p, p[0], p[1], p[2], p[3]); - if(p[0] == 0xa5a5a5a5 && p[1] == 0xa5a5a5a5 - && p[2] == 0xa5a5a5a5 && p[3] == 0xa5a5a5a5) { - break; /* FreeRTOS uses this pattern to mark untouched stack space */ - } - } -} - // .irom0.text+0x398 void sdk_wdt_init(void) { WDT.CTRL &= ~WDT_CTRL_ENABLE; diff --git a/core/debug_dumps.c b/core/debug_dumps.c new file mode 100644 index 0000000..6075f40 --- /dev/null +++ b/core/debug_dumps.c @@ -0,0 +1,88 @@ +/* Code for dumping status/debug output/etc, including fatal + * exception handling. + * + * Part of esp-open-rtos + * + * Partially reverse engineered from MIT licensed Espressif RTOS SDK Copyright (C) Espressif Systems. + * Additions Copyright (C) 2015 Superhouse Automation Pty Ltd + * BSD Licensed as described in the file LICENSE + */ +#include +#include +#include +#include + +#include "debug_dumps.h" +#include "common_macros.h" +#include "xtensa_ops.h" +#include "esp/rom.h" +#include "esp/uart.h" +#include "espressif/esp_common.h" +#include "sdk_internal.h" + +void dump_excinfo(void) { + uint32_t exccause, epc1, epc2, epc3, excvaddr, depc, excsave1; + uint32_t excinfo[8]; + + RSR(exccause, exccause); + printf("Fatal exception (%d): \n", (int)exccause); + RSR(epc1, epc1); + RSR(epc2, epc2); + RSR(epc3, epc3); + RSR(excvaddr, excvaddr); + RSR(depc, depc); + RSR(excsave1, excsave1); + printf("%s=0x%08x\n", "epc1", epc1); + printf("%s=0x%08x\n", "epc2", epc2); + printf("%s=0x%08x\n", "epc3", epc3); + printf("%s=0x%08x\n", "excvaddr", excvaddr); + printf("%s=0x%08x\n", "depc", depc); + printf("%s=0x%08x\n", "excsave1", excsave1); + sdk_system_rtc_mem_read(0, excinfo, 32); // Why? + excinfo[0] = 2; + excinfo[1] = exccause; + excinfo[2] = epc1; + excinfo[3] = epc2; + excinfo[4] = epc3; + excinfo[5] = excvaddr; + excinfo[6] = depc; + excinfo[7] = excsave1; + sdk_system_rtc_mem_write(0, excinfo, 32); +} + +/* There's a lot of smart stuff we could do while dumping stack + but for now we just dump a likely looking section of stack + memory +*/ +void dump_stack(uint32_t *sp) { + printf("\nStack: SP=%p\n", sp); + for(uint32_t *p = sp; p < sp + 32; p += 4) { + if((intptr_t)p >= 0x3fffc000) { + break; /* approximate end of RAM */ + } + printf("%p: %08x %08x %08x %08x\n", p, p[0], p[1], p[2], p[3]); + if(p[0] == 0xa5a5a5a5 && p[1] == 0xa5a5a5a5 + && p[2] == 0xa5a5a5a5 && p[3] == 0xa5a5a5a5) { + break; /* FreeRTOS uses this pattern to mark untouched stack space */ + } + } +} + +void IRAM fatal_exception_handler(uint32_t *sp) { + if (!sdk_NMIIrqIsOn) { + vPortEnterCritical(); + do { + DPORT.DPORT0 &= 0xffffffe0; + } while (DPORT.DPORT0 & 0x00000001); + } + Cache_Read_Disable(); + Cache_Read_Enable(0, 0, 1); + dump_excinfo(); + if (sp) + dump_stack(sp); + uart_flush_txfifo(0); + uart_flush_txfifo(1); + sdk_system_restart_in_nmi(); + while(1) {} +} + diff --git a/core/exception_vectors.S b/core/exception_vectors.S index 1980a0a..ba8fb25 100644 --- a/core/exception_vectors.S +++ b/core/exception_vectors.S @@ -69,7 +69,7 @@ DebugExceptionVector: wsr a0, excsave2 mov a2, a1 - call0 sdk_user_fatal_exception_handler + call0 fatal_exception_handler rfi 2 .org VecBase + 0x20 @@ -83,7 +83,7 @@ KernelExceptionVector: break 1, 0 mov a2, a1 - call0 sdk_user_fatal_exception_handler + call0 fatal_exception_handler rfe .org VecBase + 0x50 @@ -101,7 +101,7 @@ DoubleExceptionVector: break 1, 4 mov a2, a1 - call0 sdk_user_fatal_exception_handler + call0 fatal_exception_handler /* Reset vector at offset 0x80 is unused, as vecbase gets reset to mask ROM * vectors on chip reset. */ @@ -259,7 +259,7 @@ LoadStoreErrorHandler: l32i a4, sp, 0x10 rsr a1, excsave1 mov a2, a1 - call0 sdk_user_fatal_exception_handler + call0 fatal_exception_handler .balign 4 .LSE_assign_a1: @@ -520,7 +520,7 @@ UserExceptionHandler: .LUserFailOtherExceptionCause: break 1, 1 addi a2, a1, 0x50 /* UserExceptionHandler pushes stack down 0x50 */ - call0 sdk_user_fatal_exception_handler + call0 fatal_exception_handler /* _xt_user_exit is pushed onto the stack as part of the user exception handler, restores same set registers which were saved there and returns from exception */ diff --git a/core/include/debug_dumps.h b/core/include/debug_dumps.h new file mode 100644 index 0000000..d918043 --- /dev/null +++ b/core/include/debug_dumps.h @@ -0,0 +1,29 @@ +/* Functions for dumping status/debug output/etc, including fatal + * exception handling. + * + * Part of esp-open-rtos + * + * Copyright (C) 2015-2016 Superhouse Automation Pty Ltd + * BSD Licensed as described in the file LICENSE + */ +#ifndef _DEBUG_DUMPS_H +#define _DEBUG_DUMPS_H +#include + +/* Dump stack memory starting from stack pointer address sp. */ +void dump_stack(uint32_t *sp); + +/* Called from exception_vectors.S when a fatal exception occurs. + + Probably not useful to be called in other contexts. +*/ +void fatal_exception_handler(uint32_t *sp); + +/* Dump the current exception register state. + + Probably mostly useful when called from fatal exception handler. +*/ +void dump_excinfo(void); + + +#endif From cf350efd8aa6b7eeb9f4ffe81a4e926c7963b4d8 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 3 May 2016 15:37:32 +1000 Subject: [PATCH 06/10] Dump register state on fatal exception --- core/debug_dumps.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/core/debug_dumps.c b/core/debug_dumps.c index 6075f40..5878ae8 100644 --- a/core/debug_dumps.c +++ b/core/debug_dumps.c @@ -68,6 +68,22 @@ void dump_stack(uint32_t *sp) { } } +/* Dump exception status registers as stored above 'sp' + by the interrupt handler preamble +*/ +void dump_exception_registers(uint32_t *sp) { + uint32_t excsave1; + uint32_t *saved = sp - (0x50 / sizeof(uint32_t)); + printf("Registers:\n"); + RSR(excsave1, excsave1); + printf("a0 %08x ", excsave1); + printf("a1 %08x ", (intptr_t)sp); + for(int a = 2; a < 14; a++) { + printf("a%-2d %08x%c", a, saved[a+3], a == 3 || a == 7 || a == 11 ? '\n':' '); + } + printf("SAR %08x\n", saved[0x13]); +} + void IRAM fatal_exception_handler(uint32_t *sp) { if (!sdk_NMIIrqIsOn) { vPortEnterCritical(); @@ -78,11 +94,12 @@ void IRAM fatal_exception_handler(uint32_t *sp) { Cache_Read_Disable(); Cache_Read_Enable(0, 0, 1); dump_excinfo(); - if (sp) + if (sp) { + dump_exception_registers(sp); dump_stack(sp); + } uart_flush_txfifo(0); uart_flush_txfifo(1); sdk_system_restart_in_nmi(); while(1) {} } - From 36886412e627f95ec8e847f0262b617be572a772 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Sat, 7 May 2016 18:21:13 +1000 Subject: [PATCH 07/10] Add abort() implementation Also reduces the IRAM footprint of the fatal exception handler, as only the prelude (which disables interrupts & enables the flash mapping) is in IRAM now. Closes #54, relevant to #133. --- core/app_main.c | 8 +- core/debug_dumps.c | 88 ++++++++++++++++--- core/include/debug_dumps.h | 9 +- core/include/xtensa_ops.h | 14 +++ core/newlib_syscalls.c | 2 +- .../unaligned_load/unaligned_load.c | 2 +- examples/http_get_mbedtls/http_get_mbedtls.c | 6 +- examples/tls_server/tls_server.c | 8 +- lwip/include/arch/cc.h | 2 +- 9 files changed, 106 insertions(+), 33 deletions(-) diff --git a/core/app_main.c b/core/app_main.c index 6eb8195..fea9adc 100644 --- a/core/app_main.c +++ b/core/app_main.c @@ -40,8 +40,6 @@ void user_init(void); #define RTCMEM_BACKUP_PHY_VER 31 #define RTCMEM_SYSTEM_PP_VER 62 -#define halt() while (1) {} - extern uint32_t _bss_start; extern uint32_t _bss_end; @@ -100,11 +98,11 @@ static void IRAM get_otp_mac_address(uint8_t *buf) { if (!(otp_flags & 0x8000)) { //FIXME: do we really need this check? printf("Firmware ONLY supports ESP8266!!!\n"); - halt(); + abort(); } if (otp_id0 == 0 && otp_id1 == 0) { printf("empty otp\n"); - halt(); + abort(); } if (otp_flags & 0x1000) { // If bit 12 is set, it indicates that the vendor portion of the MAC @@ -258,7 +256,7 @@ static void zero_bss(void) { static void init_networking(uint8_t *phy_info, uint8_t *mac_addr) { if (sdk_register_chipv6_phy(phy_info)) { printf("FATAL: sdk_register_chipv6_phy failed"); - halt(); + abort(); } uart_set_baud(0, 74906); uart_set_baud(1, 74906); diff --git a/core/debug_dumps.c b/core/debug_dumps.c index 5878ae8..7bea87a 100644 --- a/core/debug_dumps.c +++ b/core/debug_dumps.c @@ -1,5 +1,5 @@ /* Code for dumping status/debug output/etc, including fatal - * exception handling. + * exception handling & abort implementation. * * Part of esp-open-rtos * @@ -20,7 +20,49 @@ #include "espressif/esp_common.h" #include "sdk_internal.h" -void dump_excinfo(void) { +/* Forward declarations */ +static void IRAM fatal_handler_prelude(void); +/* Inner parts of crash handlers marked noinline to ensure they don't inline into IRAM. */ +static void __attribute__((noinline)) __attribute__((noreturn)) fatal_exception_handler_inner(uint32_t *sp); +static void __attribute__((noinline)) __attribute__((noreturn)) abort_handler_inner(uint32_t *caller, uint32_t *sp); + +/* fatal_exception_handler called from any unhandled user exception + * + * (similar to a hard fault on other processor architectures) + * + * This function is run from IRAM, but the majority of the handler + * runs from flash after fatal_handler_prelude ensures it is mapped + * safely. + */ +void IRAM __attribute__((noreturn)) fatal_exception_handler(uint32_t *sp) { + fatal_handler_prelude(); + fatal_exception_handler_inner(sp); +} + +/* Abort implementation + * + * Replaces the weak-linked abort implementation provided by newlib libc. + * + * Disable interrupts, enable flash mapping, dump stack & caller + * address, restart. + * + * This function is run from IRAM, but the majority of the abort + * handler runs from flash after fatal_handler_prelude ensures it is + * mapped safely. + * + */ +void IRAM abort(void) { + uint32_t *sp, *caller; + RETADDR(caller); + /* abort() caller is one instruction before our return address */ + caller = (uint32_t *)((intptr_t)caller - 3); + SP(sp); + fatal_handler_prelude(); + abort_handler_inner(caller, sp); +} + +/* Dump exception information from special function registers */ +static void dump_excinfo(void) { uint32_t exccause, epc1, epc2, epc3, excvaddr, depc, excsave1; uint32_t excinfo[8]; @@ -50,9 +92,13 @@ void dump_excinfo(void) { sdk_system_rtc_mem_write(0, excinfo, 32); } -/* There's a lot of smart stuff we could do while dumping stack - but for now we just dump a likely looking section of stack - memory +/* dump stack memory (frames above sp) to stdout + + There's a lot of smart stuff we could do while dumping stack + but for now we just dump what looks like our stack region. + + Probably dumps more memory than it needs to, the first instance of + 0xa5a5a5a5 probably constitutes the end of our stack. */ void dump_stack(uint32_t *sp) { printf("\nStack: SP=%p\n", sp); @@ -68,10 +114,10 @@ void dump_stack(uint32_t *sp) { } } -/* Dump exception status registers as stored above 'sp' - by the interrupt handler preamble +/* Dump normal registers that were stored above 'sp' + by the exception handler preamble */ -void dump_exception_registers(uint32_t *sp) { +void dump_registers_in_exception_handler(uint32_t *sp) { uint32_t excsave1; uint32_t *saved = sp - (0x50 / sizeof(uint32_t)); printf("Registers:\n"); @@ -84,7 +130,11 @@ void dump_exception_registers(uint32_t *sp) { printf("SAR %08x\n", saved[0x13]); } -void IRAM fatal_exception_handler(uint32_t *sp) { + +/* Prelude ensures exceptions/NMI off and flash is mapped, allowing + calls to non-IRAM functions. +*/ +static void IRAM fatal_handler_prelude(void) { if (!sdk_NMIIrqIsOn) { vPortEnterCritical(); do { @@ -93,9 +143,15 @@ void IRAM fatal_exception_handler(uint32_t *sp) { } Cache_Read_Disable(); Cache_Read_Enable(0, 0, 1); +} + +/* Main part of fatal exception handler, is run from flash to save + some IRAM. +*/ +static void fatal_exception_handler_inner(uint32_t *sp) { dump_excinfo(); if (sp) { - dump_exception_registers(sp); + dump_registers_in_exception_handler(sp); dump_stack(sp); } uart_flush_txfifo(0); @@ -103,3 +159,15 @@ void IRAM fatal_exception_handler(uint32_t *sp) { sdk_system_restart_in_nmi(); while(1) {} } + +/* Main part of abort handler, can be run from flash to save some + IRAM. +*/ +static void abort_handler_inner(uint32_t *caller, uint32_t *sp) { + printf("abort() invoked at %p.\n", caller); + dump_stack(sp); + uart_flush_txfifo(0); + uart_flush_txfifo(1); + sdk_system_restart_in_nmi(); + while(1) {} +} diff --git a/core/include/debug_dumps.h b/core/include/debug_dumps.h index d918043..1a76d26 100644 --- a/core/include/debug_dumps.h +++ b/core/include/debug_dumps.h @@ -17,13 +17,6 @@ void dump_stack(uint32_t *sp); Probably not useful to be called in other contexts. */ -void fatal_exception_handler(uint32_t *sp); - -/* Dump the current exception register state. - - Probably mostly useful when called from fatal exception handler. -*/ -void dump_excinfo(void); - +void __attribute__((noreturn)) fatal_exception_handler(uint32_t *sp); #endif diff --git a/core/include/xtensa_ops.h b/core/include/xtensa_ops.h index 49d1fbe..1ef68e9 100644 --- a/core/include/xtensa_ops.h +++ b/core/include/xtensa_ops.h @@ -14,6 +14,20 @@ // GCC macros for reading, writing, and exchanging Xtensa processor special // registers: +/* Read stack pointer to variable. + * + * Note that the compiler will push a stack frame (minimum 16 bytes) + * in the prelude of a C function that calls any other functions. + */ +#define SP(var) asm volatile ("mov %0, a1" : "=r" (var)); + +/* Read the function return address to a variable. + * + * Depends on the containing function being simple enough that a0 is + * being used as a working register. + */ +#define RETADDR(var) asm volatile ("mov %0, a0" : "=r" (var)) + #define RSR(var, reg) asm volatile ("rsr %0, " #reg : "=r" (var)); #define WSR(var, reg) asm volatile ("wsr %0, " #reg : : "r" (var)); #define XSR(var, reg) asm volatile ("xsr %0, " #reg : "+r" (var)); diff --git a/core/newlib_syscalls.c b/core/newlib_syscalls.c index b414481..0d4b95e 100644 --- a/core/newlib_syscalls.c +++ b/core/newlib_syscalls.c @@ -25,7 +25,7 @@ IRAM caddr_t _sbrk_r (struct _reent *r, int incr) if (heap_end + incr > stack_ptr) { _write (1, "_sbrk: Heap collided with stack\n", 32); - while(1) {} + abort(); } */ heap_end += incr; diff --git a/examples/experiments/unaligned_load/unaligned_load.c b/examples/experiments/unaligned_load/unaligned_load.c index 258b4d0..4244804 100644 --- a/examples/experiments/unaligned_load/unaligned_load.c +++ b/examples/experiments/unaligned_load/unaligned_load.c @@ -305,7 +305,7 @@ static void test_system_interaction() } uint32_t ticks = xTaskGetTickCount() - start; printf("Timer interaction test PASSED after %dms.\n", ticks*portTICK_RATE_MS); - while(1) {} + abort(); } /* The following "sanity tests" are designed to try to execute every code path diff --git a/examples/http_get_mbedtls/http_get_mbedtls.c b/examples/http_get_mbedtls/http_get_mbedtls.c index 017f8fc..7aad0e3 100644 --- a/examples/http_get_mbedtls/http_get_mbedtls.c +++ b/examples/http_get_mbedtls/http_get_mbedtls.c @@ -115,7 +115,7 @@ void http_get_task(void *pvParameters) strlen(pers))) != 0) { printf(" failed\n ! mbedtls_ctr_drbg_seed returned %d\n", ret); - while(1) {} /* todo: replace with abort() */ + abort(); } printf(" ok\n"); @@ -129,7 +129,7 @@ void http_get_task(void *pvParameters) if(ret < 0) { printf(" failed\n ! mbedtls_x509_crt_parse returned -0x%x\n\n", -ret); - while(1) {} /* todo: replace with abort() */ + abort(); } printf(" ok (%d skipped)\n", ret); @@ -138,7 +138,7 @@ void http_get_task(void *pvParameters) if((ret = mbedtls_ssl_set_hostname(&ssl, WEB_SERVER)) != 0) { printf(" failed\n ! mbedtls_ssl_set_hostname returned %d\n\n", ret); - while(1) {} /* todo: replace with abort() */ + abort(); } /* diff --git a/examples/tls_server/tls_server.c b/examples/tls_server/tls_server.c index d253bc9..9030dc0 100644 --- a/examples/tls_server/tls_server.c +++ b/examples/tls_server/tls_server.c @@ -85,7 +85,7 @@ void tls_server_task(void *pvParameters) strlen(pers))) != 0) { printf(" failed\n ! mbedtls_ctr_drbg_seed returned %d\n", ret); - while(1) {} /* todo: replace with abort() */ + abort(); } printf(" ok\n"); @@ -99,7 +99,7 @@ void tls_server_task(void *pvParameters) if(ret < 0) { printf(" failed\n ! mbedtls_x509_crt_parse returned -0x%x\n\n", -ret); - while(1) {} /* todo: replace with abort() */ + abort(); } printf(" ok (%d skipped)\n", ret); @@ -109,7 +109,7 @@ void tls_server_task(void *pvParameters) if(ret != 0) { printf(" failed\n ! mbedtls_pk_parse_key returned - 0x%x\n\n", -ret); - while(1) { } /*todo: replace with abort() */ + abort(); } printf(" ok\n"); @@ -134,7 +134,7 @@ void tls_server_task(void *pvParameters) if( ( ret = mbedtls_ssl_conf_own_cert( &conf, &srvcert, &pkey ) ) != 0 ) { printf( " failed\n ! mbedtls_ssl_conf_own_cert returned %d\n\n", ret ); - while(1) { } + abort(); } mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg); diff --git a/lwip/include/arch/cc.h b/lwip/include/arch/cc.h index a09ea0c..04dab26 100644 --- a/lwip/include/arch/cc.h +++ b/lwip/include/arch/cc.h @@ -94,7 +94,7 @@ typedef int sys_prot_t; _Pragma("GCC diagnostic pop") \ } while(0) #define LWIP_PLATFORM_ASSERT(x) do { printf("Assertion \"%s\" failed at line %d in %s\n", \ - x, __LINE__, __FILE__); while(1) {} } while(0) + x, __LINE__, __FILE__); abort(); } while(0) #define LWIP_ERROR(message, expression, handler) do { if (!(expression)) { \ printf("Assertion \"%s\" failed at line %d in %s\n", message, __LINE__, __FILE__); \ From efedd24624488c725dfd79620b88927bed7951f9 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Sat, 7 May 2016 19:14:48 +1000 Subject: [PATCH 08/10] fatal exception handler: Only dump "registers" from stack for fatal user exceptions --- core/debug_dumps.c | 12 +++++++----- core/exception_vectors.S | 5 +++++ core/include/debug_dumps.h | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/core/debug_dumps.c b/core/debug_dumps.c index 7bea87a..15396ac 100644 --- a/core/debug_dumps.c +++ b/core/debug_dumps.c @@ -23,7 +23,7 @@ /* Forward declarations */ static void IRAM fatal_handler_prelude(void); /* Inner parts of crash handlers marked noinline to ensure they don't inline into IRAM. */ -static void __attribute__((noinline)) __attribute__((noreturn)) fatal_exception_handler_inner(uint32_t *sp); +static void __attribute__((noinline)) __attribute__((noreturn)) fatal_exception_handler_inner(uint32_t *sp, bool registers_saved_on_stack); static void __attribute__((noinline)) __attribute__((noreturn)) abort_handler_inner(uint32_t *caller, uint32_t *sp); /* fatal_exception_handler called from any unhandled user exception @@ -34,9 +34,9 @@ static void __attribute__((noinline)) __attribute__((noreturn)) abort_handler_in * runs from flash after fatal_handler_prelude ensures it is mapped * safely. */ -void IRAM __attribute__((noreturn)) fatal_exception_handler(uint32_t *sp) { +void IRAM __attribute__((noreturn)) fatal_exception_handler(uint32_t *sp, bool registers_saved_on_stack) { fatal_handler_prelude(); - fatal_exception_handler_inner(sp); + fatal_exception_handler_inner(sp, registers_saved_on_stack); } /* Abort implementation @@ -148,10 +148,12 @@ static void IRAM fatal_handler_prelude(void) { /* Main part of fatal exception handler, is run from flash to save some IRAM. */ -static void fatal_exception_handler_inner(uint32_t *sp) { +static void fatal_exception_handler_inner(uint32_t *sp, bool registers_saved_on_stack) { dump_excinfo(); if (sp) { - dump_registers_in_exception_handler(sp); + if (registers_saved_on_stack) { + dump_registers_in_exception_handler(sp); + } dump_stack(sp); } uart_flush_txfifo(0); diff --git a/core/exception_vectors.S b/core/exception_vectors.S index ba8fb25..000e742 100644 --- a/core/exception_vectors.S +++ b/core/exception_vectors.S @@ -69,6 +69,7 @@ DebugExceptionVector: wsr a0, excsave2 mov a2, a1 + movi a3, 0 call0 fatal_exception_handler rfi 2 @@ -83,6 +84,7 @@ KernelExceptionVector: break 1, 0 mov a2, a1 + movi a3, 0 call0 fatal_exception_handler rfe @@ -101,6 +103,7 @@ DoubleExceptionVector: break 1, 4 mov a2, a1 + movi a3, 0 call0 fatal_exception_handler /* Reset vector at offset 0x80 is unused, as vecbase gets reset to mask ROM @@ -259,6 +262,7 @@ LoadStoreErrorHandler: l32i a4, sp, 0x10 rsr a1, excsave1 mov a2, a1 + movi a3, 0 call0 fatal_exception_handler .balign 4 @@ -520,6 +524,7 @@ UserExceptionHandler: .LUserFailOtherExceptionCause: break 1, 1 addi a2, a1, 0x50 /* UserExceptionHandler pushes stack down 0x50 */ + movi a3, 1 call0 fatal_exception_handler /* _xt_user_exit is pushed onto the stack as part of the user exception handler, diff --git a/core/include/debug_dumps.h b/core/include/debug_dumps.h index 1a76d26..809b87a 100644 --- a/core/include/debug_dumps.h +++ b/core/include/debug_dumps.h @@ -17,6 +17,6 @@ void dump_stack(uint32_t *sp); Probably not useful to be called in other contexts. */ -void __attribute__((noreturn)) fatal_exception_handler(uint32_t *sp); +void __attribute__((noreturn)) fatal_exception_handler(uint32_t *sp, bool registers_saved_on_stack); #endif From 981c87899b565a9def5f12fc6ac337cf93078bf3 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Sun, 15 May 2016 10:36:33 +1000 Subject: [PATCH 09/10] Add heap information to fatal exception & abort dumps --- FreeRTOS/Source/portable/esp8266/port.c | 5 ++-- core/debug_dumps.c | 39 +++++++++++++++++++++++++ core/include/debug_dumps.h | 5 +++- core/include/xtensa_ops.h | 2 +- 4 files changed, 47 insertions(+), 4 deletions(-) diff --git a/FreeRTOS/Source/portable/esp8266/port.c b/FreeRTOS/Source/portable/esp8266/port.c index 21a0d55..a9dac2d 100644 --- a/FreeRTOS/Source/portable/esp8266/port.c +++ b/FreeRTOS/Source/portable/esp8266/port.c @@ -73,6 +73,7 @@ #include #include #include +#include #include "FreeRTOS.h" #include "task.h" @@ -87,7 +88,7 @@ char level1_int_disabled; After tasks start, task stacks are all allocated from the heap and FreeRTOS checks for stack overflow. */ -static uint32_t xPortSupervisorStackPointer; +uint32_t xPortSupervisorStackPointer; /* * Stack initialization @@ -220,7 +221,7 @@ size_t xPortGetFreeHeapSize( void ) uint32_t sp = xPortSupervisorStackPointer; if(sp == 0) /* scheduler not started */ - __asm__ __volatile__ ("mov %0, a1\n" : "=a"(sp)); + SP(sp); return sp - brk_val + mi.fordblks; } diff --git a/core/debug_dumps.c b/core/debug_dumps.c index 15396ac..8b20c1b 100644 --- a/core/debug_dumps.c +++ b/core/debug_dumps.c @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include "debug_dumps.h" #include "common_macros.h" @@ -156,18 +158,55 @@ static void fatal_exception_handler_inner(uint32_t *sp, bool registers_saved_on_ } dump_stack(sp); } + dump_heapinfo(); uart_flush_txfifo(0); uart_flush_txfifo(1); sdk_system_restart_in_nmi(); while(1) {} } +void dump_heapinfo(void) +{ + extern char _heap_start; + extern uint32_t xPortSupervisorStackPointer; + struct mallinfo mi = mallinfo(); + uint32_t brk_val = (uint32_t) sbrk(0); + uint32_t sp = xPortSupervisorStackPointer; + if(sp == 0) { + SP(sp); + } + + /* Total free heap is all memory that could be allocated via + malloc (assuming fragmentation doesn't become a problem) */ + printf("\nFree Heap: %d\n", sp - brk_val + mi.fordblks); + + /* delta between brk & supervisor sp is the contiguous memory + region that is available to be put into heap space via + brk(). */ + printf("_heap_start %p brk 0x%08x supervisor sp 0x%08x sp-brk %d bytes\n", + &_heap_start, brk_val, sp, sp-brk_val); + + /* arena/fordblks/uordblks determines the amount of free space + inside the heap region already added via brk(). May be + fragmented. + + The values in parentheses are the values used internally by + nano-mallocr.c, the field names outside parentheses are the + POSIX compliant field names of the mallinfo structure. + + "arena" should be equal to brk-_heap_start ie total size available. + */ + printf("arena (total_size) %d fordblks (free_size) %d uordblocks (used_size) %d\n", + mi.arena, mi.fordblks, mi.uordblks); +} + /* Main part of abort handler, can be run from flash to save some IRAM. */ static void abort_handler_inner(uint32_t *caller, uint32_t *sp) { printf("abort() invoked at %p.\n", caller); dump_stack(sp); + dump_heapinfo(); uart_flush_txfifo(0); uart_flush_txfifo(1); sdk_system_restart_in_nmi(); diff --git a/core/include/debug_dumps.h b/core/include/debug_dumps.h index 809b87a..ab3c972 100644 --- a/core/include/debug_dumps.h +++ b/core/include/debug_dumps.h @@ -10,9 +10,12 @@ #define _DEBUG_DUMPS_H #include -/* Dump stack memory starting from stack pointer address sp. */ +/* Dump stack memory to stdout, starting from stack pointer address sp. */ void dump_stack(uint32_t *sp); +/* Dump heap statistics to stdout */ +void dump_heapinfo(void); + /* Called from exception_vectors.S when a fatal exception occurs. Probably not useful to be called in other contexts. diff --git a/core/include/xtensa_ops.h b/core/include/xtensa_ops.h index 1ef68e9..52eba2a 100644 --- a/core/include/xtensa_ops.h +++ b/core/include/xtensa_ops.h @@ -19,7 +19,7 @@ * Note that the compiler will push a stack frame (minimum 16 bytes) * in the prelude of a C function that calls any other functions. */ -#define SP(var) asm volatile ("mov %0, a1" : "=r" (var)); +#define SP(var) asm volatile ("mov %0, a1" : "=r" (var)) /* Read the function return address to a variable. * From 1e9296f60cbe5679d1c532530071c1080ad13c06 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 17 May 2016 09:08:53 +1000 Subject: [PATCH 10/10] Fatal exceptions: Cleanly deal with exceptions that occur inside fatal_exception_handler_inner() In case of heap corruption or some other major problem, dumping details in the exception handler can cause a crash loop - so fail out if we seem to be going in circles. --- core/debug_dumps.c | 41 +++++++++++++++++++++++++----------- core/include/common_macros.h | 10 ++++----- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/core/debug_dumps.c b/core/debug_dumps.c index 8b20c1b..7f1a1ca 100644 --- a/core/debug_dumps.c +++ b/core/debug_dumps.c @@ -24,10 +24,14 @@ /* Forward declarations */ static void IRAM fatal_handler_prelude(void); -/* Inner parts of crash handlers marked noinline to ensure they don't inline into IRAM. */ -static void __attribute__((noinline)) __attribute__((noreturn)) fatal_exception_handler_inner(uint32_t *sp, bool registers_saved_on_stack); +/* Inner parts of crash handlers */ +typedef void __attribute__((noreturn)) (*fatal_exception_handler_fn)(uint32_t *sp, bool registers_saved_on_stack); +static void __attribute__((noreturn)) standard_fatal_exception_handler_inner(uint32_t *sp, bool registers_saved_on_stack); +static void __attribute__((noreturn)) second_fatal_exception_handler_inner(uint32_t *sp, bool registers_saved_on_stack); static void __attribute__((noinline)) __attribute__((noreturn)) abort_handler_inner(uint32_t *caller, uint32_t *sp); +static IRAM_DATA fatal_exception_handler_fn fatal_exception_handler_inner = standard_fatal_exception_handler_inner; + /* fatal_exception_handler called from any unhandled user exception * * (similar to a hard fault on other processor architectures) @@ -38,7 +42,8 @@ static void __attribute__((noinline)) __attribute__((noreturn)) abort_handler_in */ void IRAM __attribute__((noreturn)) fatal_exception_handler(uint32_t *sp, bool registers_saved_on_stack) { fatal_handler_prelude(); - fatal_exception_handler_inner(sp, registers_saved_on_stack); + fatal_exception_handler_fn inner_fn = fatal_exception_handler_inner; + inner_fn(sp, registers_saved_on_stack); } /* Abort implementation @@ -132,6 +137,12 @@ void dump_registers_in_exception_handler(uint32_t *sp) { printf("SAR %08x\n", saved[0x13]); } +static void __attribute__((noreturn)) post_crash_reset(void) { + uart_flush_txfifo(0); + uart_flush_txfifo(1); + sdk_system_restart_in_nmi(); + while(1) {} +} /* Prelude ensures exceptions/NMI off and flash is mapped, allowing calls to non-IRAM functions. @@ -150,7 +161,10 @@ static void IRAM fatal_handler_prelude(void) { /* Main part of fatal exception handler, is run from flash to save some IRAM. */ -static void fatal_exception_handler_inner(uint32_t *sp, bool registers_saved_on_stack) { +static void standard_fatal_exception_handler_inner(uint32_t *sp, bool registers_saved_on_stack) { + /* Replace the fatal exception handler 'inner' function so we + don't end up in a crash loop if this handler crashes. */ + fatal_exception_handler_inner = second_fatal_exception_handler_inner; dump_excinfo(); if (sp) { if (registers_saved_on_stack) { @@ -159,10 +173,16 @@ static void fatal_exception_handler_inner(uint32_t *sp, bool registers_saved_on_ dump_stack(sp); } dump_heapinfo(); - uart_flush_txfifo(0); - uart_flush_txfifo(1); - sdk_system_restart_in_nmi(); - while(1) {} + post_crash_reset(); +} + +/* This is the exception handler that gets called if a crash occurs inside the standard handler, + so we don't end up in a crash loop. It doesn't rely on contents of stack or heap. +*/ +static void second_fatal_exception_handler_inner(uint32_t *sp, bool registers_saved_on_stack) { + dump_excinfo(); + printf("Second fatal exception occured inside fatal exception handler. Can't continue.\n"); + post_crash_reset(); } void dump_heapinfo(void) @@ -207,8 +227,5 @@ static void abort_handler_inner(uint32_t *caller, uint32_t *sp) { printf("abort() invoked at %p.\n", caller); dump_stack(sp); dump_heapinfo(); - uart_flush_txfifo(0); - uart_flush_txfifo(1); - sdk_system_restart_in_nmi(); - while(1) {} + post_crash_reset(); } diff --git a/core/include/common_macros.h b/core/include/common_macros.h index 7c6dd56..4aa6248 100644 --- a/core/include/common_macros.h +++ b/core/include/common_macros.h @@ -65,18 +65,16 @@ */ #define IRAM __attribute__((section(".iram1.text"))) -/* Use this macro to place read-only data into Instruction RAM (IRAM) +/* Use this macro to place data into Instruction RAM (IRAM) instead of loaded into rodata which resides in DRAM. + (IRAM can also be written to as necessary.) + This may be useful to free up data RAM. However all data read from the instruction space must be 32-bit aligned word reads (non-aligned reads will use an interrupt routine to "fix" them and still work, but are very slow.. */ -#ifdef __cplusplus - #define IRAM_DATA __attribute__((section(".iram1.rodata"))) -#else - #define IRAM_DATA __attribute__((section(".iram1.rodata"))) const -#endif +#define IRAM_DATA __attribute__((section(".iram1.rodata"))) #endif