From 01d17dd5f9f9c87f0a90aea328ccff74e319999a Mon Sep 17 00:00:00 2001 From: daniel Date: Thu, 22 Sep 2016 12:20:56 +0800 Subject: [PATCH 001/149] Backport the static allocation feature from FreeRTOS V9.0.0 This feature allows to use static buffers (or from a pool of memory which is not controlled by FreeRTOS). In order to reduce the impact of the changes, the static feature has only been added to the queus (and in consequence to the semaphores and the mutexes) and the tasks. The Timer task is always dynamically allocated and also the idle task(s), which in the case of the ESP-IDF is ok, since we always need to have dynamic allocation enabled. --- .../freertos/include/freertos/FreeRTOS.h | 162 ++++ .../include/freertos/FreeRTOSConfig.h | 2 + components/freertos/include/freertos/queue.h | 110 ++- components/freertos/include/freertos/semphr.h | 382 +++++++- components/freertos/include/freertos/task.h | 161 +++- components/freertos/queue.c | 351 +++++--- components/freertos/tasks.c | 839 +++++++++++------- 7 files changed, 1523 insertions(+), 484 deletions(-) diff --git a/components/freertos/include/freertos/FreeRTOS.h b/components/freertos/include/freertos/FreeRTOS.h index 04b39b65e..f6c9aa497 100644 --- a/components/freertos/include/freertos/FreeRTOS.h +++ b/components/freertos/include/freertos/FreeRTOS.h @@ -74,6 +74,7 @@ * Include the generic headers required for the FreeRTOS port being used. */ #include +#include "sys/reent.h" /* * If stdint.h cannot be located then: @@ -739,6 +740,20 @@ extern "C" { #define portTICK_TYPE_IS_ATOMIC 0 #endif +#ifndef configSUPPORT_STATIC_ALLOCATION + /* Defaults to 0 for backward compatibility. */ + #define configSUPPORT_STATIC_ALLOCATION 0 +#endif + +#ifndef configSUPPORT_DYNAMIC_ALLOCATION + /* Defaults to 1 for backward compatibility. */ + #define configSUPPORT_DYNAMIC_ALLOCATION 1 +#endif + +#if( ( configSUPPORT_STATIC_ALLOCATION == 0 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 0 ) ) + #error configSUPPORT_STATIC_ALLOCATION and configSUPPORT_DYNAMIC_ALLOCATION cannot both be 0, but can both be 1. +#endif + #if( portTICK_TYPE_IS_ATOMIC == 0 ) /* Either variables of tick type cannot be read atomically, or portTICK_TYPE_IS_ATOMIC was not set - map the critical sections used when @@ -791,6 +806,153 @@ V8 if desired. */ #define configESP32_PER_TASK_DATA 1 #endif +/* + * In line with software engineering best practice, FreeRTOS implements a strict + * data hiding policy, so the real structures used by FreeRTOS to maintain the + * state of tasks, queues, semaphores, etc. are not accessible to the application + * code. However, if the application writer wants to statically allocate such + * an object then the size of the object needs to be know. Dummy structures + * that are guaranteed to have the same size and alignment requirements of the + * real objects are used for this purpose. The dummy list and list item + * structures below are used for inclusion in such a dummy structure. + */ +struct xSTATIC_LIST_ITEM +{ + TickType_t xDummy1; + void *pvDummy2[ 4 ]; +}; +typedef struct xSTATIC_LIST_ITEM StaticListItem_t; + +/* See the comments above the struct xSTATIC_LIST_ITEM definition. */ +struct xSTATIC_MINI_LIST_ITEM +{ + TickType_t xDummy1; + void *pvDummy2[ 2 ]; +}; +typedef struct xSTATIC_MINI_LIST_ITEM StaticMiniListItem_t; + +/* See the comments above the struct xSTATIC_LIST_ITEM definition. */ +typedef struct xSTATIC_LIST +{ + UBaseType_t uxDummy1; + void *pvDummy2; + StaticMiniListItem_t xDummy3; +} StaticList_t; + +/* + * In line with software engineering best practice, especially when supplying a + * library that is likely to change in future versions, FreeRTOS implements a + * strict data hiding policy. This means the Task structure used internally by + * FreeRTOS is not accessible to application code. However, if the application + * writer wants to statically allocate the memory required to create a task then + * the size of the task object needs to be know. The StaticTask_t structure + * below is provided for this purpose. Its sizes and alignment requirements are + * guaranteed to match those of the genuine structure, no matter which + * architecture is being used, and no matter how the values in FreeRTOSConfig.h + * are set. Its contents are somewhat obfuscated in the hope users will + * recognise that it would be unwise to make direct use of the structure members. + */ +typedef struct xSTATIC_TCB +{ + void *pxDummy1; + #if ( portUSING_MPU_WRAPPERS == 1 ) + xMPU_SETTINGS xDummy2; + #endif + StaticListItem_t xDummy3[ 2 ]; + UBaseType_t uxDummy5; + void *pxDummy6; + uint8_t ucDummy7[ configMAX_TASK_NAME_LEN ]; + UBaseType_t uxDummyCoreId; + #if ( portSTACK_GROWTH > 0 ) + void *pxDummy8; + #endif + #if ( portCRITICAL_NESTING_IN_TCB == 1 ) + UBaseType_t uxDummy9; + uint32_t OldInterruptState; + #endif + #if ( configUSE_TRACE_FACILITY == 1 ) + UBaseType_t uxDummy10[ 2 ]; + #endif + #if ( configUSE_MUTEXES == 1 ) + UBaseType_t uxDummy12[ 2 ]; + #endif + #if ( configUSE_APPLICATION_TASK_TAG == 1 ) + void *pxDummy14; + #endif + #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 ) + void *pvDummy15[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ]; + #if ( configTHREAD_LOCAL_STORAGE_DELETE_CALLBACKS ) + void *pvDummyLocalStorageCallBack[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ]; + #endif + #endif + #if ( configGENERATE_RUN_TIME_STATS == 1 ) + uint32_t ulDummy16; + #endif + #if ( configUSE_NEWLIB_REENTRANT == 1 ) + struct _reent xDummy17; + #endif + #if ( configUSE_TASK_NOTIFICATIONS == 1 ) + uint32_t ulDummy18; + uint32_t ucDummy19; + #endif + #if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) ) + uint8_t uxDummy20; + #endif + +} StaticTask_t; + +/* + * In line with software engineering best practice, especially when supplying a + * library that is likely to change in future versions, FreeRTOS implements a + * strict data hiding policy. This means the Queue structure used internally by + * FreeRTOS is not accessible to application code. However, if the application + * writer wants to statically allocate the memory required to create a queue + * then the size of the queue object needs to be know. The StaticQueue_t + * structure below is provided for this purpose. Its sizes and alignment + * requirements are guaranteed to match those of the genuine structure, no + * matter which architecture is being used, and no matter how the values in + * FreeRTOSConfig.h are set. Its contents are somewhat obfuscated in the hope + * users will recognise that it would be unwise to make direct use of the + * structure members. + */ +typedef struct xSTATIC_QUEUE +{ + void *pvDummy1[ 3 ]; + + union + { + void *pvDummy2; + UBaseType_t uxDummy2; + } u; + + StaticList_t xDummy3[ 2 ]; + UBaseType_t uxDummy4[ 3 ]; + BaseType_t ucDummy5[ 2 ]; + + #if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) ) + uint8_t ucDummy6; + #endif + + #if ( configUSE_QUEUE_SETS == 1 ) + void *pvDummy7; + #endif + + #if ( configUSE_TRACE_FACILITY == 1 ) + UBaseType_t uxDummy8; + uint8_t ucDummy9; + #endif + + struct { + volatile uint32_t mux; + #ifdef CONFIG_FREERTOS_PORTMUX_DEBUG + const char *lastLockedFn; + int lastLockedLine; + #endif + } mux; + +} StaticQueue_t; +typedef StaticQueue_t StaticSemaphore_t; + #ifdef __cplusplus } #endif diff --git a/components/freertos/include/freertos/FreeRTOSConfig.h b/components/freertos/include/freertos/FreeRTOSConfig.h index 089d799dd..f3f2df73c 100644 --- a/components/freertos/include/freertos/FreeRTOSConfig.h +++ b/components/freertos/include/freertos/FreeRTOSConfig.h @@ -241,6 +241,8 @@ #define configUSE_NEWLIB_REENTRANT 1 +#define configSUPPORT_DYNAMIC_ALLOCATION 1 + /* Test FreeRTOS timers (with timer task) and more. */ /* Some files don't compile if this flag is disabled */ #define configUSE_TIMERS 1 diff --git a/components/freertos/include/freertos/queue.h b/components/freertos/include/freertos/queue.h index 2095c59b0..876f1a1b3 100644 --- a/components/freertos/include/freertos/queue.h +++ b/components/freertos/include/freertos/queue.h @@ -170,7 +170,95 @@ typedef void * QueueSetMemberHandle_t; * \defgroup xQueueCreate xQueueCreate * \ingroup QueueManagement */ -#define xQueueCreate( uxQueueLength, uxItemSize ) xQueueGenericCreate( uxQueueLength, uxItemSize, queueQUEUE_TYPE_BASE ) +#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) + #define xQueueCreate( uxQueueLength, uxItemSize ) xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) ) +#endif + +/** + * queue. h + *
+ QueueHandle_t xQueueCreateStatic(
+							  UBaseType_t uxQueueLength,
+							  UBaseType_t uxItemSize,
+							  uint8_t *pucQueueStorageBuffer,
+							  StaticQueue_t *pxQueueBuffer
+						  );
+ * 
+ * + * Creates a new queue instance, and returns a handle by which the new queue + * can be referenced. + * + * Internally, within the FreeRTOS implementation, queues use two blocks of + * memory. The first block is used to hold the queue's data structures. The + * second block is used to hold items placed into the queue. If a queue is + * created using xQueueCreate() then both blocks of memory are automatically + * dynamically allocated inside the xQueueCreate() function. (see + * http://www.freertos.org/a00111.html). If a queue is created using + * xQueueCreateStatic() then the application writer must provide the memory that + * will get used by the queue. xQueueCreateStatic() therefore allows a queue to + * be created without using any dynamic memory allocation. + * + * http://www.FreeRTOS.org/Embedded-RTOS-Queues.html + * + * @param uxQueueLength The maximum number of items that the queue can contain. + * + * @param uxItemSize The number of bytes each item in the queue will require. + * Items are queued by copy, not by reference, so this is the number of bytes + * that will be copied for each posted item. Each item on the queue must be + * the same size. + * + * @param pucQueueStorageBuffer If uxItemSize is not zero then + * pucQueueStorageBuffer must point to a uint8_t array that is at least large + * enough to hold the maximum number of items that can be in the queue at any + * one time - which is ( uxQueueLength * uxItemsSize ) bytes. If uxItemSize is + * zero then pucQueueStorageBuffer can be NULL. + * + * @param pxQueueBuffer Must point to a variable of type StaticQueue_t, which + * will be used to hold the queue's data structure. + * + * @return If the queue is created then a handle to the created queue is + * returned. If pxQueueBuffer is NULL then NULL is returned. + * + * Example usage: +
+ struct AMessage
+ {
+	char ucMessageID;
+	char ucData[ 20 ];
+ };
+
+ #define QUEUE_LENGTH 10
+ #define ITEM_SIZE sizeof( uint32_t )
+
+ // xQueueBuffer will hold the queue structure.
+ StaticQueue_t xQueueBuffer;
+
+ // ucQueueStorage will hold the items posted to the queue.  Must be at least
+ // [(queue length) * ( queue item size)] bytes long.
+ uint8_t ucQueueStorage[ QUEUE_LENGTH * ITEM_SIZE ];
+
+ void vATask( void *pvParameters )
+ {
+ QueueHandle_t xQueue1;
+
+	// Create a queue capable of containing 10 uint32_t values.
+	xQueue1 = xQueueCreate( QUEUE_LENGTH, // The number of items the queue can hold.
+							ITEM_SIZE	  // The size of each item in the queue
+							&( ucQueueStorage[ 0 ] ), // The buffer that will hold the items in the queue.
+							&xQueueBuffer ); // The buffer that will hold the queue structure.
+
+	// The queue is guaranteed to be created successfully as no dynamic memory
+	// allocation is used.  Therefore xQueue1 is now a handle to a valid queue.
+
+	// ... Rest of task code.
+ }
+ 
+ * \defgroup xQueueCreateStatic xQueueCreateStatic + * \ingroup QueueManagement + */ +#if( configSUPPORT_STATIC_ALLOCATION == 1 ) + #define xQueueCreateStatic( uxQueueLength, uxItemSize, pucQueueStorage, pxQueueBuffer ) xQueueGenericCreateStatic( ( uxQueueLength ), ( uxItemSize ), ( pucQueueStorage ), ( pxQueueBuffer ), ( queueQUEUE_TYPE_BASE ) ) +#endif /* configSUPPORT_STATIC_ALLOCATION */ /** * queue. h @@ -1479,7 +1567,9 @@ BaseType_t xQueueCRReceive( QueueHandle_t xQueue, void *pvBuffer, TickType_t xTi * these functions directly. */ QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType ) PRIVILEGED_FUNCTION; +QueueHandle_t xQueueCreateMutexStatic( const uint8_t ucQueueType, StaticQueue_t *pxStaticQueue ) PRIVILEGED_FUNCTION; QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_t uxMaxCount, const UBaseType_t uxInitialCount ) PRIVILEGED_FUNCTION; +QueueHandle_t xQueueCreateCountingSemaphoreStatic( const UBaseType_t uxMaxCount, const UBaseType_t uxInitialCount, StaticQueue_t *pxStaticQueue ) PRIVILEGED_FUNCTION; void* xQueueGetMutexHolder( QueueHandle_t xSemaphore ) PRIVILEGED_FUNCTION; /* @@ -1538,10 +1628,22 @@ BaseType_t xQueueGiveMutexRecursive( QueueHandle_t pxMutex ) PRIVILEGED_FUNCTION #endif /* - * Generic version of the queue creation function, which is in turn called by - * any queue, semaphore or mutex creation function or macro. + * Generic version of the function used to creaet a queue using dynamic memory + * allocation. This is called by other functions and macros that create other + * RTOS objects that use the queue structure as their base. */ -QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType ) PRIVILEGED_FUNCTION; +#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) + QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType ) PRIVILEGED_FUNCTION; +#endif + +/* + * Generic version of the function used to creaet a queue using dynamic memory + * allocation. This is called by other functions and macros that create other + * RTOS objects that use the queue structure as their base. + */ +#if( configSUPPORT_STATIC_ALLOCATION == 1 ) + QueueHandle_t xQueueGenericCreateStatic( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, uint8_t *pucQueueStorage, StaticQueue_t *pxStaticQueue, const uint8_t ucQueueType ) PRIVILEGED_FUNCTION; +#endif /* * Queue sets provide a mechanism to allow a task to block (pend) on a read diff --git a/components/freertos/include/freertos/semphr.h b/components/freertos/include/freertos/semphr.h index 5866ab1ec..6343d0190 100644 --- a/components/freertos/include/freertos/semphr.h +++ b/components/freertos/include/freertos/semphr.h @@ -128,19 +128,37 @@ typedef QueueHandle_t SemaphoreHandle_t; * \defgroup vSemaphoreCreateBinary vSemaphoreCreateBinary * \ingroup Semaphores */ -#define vSemaphoreCreateBinary( xSemaphore ) \ - { \ - ( xSemaphore ) = xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE ); \ - if( ( xSemaphore ) != NULL ) \ - { \ - ( void ) xSemaphoreGive( ( xSemaphore ) ); \ - } \ - } +#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) + #define vSemaphoreCreateBinary( xSemaphore ) \ + { \ + ( xSemaphore ) = xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE ); \ + if( ( xSemaphore ) != NULL ) \ + { \ + ( void ) xSemaphoreGive( ( xSemaphore ) ); \ + } \ + } +#endif /** * semphr. h *
SemaphoreHandle_t xSemaphoreCreateBinary( void )
* + * Creates a new binary semaphore instance, and returns a handle by which the + * new semaphore can be referenced. + * + * In many usage scenarios it is faster and more memory efficient to use a + * direct to task notification in place of a binary semaphore! + * http://www.freertos.org/RTOS-task-notifications.html + * + * Internally, within the FreeRTOS implementation, binary semaphores use a block + * of memory, in which the semaphore structure is stored. If a binary semaphore + * is created using xSemaphoreCreateBinary() then the required memory is + * automatically dynamically allocated inside the xSemaphoreCreateBinary() + * function. (see http://www.freertos.org/a00111.html). If a binary semaphore + * is created using xSemaphoreCreateBinaryStatic() then the application writer + * must provide the memory. xSemaphoreCreateBinaryStatic() therefore allows a + * binary semaphore to be created without using any dynamic memory allocation. + * * The old vSemaphoreCreateBinary() macro is now deprecated in favour of this * xSemaphoreCreateBinary() function. Note that binary semaphores created using * the vSemaphoreCreateBinary() macro are created in a state such that the @@ -182,7 +200,68 @@ typedef QueueHandle_t SemaphoreHandle_t; * \defgroup vSemaphoreCreateBinary vSemaphoreCreateBinary * \ingroup Semaphores */ -#define xSemaphoreCreateBinary() xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE ) +#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) + #define xSemaphoreCreateBinary() xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE ) +#endif + +/** + * semphr. h + *
SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuffer )
+ * + * Creates a new binary semaphore instance, and returns a handle by which the + * new semaphore can be referenced. + * + * NOTE: In many usage scenarios it is faster and more memory efficient to use a + * direct to task notification in place of a binary semaphore! + * http://www.freertos.org/RTOS-task-notifications.html + * + * Internally, within the FreeRTOS implementation, binary semaphores use a block + * of memory, in which the semaphore structure is stored. If a binary semaphore + * is created using xSemaphoreCreateBinary() then the required memory is + * automatically dynamically allocated inside the xSemaphoreCreateBinary() + * function. (see http://www.freertos.org/a00111.html). If a binary semaphore + * is created using xSemaphoreCreateBinaryStatic() then the application writer + * must provide the memory. xSemaphoreCreateBinaryStatic() therefore allows a + * binary semaphore to be created without using any dynamic memory allocation. + * + * This type of semaphore can be used for pure synchronisation between tasks or + * between an interrupt and a task. The semaphore need not be given back once + * obtained, so one task/interrupt can continuously 'give' the semaphore while + * another continuously 'takes' the semaphore. For this reason this type of + * semaphore does not use a priority inheritance mechanism. For an alternative + * that does use priority inheritance see xSemaphoreCreateMutex(). + * + * @param pxSemaphoreBuffer Must point to a variable of type StaticSemaphore_t, + * which will then be used to hold the semaphore's data structure, removing the + * need for the memory to be allocated dynamically. + * + * @return If the semaphore is created then a handle to the created semaphore is + * returned. If pxSemaphoreBuffer is NULL then NULL is returned. + * + * Example usage: +
+ SemaphoreHandle_t xSemaphore = NULL;
+ StaticSemaphore_t xSemaphoreBuffer;
+
+ void vATask( void * pvParameters )
+ {
+    // Semaphore cannot be used before a call to xSemaphoreCreateBinary().
+    // The semaphore's data structures will be placed in the xSemaphoreBuffer
+    // variable, the address of which is passed into the function.  The
+    // function's parameter is not NULL, so the function will not attempt any
+    // dynamic memory allocation, and therefore the function will not return
+    // return NULL.
+    xSemaphore = xSemaphoreCreateBinary( &xSemaphoreBuffer );
+
+    // Rest of task code goes here.
+ }
+ 
+ * \defgroup xSemaphoreCreateBinaryStatic xSemaphoreCreateBinaryStatic + * \ingroup Semaphores + */ +#if( configSUPPORT_STATIC_ALLOCATION == 1 ) + #define xSemaphoreCreateBinaryStatic( pxStaticSemaphore ) xQueueGenericCreateStatic( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, NULL, pxStaticSemaphore, queueQUEUE_TYPE_BINARY_SEMAPHORE ) +#endif /* configSUPPORT_STATIC_ALLOCATION */ /** * semphr. h @@ -652,9 +731,18 @@ typedef QueueHandle_t SemaphoreHandle_t; * Macro that implements a mutex semaphore by using the existing queue * mechanism. * - * Mutexes created using this macro can be accessed using the xSemaphoreTake() + * Internally, within the FreeRTOS implementation, mutex semaphores use a block + * of memory, in which the mutex structure is stored. If a mutex is created + * using xSemaphoreCreateMutex() then the required memory is automatically + * dynamically allocated inside the xSemaphoreCreateMutex() function. (see + * http://www.freertos.org/a00111.html). If a mutex is created using + * xSemaphoreCreateMutexStatic() then the application writer must provided the + * memory. xSemaphoreCreateMutexStatic() therefore allows a mutex to be created + * without using any dynamic memory allocation. + * + * Mutexes created using this function can be accessed using the xSemaphoreTake() * and xSemaphoreGive() macros. The xSemaphoreTakeRecursive() and - * xSemaphoreGiveRecursive() macros should not be used. + * xSemaphoreGiveRecursive() macros must not be used. * * This type of semaphore uses a priority inheritance mechanism so a task * 'taking' a semaphore MUST ALWAYS 'give' the semaphore back once the @@ -667,8 +755,9 @@ typedef QueueHandle_t SemaphoreHandle_t; * semaphore and another always 'takes' the semaphore) and from within interrupt * service routines. * - * @return xSemaphore Handle to the created mutex semaphore. Should be of type - * SemaphoreHandle_t. + * @return If the mutex was successfully created then a handle to the created + * semaphore is returned. If there was not enough heap to allocate the mutex + * data structures then NULL is returned. * * Example usage:
@@ -690,19 +779,93 @@ typedef QueueHandle_t SemaphoreHandle_t;
  * \defgroup vSemaphoreCreateMutex vSemaphoreCreateMutex
  * \ingroup Semaphores
  */
-#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )
+#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
+	#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )
+#endif
+
+/**
+ * semphr. h
+ * 
SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer )
+ * + * Creates a new mutex type semaphore instance, and returns a handle by which + * the new mutex can be referenced. + * + * Internally, within the FreeRTOS implementation, mutex semaphores use a block + * of memory, in which the mutex structure is stored. If a mutex is created + * using xSemaphoreCreateMutex() then the required memory is automatically + * dynamically allocated inside the xSemaphoreCreateMutex() function. (see + * http://www.freertos.org/a00111.html). If a mutex is created using + * xSemaphoreCreateMutexStatic() then the application writer must provided the + * memory. xSemaphoreCreateMutexStatic() therefore allows a mutex to be created + * without using any dynamic memory allocation. + * + * Mutexes created using this function can be accessed using the xSemaphoreTake() + * and xSemaphoreGive() macros. The xSemaphoreTakeRecursive() and + * xSemaphoreGiveRecursive() macros must not be used. + * + * This type of semaphore uses a priority inheritance mechanism so a task + * 'taking' a semaphore MUST ALWAYS 'give' the semaphore back once the + * semaphore it is no longer required. + * + * Mutex type semaphores cannot be used from within interrupt service routines. + * + * See xSemaphoreCreateBinary() for an alternative implementation that can be + * used for pure synchronisation (where one task or interrupt always 'gives' the + * semaphore and another always 'takes' the semaphore) and from within interrupt + * service routines. + * + * @param pxMutexBuffer Must point to a variable of type StaticSemaphore_t, + * which will be used to hold the mutex's data structure, removing the need for + * the memory to be allocated dynamically. + * + * @return If the mutex was successfully created then a handle to the created + * mutex is returned. If pxMutexBuffer was NULL then NULL is returned. + * + * Example usage: +
+ SemaphoreHandle_t xSemaphore;
+ StaticSemaphore_t xMutexBuffer;
+
+ void vATask( void * pvParameters )
+ {
+    // A mutex cannot be used before it has been created.  xMutexBuffer is
+    // into xSemaphoreCreateMutexStatic() so no dynamic memory allocation is
+    // attempted.
+    xSemaphore = xSemaphoreCreateMutexStatic( &xMutexBuffer );
+
+    // As no dynamic memory allocation was performed, xSemaphore cannot be NULL,
+    // so there is no need to check it.
+ }
+ 
+ * \defgroup xSemaphoreCreateMutexStatic xSemaphoreCreateMutexStatic + * \ingroup Semaphores + */ + #if( configSUPPORT_STATIC_ALLOCATION == 1 ) + #define xSemaphoreCreateMutexStatic( pxMutexBuffer ) xQueueCreateMutexStatic( queueQUEUE_TYPE_MUTEX, ( pxMutexBuffer ) ) +#endif /* configSUPPORT_STATIC_ALLOCATION */ /** * semphr. h *
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void )
* - * Macro that implements a recursive mutex by using the existing queue - * mechanism. + * Creates a new recursive mutex type semaphore instance, and returns a handle + * by which the new recursive mutex can be referenced. + * + * Internally, within the FreeRTOS implementation, recursive mutexs use a block + * of memory, in which the mutex structure is stored. If a recursive mutex is + * created using xSemaphoreCreateRecursiveMutex() then the required memory is + * automatically dynamically allocated inside the + * xSemaphoreCreateRecursiveMutex() function. (see + * http://www.freertos.org/a00111.html). If a recursive mutex is created using + * xSemaphoreCreateRecursiveMutexStatic() then the application writer must + * provide the memory that will get used by the mutex. + * xSemaphoreCreateRecursiveMutexStatic() therefore allows a recursive mutex to + * be created without using any dynamic memory allocation. * * Mutexes created using this macro can be accessed using the * xSemaphoreTakeRecursive() and xSemaphoreGiveRecursive() macros. The - * xSemaphoreTake() and xSemaphoreGive() macros should not be used. + * xSemaphoreTake() and xSemaphoreGive() macros must not be used. * * A mutex used recursively can be 'taken' repeatedly by the owner. The mutex * doesn't become available again until the owner has called @@ -745,14 +908,104 @@ typedef QueueHandle_t SemaphoreHandle_t; * \defgroup vSemaphoreCreateMutex vSemaphoreCreateMutex * \ingroup Semaphores */ -#define xSemaphoreCreateRecursiveMutex() xQueueCreateMutex( queueQUEUE_TYPE_RECURSIVE_MUTEX ) +#if( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configUSE_RECURSIVE_MUTEXES == 1 ) ) + #define xSemaphoreCreateRecursiveMutex() xQueueCreateMutex( queueQUEUE_TYPE_RECURSIVE_MUTEX ) +#endif + +/** + * semphr. h + *
SemaphoreHandle_t xSemaphoreCreateRecursiveMutexStatic( StaticSemaphore_t *pxMutexBuffer )
+ * + * Creates a new recursive mutex type semaphore instance, and returns a handle + * by which the new recursive mutex can be referenced. + * + * Internally, within the FreeRTOS implementation, recursive mutexs use a block + * of memory, in which the mutex structure is stored. If a recursive mutex is + * created using xSemaphoreCreateRecursiveMutex() then the required memory is + * automatically dynamically allocated inside the + * xSemaphoreCreateRecursiveMutex() function. (see + * http://www.freertos.org/a00111.html). If a recursive mutex is created using + * xSemaphoreCreateRecursiveMutexStatic() then the application writer must + * provide the memory that will get used by the mutex. + * xSemaphoreCreateRecursiveMutexStatic() therefore allows a recursive mutex to + * be created without using any dynamic memory allocation. + * + * Mutexes created using this macro can be accessed using the + * xSemaphoreTakeRecursive() and xSemaphoreGiveRecursive() macros. The + * xSemaphoreTake() and xSemaphoreGive() macros must not be used. + * + * A mutex used recursively can be 'taken' repeatedly by the owner. The mutex + * doesn't become available again until the owner has called + * xSemaphoreGiveRecursive() for each successful 'take' request. For example, + * if a task successfully 'takes' the same mutex 5 times then the mutex will + * not be available to any other task until it has also 'given' the mutex back + * exactly five times. + * + * This type of semaphore uses a priority inheritance mechanism so a task + * 'taking' a semaphore MUST ALWAYS 'give' the semaphore back once the + * semaphore it is no longer required. + * + * Mutex type semaphores cannot be used from within interrupt service routines. + * + * See xSemaphoreCreateBinary() for an alternative implementation that can be + * used for pure synchronisation (where one task or interrupt always 'gives' the + * semaphore and another always 'takes' the semaphore) and from within interrupt + * service routines. + * + * @param pxMutexBuffer Must point to a variable of type StaticSemaphore_t, + * which will then be used to hold the recursive mutex's data structure, + * removing the need for the memory to be allocated dynamically. + * + * @return If the recursive mutex was successfully created then a handle to the + * created recursive mutex is returned. If pxMutexBuffer was NULL then NULL is + * returned. + * + * Example usage: +
+ SemaphoreHandle_t xSemaphore;
+ StaticSemaphore_t xMutexBuffer;
+
+ void vATask( void * pvParameters )
+ {
+    // A recursive semaphore cannot be used before it is created.  Here a
+    // recursive mutex is created using xSemaphoreCreateRecursiveMutexStatic().
+    // The address of xMutexBuffer is passed into the function, and will hold
+    // the mutexes data structures - so no dynamic memory allocation will be
+    // attempted.
+    xSemaphore = xSemaphoreCreateRecursiveMutexStatic( &xMutexBuffer );
+
+    // As no dynamic memory allocation was performed, xSemaphore cannot be NULL,
+    // so there is no need to check it.
+ }
+ 
+ * \defgroup xSemaphoreCreateRecursiveMutexStatic xSemaphoreCreateRecursiveMutexStatic + * \ingroup Semaphores + */ +#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configUSE_RECURSIVE_MUTEXES == 1 ) ) + #define xSemaphoreCreateRecursiveMutexStatic( pxStaticSemaphore ) xQueueCreateMutexStatic( queueQUEUE_TYPE_RECURSIVE_MUTEX, pxStaticSemaphore ) +#endif /* configSUPPORT_STATIC_ALLOCATION */ /** * semphr. h *
SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount, UBaseType_t uxInitialCount )
* - * Macro that creates a counting semaphore by using the existing - * queue mechanism. + * Creates a new counting semaphore instance, and returns a handle by which the + * new counting semaphore can be referenced. + * + * In many usage scenarios it is faster and more memory efficient to use a + * direct to task notification in place of a counting semaphore! + * http://www.freertos.org/RTOS-task-notifications.html + * + * Internally, within the FreeRTOS implementation, counting semaphores use a + * block of memory, in which the counting semaphore structure is stored. If a + * counting semaphore is created using xSemaphoreCreateCounting() then the + * required memory is automatically dynamically allocated inside the + * xSemaphoreCreateCounting() function. (see + * http://www.freertos.org/a00111.html). If a counting semaphore is created + * using xSemaphoreCreateCountingStatic() then the application writer can + * instead optionally provide the memory that will get used by the counting + * semaphore. xSemaphoreCreateCountingStatic() therefore allows a counting + * semaphore to be created without using any dynamic memory allocation. * * Counting semaphores are typically used for two things: * @@ -808,7 +1061,94 @@ typedef QueueHandle_t SemaphoreHandle_t; * \defgroup xSemaphoreCreateCounting xSemaphoreCreateCounting * \ingroup Semaphores */ -#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount ) xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ) ) +#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) + #define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount ) xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ) ) +#endif + +/** + * semphr. h + *
SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount, UBaseType_t uxInitialCount, StaticSemaphore_t *pxSemaphoreBuffer )
+ * + * Creates a new counting semaphore instance, and returns a handle by which the + * new counting semaphore can be referenced. + * + * In many usage scenarios it is faster and more memory efficient to use a + * direct to task notification in place of a counting semaphore! + * http://www.freertos.org/RTOS-task-notifications.html + * + * Internally, within the FreeRTOS implementation, counting semaphores use a + * block of memory, in which the counting semaphore structure is stored. If a + * counting semaphore is created using xSemaphoreCreateCounting() then the + * required memory is automatically dynamically allocated inside the + * xSemaphoreCreateCounting() function. (see + * http://www.freertos.org/a00111.html). If a counting semaphore is created + * using xSemaphoreCreateCountingStatic() then the application writer must + * provide the memory. xSemaphoreCreateCountingStatic() therefore allows a + * counting semaphore to be created without using any dynamic memory allocation. + * + * Counting semaphores are typically used for two things: + * + * 1) Counting events. + * + * In this usage scenario an event handler will 'give' a semaphore each time + * an event occurs (incrementing the semaphore count value), and a handler + * task will 'take' a semaphore each time it processes an event + * (decrementing the semaphore count value). The count value is therefore + * the difference between the number of events that have occurred and the + * number that have been processed. In this case it is desirable for the + * initial count value to be zero. + * + * 2) Resource management. + * + * In this usage scenario the count value indicates the number of resources + * available. To obtain control of a resource a task must first obtain a + * semaphore - decrementing the semaphore count value. When the count value + * reaches zero there are no free resources. When a task finishes with the + * resource it 'gives' the semaphore back - incrementing the semaphore count + * value. In this case it is desirable for the initial count value to be + * equal to the maximum count value, indicating that all resources are free. + * + * @param uxMaxCount The maximum count value that can be reached. When the + * semaphore reaches this value it can no longer be 'given'. + * + * @param uxInitialCount The count value assigned to the semaphore when it is + * created. + * + * @param pxSemaphoreBuffer Must point to a variable of type StaticSemaphore_t, + * which will then be used to hold the semaphore's data structure, removing the + * need for the memory to be allocated dynamically. + * + * @return If the counting semaphore was successfully created then a handle to + * the created counting semaphore is returned. If pxSemaphoreBuffer was NULL + * then NULL is returned. + * + * Example usage: +
+ SemaphoreHandle_t xSemaphore;
+ StaticSemaphore_t xSemaphoreBuffer;
+
+ void vATask( void * pvParameters )
+ {
+ SemaphoreHandle_t xSemaphore = NULL;
+
+    // Counting semaphore cannot be used before they have been created.  Create
+    // a counting semaphore using xSemaphoreCreateCountingStatic().  The max
+    // value to which the semaphore can count is 10, and the initial value
+    // assigned to the count will be 0.  The address of xSemaphoreBuffer is
+    // passed in and will be used to hold the semaphore structure, so no dynamic
+    // memory allocation will be used.
+    xSemaphore = xSemaphoreCreateCounting( 10, 0, &xSemaphoreBuffer );
+
+    // No memory allocation was attempted so xSemaphore cannot be NULL, so there
+    // is no need to check its value.
+ }
+ 
+ * \defgroup xSemaphoreCreateCountingStatic xSemaphoreCreateCountingStatic + * \ingroup Semaphores + */ +#if( configSUPPORT_STATIC_ALLOCATION == 1 ) + #define xSemaphoreCreateCountingStatic( uxMaxCount, uxInitialCount, pxSemaphoreBuffer ) xQueueCreateCountingSemaphoreStatic( ( uxMaxCount ), ( uxInitialCount ), ( pxSemaphoreBuffer ) ) +#endif /* configSUPPORT_STATIC_ALLOCATION */ /** * semphr. h diff --git a/components/freertos/include/freertos/task.h b/components/freertos/include/freertos/task.h index ddf7a7589..6a781dc8c 100644 --- a/components/freertos/include/freertos/task.h +++ b/components/freertos/include/freertos/task.h @@ -177,6 +177,7 @@ typedef struct xTASK_STATUS UBaseType_t uxCurrentPriority; /* The priority at which the task was running (may be inherited) when the structure was populated. */ UBaseType_t uxBasePriority; /* The priority to which the task will return if the task's current priority has been inherited to avoid unbounded priority inversion when obtaining a mutex. Only valid if configUSE_MUTEXES is defined as 1 in FreeRTOSConfig.h. */ uint32_t ulRunTimeCounter; /* The total run time allocated to the task so far, as defined by the run time stats clock. See http://www.freertos.org/rtos-run-time-stats.html. Only valid when configGENERATE_RUN_TIME_STATS is defined as 1 in FreeRTOSConfig.h. */ + StackType_t *pxStackBase; /* Points to the lowest address of the task's stack area. */ uint16_t usStackHighWaterMark; /* The minimum amount of stack space that has remained for the task since the task was created. The closer this value is to zero the closer the task has come to overflowing its stack. */ } TaskStatus_t; @@ -281,8 +282,19 @@ is used in assert() statements. */ );
* * Create a new task and add it to the list of tasks that are ready to run. - * On multicore environments, this will give no specific affinity to the task. - * Use xTaskCreatePinnedToCore to give affinity. + * + * Internally, within the FreeRTOS implementation, tasks use two blocks of + * memory. The first block is used to hold the task's data structures. The + * second block is used by the task as its stack. If a task is created using + * xTaskCreate() then both blocks of memory are automatically dynamically + * allocated inside the xTaskCreate() function. (see + * http://www.freertos.org/a00111.html). If a task is created using + * xTaskCreateStatic() then the application writer must provide the required + * memory. xTaskCreateStatic() therefore allows a task to be created without + * using any dynamic memory allocation. + * + * See xTaskCreateStatic() for a version that does not use any dynamic memory + * allocation. * * xTaskCreate() can only be used to create a task that has unrestricted * access to the entire microcontroller memory map. Systems that include MPU @@ -350,8 +362,139 @@ is used in assert() statements. */ * \defgroup xTaskCreate xTaskCreate * \ingroup Tasks */ -#define xTaskCreate( pvTaskCode, pcName, usStackDepth, pvParameters, uxPriority, pxCreatedTask ) xTaskGenericCreate( ( pvTaskCode ), ( pcName ), ( usStackDepth ), ( pvParameters ), ( uxPriority ), ( pxCreatedTask ), ( NULL ), ( NULL ), tskNO_AFFINITY ) -#define xTaskCreatePinnedToCore( pvTaskCode, pcName, usStackDepth, pvParameters, uxPriority, pxCreatedTask, xCoreID ) xTaskGenericCreate( ( pvTaskCode ), ( pcName ), ( usStackDepth ), ( pvParameters ), ( uxPriority ), ( pxCreatedTask ), ( NULL ), ( NULL ), xCoreID ) +#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) + BaseType_t xTaskCreatePinnedToCore( TaskFunction_t pxTaskCode, + const char * const pcName, + const uint16_t usStackDepth, + void * const pvParameters, + UBaseType_t uxPriority, + TaskHandle_t * const pxCreatedTask, + const BaseType_t xCoreID); + +#define xTaskCreate( pvTaskCode, pcName, usStackDepth, pvParameters, uxPriority, pxCreatedTask ) xTaskCreatePinnedToCore( ( pvTaskCode ), ( pcName ), ( usStackDepth ), ( pvParameters ), ( uxPriority ), ( pxCreatedTask ), tskNO_AFFINITY ) +#endif + +/** + * task. h + *
+ TaskHandle_t xTaskCreateStatic( TaskFunction_t pvTaskCode,
+								 const char * const pcName,
+								 uint32_t ulStackDepth,
+								 void *pvParameters,
+								 UBaseType_t uxPriority,
+								 StackType_t *pxStackBuffer,
+								 StaticTask_t *pxTaskBuffer,
+                                 const BaseType_t xCoreID );
+ + * + * Create a new task and add it to the list of tasks that are ready to run. + * + * Internally, within the FreeRTOS implementation, tasks use two blocks of + * memory. The first block is used to hold the task's data structures. The + * second block is used by the task as its stack. If a task is created using + * xTaskCreate() then both blocks of memory are automatically dynamically + * allocated inside the xTaskCreate() function. (see + * http://www.freertos.org/a00111.html). If a task is created using + * xTaskCreateStatic() then the application writer must provide the required + * memory. xTaskCreateStatic() therefore allows a task to be created without + * using any dynamic memory allocation. + * + * @param pvTaskCode Pointer to the task entry function. Tasks + * must be implemented to never return (i.e. continuous loop). + * + * @param pcName A descriptive name for the task. This is mainly used to + * facilitate debugging. The maximum length of the string is defined by + * configMAX_TASK_NAME_LEN in FreeRTOSConfig.h. + * + * @param ulStackDepth The size of the task stack specified as the number of + * variables the stack can hold - not the number of bytes. For example, if + * the stack is 32-bits wide and ulStackDepth is defined as 100 then 400 bytes + * will be allocated for stack storage. + * + * @param pvParameters Pointer that will be used as the parameter for the task + * being created. + * + * @param uxPriority The priority at which the task will run. + * + * @param pxStackBuffer Must point to a StackType_t array that has at least + * ulStackDepth indexes - the array will then be used as the task's stack, + * removing the need for the stack to be allocated dynamically. + * + * @param pxTaskBuffer Must point to a variable of type StaticTask_t, which will + * then be used to hold the task's data structures, removing the need for the + * memory to be allocated dynamically. + * + * @return If neither pxStackBuffer or pxTaskBuffer are NULL, then the task will + * be created and pdPASS is returned. If either pxStackBuffer or pxTaskBuffer + * are NULL then the task will not be created and + * errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY is returned. + * + * Example usage: +
+
+    // Dimensions the buffer that the task being created will use as its stack.
+    // NOTE:  This is the number of words the stack will hold, not the number of
+    // bytes.  For example, if each stack item is 32-bits, and this is set to 100,
+    // then 400 bytes (100 * 32-bits) will be allocated.
+    #define STACK_SIZE 200
+
+    // Structure that will hold the TCB of the task being created.
+    StaticTask_t xTaskBuffer;
+
+    // Buffer that the task being created will use as its stack.  Note this is
+    // an array of StackType_t variables.  The size of StackType_t is dependent on
+    // the RTOS port.
+    StackType_t xStack[ STACK_SIZE ];
+
+    // Function that implements the task being created.
+    void vTaskCode( void * pvParameters )
+    {
+        // The parameter value is expected to be 1 as 1 is passed in the
+        // pvParameters value in the call to xTaskCreateStatic().
+        configASSERT( ( uint32_t ) pvParameters == 1UL );
+
+        for( ;; )
+        {
+            // Task code goes here.
+        }
+    }
+
+    // Function that creates a task.
+    void vOtherFunction( void )
+    {
+        TaskHandle_t xHandle = NULL;
+
+        // Create the task without using any dynamic memory allocation.
+        xHandle = xTaskCreateStatic(
+                      vTaskCode,       // Function that implements the task.
+                      "NAME",          // Text name for the task.
+                      STACK_SIZE,      // Stack size in words, not bytes.
+                      ( void * ) 1,    // Parameter passed into the task.
+                      tskIDLE_PRIORITY,// Priority at which the task is created.
+                      xStack,          // Array to use as the task's stack.
+                      &xTaskBuffer );  // Variable to hold the task's data structure.
+
+        // puxStackBuffer and pxTaskBuffer were not NULL, so the task will have
+        // been created, and xHandle will be the task's handle.  Use the handle
+        // to suspend the task.
+        vTaskSuspend( xHandle );
+    }
+   
+ * \defgroup xTaskCreateStatic xTaskCreateStatic + * \ingroup Tasks + */ +#if( configSUPPORT_STATIC_ALLOCATION == 1 ) + TaskHandle_t xTaskCreateStaticPinnedToCore( TaskFunction_t pxTaskCode, + const char * const pcName, + const uint32_t ulStackDepth, + void * const pvParameters, + UBaseType_t uxPriority, + StackType_t * const puxStackBuffer, + StaticTask_t * const pxTaskBuffer, + const BaseType_t xCoreID ); + +#define xTaskCreateStatic( pvTaskCode, pcName, usStackDepth, pvParameters, uxPriority, pxStackBuffer, pxTaskBuffer ) xTaskCreateStaticPinnedToCore( ( pvTaskCode ), ( pcName ), ( usStackDepth ), ( pvParameters ), ( uxPriority ), ( pxStackBuffer ), ( pxTaskBuffer ), tskNO_AFFINITY ) +#endif /* configSUPPORT_STATIC_ALLOCATION */ /** * task. h @@ -420,7 +563,9 @@ TaskHandle_t xHandle; * \defgroup xTaskCreateRestricted xTaskCreateRestricted * \ingroup Tasks */ -#define xTaskCreateRestricted( x, pxCreatedTask ) xTaskGenericCreate( ((x)->pvTaskCode), ((x)->pcName), ((x)->usStackDepth), ((x)->pvParameters), ((x)->uxPriority), (pxCreatedTask), ((x)->puxStackBuffer), ((x)->xRegions) ) +#if( portUSING_MPU_WRAPPERS == 1 ) + BaseType_t xTaskCreateRestricted( const TaskParameters_t * const pxTaskDefinition, TaskHandle_t *pxCreatedTask ) PRIVILEGED_FUNCTION; +#endif /** * task. h @@ -1968,12 +2113,6 @@ void vTaskPriorityInherit( TaskHandle_t const pxMutexHolder ) PRIVILEGED_FUNCTIO */ BaseType_t xTaskPriorityDisinherit( TaskHandle_t const pxMutexHolder ) PRIVILEGED_FUNCTION; -/* - * Generic version of the task creation function which is in turn called by the - * xTaskCreate() and xTaskCreateRestricted() macros. - */ -BaseType_t xTaskGenericCreate( TaskFunction_t pxTaskCode, const char * const pcName, const uint16_t usStackDepth, void * const pvParameters, UBaseType_t uxPriority, TaskHandle_t * const pxCreatedTask, StackType_t * const puxStackBuffer, const MemoryRegion_t * const xRegions, const BaseType_t xCoreID) PRIVILEGED_FUNCTION; /*lint !e971 Unqualified char types are allowed for strings and single characters only. */ - /* * Get the uxTCBNumber assigned to the task referenced by the xTask parameter. */ diff --git a/components/freertos/queue.c b/components/freertos/queue.c index 248ae7c00..445a9e2e9 100644 --- a/components/freertos/queue.c +++ b/components/freertos/queue.c @@ -166,15 +166,19 @@ typedef struct QueueDefinition volatile BaseType_t xRxLock; /*< Stores the number of items received from the queue (removed from the queue) while the queue was locked. Set to queueUNLOCKED when the queue is not locked. */ volatile BaseType_t xTxLock; /*< Stores the number of items transmitted to the queue (added to the queue) while the queue was locked. Set to queueUNLOCKED when the queue is not locked. */ - #if ( configUSE_TRACE_FACILITY == 1 ) - UBaseType_t uxQueueNumber; - uint8_t ucQueueType; + #if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) ) + uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the memory used by the queue was statically allocated to ensure no attempt is made to free the memory. */ #endif #if ( configUSE_QUEUE_SETS == 1 ) struct QueueDefinition *pxQueueSetContainer; #endif + #if ( configUSE_TRACE_FACILITY == 1 ) + UBaseType_t uxQueueNumber; + uint8_t ucQueueType; + #endif + portMUX_TYPE mux; } xQUEUE; @@ -255,6 +259,21 @@ static void prvCopyDataFromQueue( Queue_t * const pxQueue, void * const pvBuffer static BaseType_t prvNotifyQueueSetContainer( const Queue_t * const pxQueue, const BaseType_t xCopyPosition ) PRIVILEGED_FUNCTION; #endif +/* + * Called after a Queue_t structure has been allocated either statically or + * dynamically to fill in the structure's members. + */ +static void prvInitialiseNewQueue( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, uint8_t *pucQueueStorage, const uint8_t ucQueueType, Queue_t *pxNewQueue ) PRIVILEGED_FUNCTION; + +/* + * Mutexes are a special type of queue. When a mutex is created, first the + * queue is created, then prvInitialiseMutex() is called to configure the queue + * as a mutex. + */ +#if( configUSE_MUTEXES == 1 ) + static void prvInitialiseMutex( Queue_t *pxNewQueue ) PRIVILEGED_FUNCTION; +#endif + /*-----------------------------------------------------------*/ /* @@ -333,134 +352,165 @@ Queue_t * const pxQueue = ( Queue_t * ) xQueue; } /*-----------------------------------------------------------*/ -QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType ) -{ -Queue_t *pxNewQueue; -size_t xQueueSizeInBytes; -QueueHandle_t xReturn = NULL; -int8_t *pcAllocatedBuffer; +#if( configSUPPORT_STATIC_ALLOCATION == 1 ) + QueueHandle_t xQueueGenericCreateStatic( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, uint8_t *pucQueueStorage, StaticQueue_t *pxStaticQueue, const uint8_t ucQueueType ) + { + Queue_t *pxNewQueue; + + configASSERT( uxQueueLength > ( UBaseType_t ) 0 ); + + /* The StaticQueue_t structure and the queue storage area must be + supplied. */ + configASSERT( pxStaticQueue != NULL ); + + /* A queue storage area should be provided if the item size is not 0, and + should not be provided if the item size is 0. */ + configASSERT( !( ( pucQueueStorage != NULL ) && ( uxItemSize == 0 ) ) ); + configASSERT( !( ( pucQueueStorage == NULL ) && ( uxItemSize != 0 ) ) ); + + #if( configASSERT_DEFINED == 1 ) + { + /* Sanity check that the size of the structure used to declare a + variable of type StaticQueue_t or StaticSemaphore_t equals the size of + the real queue and semaphore structures. */ + volatile size_t xSize = sizeof( StaticQueue_t ); + configASSERT( xSize == sizeof( Queue_t ) ); + } + #endif /* configASSERT_DEFINED */ + + /* The address of a statically allocated queue was passed in, use it. + The address of a statically allocated storage area was also passed in + but is already set. */ + pxNewQueue = ( Queue_t * ) pxStaticQueue; /*lint !e740 Unusual cast is ok as the structures are designed to have the same alignment, and the size is checked by an assert. */ + + if( pxNewQueue != NULL ) + { + #if( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) + { + /* Queues can be allocated wither statically or dynamically, so + note this queue was allocated statically in case the queue is + later deleted. */ + pxNewQueue->ucStaticallyAllocated = pdTRUE; + } + #endif /* configSUPPORT_DYNAMIC_ALLOCATION */ + + prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue ); + } + + return pxNewQueue; + } + +#endif /* configSUPPORT_STATIC_ALLOCATION */ +/*-----------------------------------------------------------*/ + +#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) + + QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType ) + { + Queue_t *pxNewQueue; + size_t xQueueSizeInBytes; + uint8_t *pucQueueStorage; + + configASSERT( uxQueueLength > ( UBaseType_t ) 0 ); + + if( uxItemSize == ( UBaseType_t ) 0 ) + { + /* There is not going to be a queue storage area. */ + xQueueSizeInBytes = ( size_t ) 0; + } + else + { + /* Allocate enough space to hold the maximum number of items that + can be in the queue at any time. */ + xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */ + } + + pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes ); + + if( pxNewQueue != NULL ) + { + /* Jump past the queue structure to find the location of the queue + storage area. */ + pucQueueStorage = ( ( uint8_t * ) pxNewQueue ) + sizeof( Queue_t ); + + #if( configSUPPORT_STATIC_ALLOCATION == 1 ) + { + /* Queues can be created either statically or dynamically, so + note this task was created dynamically in case it is later + deleted. */ + pxNewQueue->ucStaticallyAllocated = pdFALSE; + } + #endif /* configSUPPORT_STATIC_ALLOCATION */ + + prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue ); + } + + return pxNewQueue; + } + +#endif /* configSUPPORT_STATIC_ALLOCATION */ +/*-----------------------------------------------------------*/ + +static void prvInitialiseNewQueue( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, uint8_t *pucQueueStorage, const uint8_t ucQueueType, Queue_t *pxNewQueue ) +{ /* Remove compiler warnings about unused parameters should configUSE_TRACE_FACILITY not be set to 1. */ ( void ) ucQueueType; - configASSERT( uxQueueLength > ( UBaseType_t ) 0 ); - if( uxItemSize == ( UBaseType_t ) 0 ) { - /* There is not going to be a queue storage area. */ - xQueueSizeInBytes = ( size_t ) 0; + /* No RAM was allocated for the queue storage area, but PC head cannot + be set to NULL because NULL is used as a key to say the queue is used as + a mutex. Therefore just set pcHead to point to the queue as a benign + value that is known to be within the memory map. */ + pxNewQueue->pcHead = ( int8_t * ) pxNewQueue; } else { - /* The queue is one byte longer than asked for to make wrap checking - easier/faster. */ - xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ) + ( size_t ) 1; /*lint !e961 MISRA exception as the casts are only redundant for some ports. */ + /* Set the head to the start of the queue storage area. */ + pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage; } - /* Allocate the new queue structure and storage area. */ - pcAllocatedBuffer = ( int8_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes ); + /* Initialise the queue members as described where the queue type is + defined. */ + pxNewQueue->uxLength = uxQueueLength; + pxNewQueue->uxItemSize = uxItemSize; + ( void ) xQueueGenericReset( pxNewQueue, pdTRUE ); - if( pcAllocatedBuffer != NULL ) + #if ( configUSE_TRACE_FACILITY == 1 ) { - pxNewQueue = ( Queue_t * ) pcAllocatedBuffer; /*lint !e826 MISRA The buffer cannot be to small because it was dimensioned by sizeof( Queue_t ) + xQueueSizeInBytes. */ - - if( uxItemSize == ( UBaseType_t ) 0 ) - { - /* No RAM was allocated for the queue storage area, but PC head - cannot be set to NULL because NULL is used as a key to say the queue - is used as a mutex. Therefore just set pcHead to point to the queue - as a benign value that is known to be within the memory map. */ - pxNewQueue->pcHead = ( int8_t * ) pxNewQueue; - } - else - { - /* Jump past the queue structure to find the location of the queue - storage area - adding the padding bytes to get a better alignment. */ - pxNewQueue->pcHead = pcAllocatedBuffer + sizeof( Queue_t ); - } - - /* Initialise the queue members as described above where the queue type - is defined. */ - pxNewQueue->uxLength = uxQueueLength; - pxNewQueue->uxItemSize = uxItemSize; - ( void ) xQueueGenericReset( pxNewQueue, pdTRUE ); - - #if ( configUSE_TRACE_FACILITY == 1 ) - { - pxNewQueue->ucQueueType = ucQueueType; - } - #endif /* configUSE_TRACE_FACILITY */ - - #if( configUSE_QUEUE_SETS == 1 ) - { - pxNewQueue->pxQueueSetContainer = NULL; - } - #endif /* configUSE_QUEUE_SETS */ - - traceQUEUE_CREATE( pxNewQueue ); - xReturn = pxNewQueue; + pxNewQueue->ucQueueType = ucQueueType; } - else + #endif /* configUSE_TRACE_FACILITY */ + + #if( configUSE_QUEUE_SETS == 1 ) { - mtCOVERAGE_TEST_MARKER(); + pxNewQueue->pxQueueSetContainer = NULL; } + #endif /* configUSE_QUEUE_SETS */ - configASSERT( xReturn ); - - return xReturn; + traceQUEUE_CREATE( pxNewQueue ); } /*-----------------------------------------------------------*/ -#if ( configUSE_MUTEXES == 1 ) +#if( configUSE_MUTEXES == 1 ) - QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType ) + static void prvInitialiseMutex( Queue_t *pxNewQueue ) { - Queue_t *pxNewQueue; - - /* Prevent compiler warnings about unused parameters if - configUSE_TRACE_FACILITY does not equal 1. */ - ( void ) ucQueueType; - - /* Allocate the new queue structure. */ - pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) ); if( pxNewQueue != NULL ) { - /* Information required for priority inheritance. */ + /* The queue create function will set all the queue structure members + correctly for a generic queue, but this function is creating a + mutex. Overwrite those members that need to be set differently - + in particular the information required for priority inheritance. */ pxNewQueue->pxMutexHolder = NULL; pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX; - /* Queues used as a mutex no data is actually copied into or out - of the queue. */ - pxNewQueue->pcWriteTo = NULL; - pxNewQueue->u.pcReadFrom = NULL; + /* In case this is a recursive mutex. */ + pxNewQueue->u.uxRecursiveCallCount = 0; - /* Each mutex has a length of 1 (like a binary semaphore) and - an item size of 0 as nothing is actually copied into or out - of the mutex. */ - pxNewQueue->uxMessagesWaiting = ( UBaseType_t ) 0U; - pxNewQueue->uxLength = ( UBaseType_t ) 1U; - pxNewQueue->uxItemSize = ( UBaseType_t ) 0U; - pxNewQueue->xRxLock = queueUNLOCKED; - pxNewQueue->xTxLock = queueUNLOCKED; - - #if ( configUSE_TRACE_FACILITY == 1 ) - { - pxNewQueue->ucQueueType = ucQueueType; - } - #endif - - #if ( configUSE_QUEUE_SETS == 1 ) - { - pxNewQueue->pxQueueSetContainer = NULL; - } - #endif - - /* Ensure the event queues start with the correct state. */ - vListInitialise( &( pxNewQueue->xTasksWaitingToSend ) ); - vListInitialise( &( pxNewQueue->xTasksWaitingToReceive ) ); - - vPortCPUInitializeMutex(&pxNewQueue->mux); + vPortCPUInitializeMutex(&pxNewQueue->mux); traceCREATE_MUTEX( pxNewQueue ); @@ -471,8 +521,41 @@ int8_t *pcAllocatedBuffer; { traceCREATE_MUTEX_FAILED(); } + } + +#endif /* configUSE_MUTEXES */ +/*-----------------------------------------------------------*/ + +#if( ( configUSE_MUTEXES == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) ) + + QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType ) + { + Queue_t *pxNewQueue; + const UBaseType_t uxMutexLength = ( UBaseType_t ) 1, uxMutexSize = ( UBaseType_t ) 0; + + pxNewQueue = ( Queue_t * ) xQueueGenericCreate( uxMutexLength, uxMutexSize, ucQueueType ); + prvInitialiseMutex( pxNewQueue ); + + return pxNewQueue; + } + +#endif /* configUSE_MUTEXES */ +/*-----------------------------------------------------------*/ + +#if( ( configUSE_MUTEXES == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 1 ) ) + + QueueHandle_t xQueueCreateMutexStatic( const uint8_t ucQueueType, StaticQueue_t *pxStaticQueue ) + { + Queue_t *pxNewQueue; + const UBaseType_t uxMutexLength = ( UBaseType_t ) 1, uxMutexSize = ( UBaseType_t ) 0; + + /* Prevent compiler warnings about unused parameters if + configUSE_TRACE_FACILITY does not equal 1. */ + ( void ) ucQueueType; + + pxNewQueue = ( Queue_t * ) xQueueGenericCreateStatic( uxMutexLength, uxMutexSize, NULL, pxStaticQueue, ucQueueType ); + prvInitialiseMutex( pxNewQueue ); - configASSERT( pxNewQueue ); return pxNewQueue; } @@ -607,7 +690,35 @@ int8_t *pcAllocatedBuffer; #endif /* configUSE_RECURSIVE_MUTEXES */ /*-----------------------------------------------------------*/ -#if ( configUSE_COUNTING_SEMAPHORES == 1 ) +#if( ( configUSE_COUNTING_SEMAPHORES == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 1 ) ) + + QueueHandle_t xQueueCreateCountingSemaphoreStatic( const UBaseType_t uxMaxCount, const UBaseType_t uxInitialCount, StaticQueue_t *pxStaticQueue ) + { + QueueHandle_t xHandle; + + configASSERT( uxMaxCount != 0 ); + configASSERT( uxInitialCount <= uxMaxCount ); + + xHandle = xQueueGenericCreateStatic( uxMaxCount, queueSEMAPHORE_QUEUE_ITEM_LENGTH, NULL, pxStaticQueue, queueQUEUE_TYPE_COUNTING_SEMAPHORE ); + + if( xHandle != NULL ) + { + ( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount; + + traceCREATE_COUNTING_SEMAPHORE(); + } + else + { + traceCREATE_COUNTING_SEMAPHORE_FAILED(); + } + + return xHandle; + } + +#endif /* ( ( configUSE_COUNTING_SEMAPHORES == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) ) */ +/*-----------------------------------------------------------*/ + +#if( ( configUSE_COUNTING_SEMAPHORES == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) ) QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_t uxMaxCount, const UBaseType_t uxInitialCount ) { @@ -633,7 +744,7 @@ int8_t *pcAllocatedBuffer; return xHandle; } -#endif /* configUSE_COUNTING_SEMAPHORES */ +#endif /* ( ( configUSE_COUNTING_SEMAPHORES == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) ) */ /*-----------------------------------------------------------*/ BaseType_t xQueueGenericSend( QueueHandle_t xQueue, const void * const pvItemToQueue, TickType_t xTicksToWait, const BaseType_t xCopyPosition ) @@ -1777,7 +1888,33 @@ Queue_t * const pxQueue = ( Queue_t * ) xQueue; vQueueUnregisterQueue( pxQueue ); } #endif - vPortFree( pxQueue ); + + #if( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 0 ) ) + { + /* The queue can only have been allocated dynamically - free it + again. */ + vPortFree( pxQueue ); + } + #elif( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 1 ) ) + { + /* The queue could have been allocated statically or dynamically, so + check before attempting to free the memory. */ + if( pxQueue->ucStaticallyAllocated == ( uint8_t ) pdFALSE ) + { + vPortFree( pxQueue ); + } + else + { + mtCOVERAGE_TEST_MARKER(); + } + } + #else + { + /* The queue must have been statically allocated, so is not going to be + deleted. Avoid compiler warnings about the unused parameter. */ + ( void ) pxQueue; + } + #endif /* configSUPPORT_DYNAMIC_ALLOCATION */ } /*-----------------------------------------------------------*/ @@ -2477,7 +2614,7 @@ Queue_t * const pxQueue = ( Queue_t * ) xQueue; #endif /* configUSE_TIMERS */ /*-----------------------------------------------------------*/ -#if ( configUSE_QUEUE_SETS == 1 ) +#if( ( configUSE_QUEUE_SETS == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) ) QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength ) { diff --git a/components/freertos/tasks.c b/components/freertos/tasks.c index ff3a0530d..034ffc08e 100644 --- a/components/freertos/tasks.c +++ b/components/freertos/tasks.c @@ -85,7 +85,6 @@ task.h is included from an application file. */ #include "StackMacros.h" #include "portmacro.h" #include "semphr.h" -#include "sys/reent.h" /* Lint e961 and e750 are suppressed as a MISRA exception justified because the MPU ports require MPU_WRAPPERS_INCLUDED_FROM_API_FILE to be defined for the @@ -129,6 +128,8 @@ functions but without including stdio.h here. */ } while(0) #endif + + /* Value that can be assigned to the eNotifyState member of the TCB. */ typedef enum { @@ -137,6 +138,26 @@ typedef enum eNotified } eNotifyValue; +/* Sometimes the FreeRTOSConfig.h settings only allow a task to be created using +dynamically allocated RAM, in which case when any task is deleted it is known +that both the task's stack and TCB need to be freed. Sometimes the +FreeRTOSConfig.h settings only allow a task to be created using statically +allocated RAM, in which case when any task is deleted it is known that neither +the task's stack or TCB should be freed. Sometimes the FreeRTOSConfig.h +settings allow a task to be created using either statically or dynamically +allocated RAM, in which case a member of the TCB is used to record whether the +stack and/or TCB were allocated statically or dynamically, so when a task is +deleted the RAM that was allocated dynamically is freed again and no attempt is +made to free the RAM that was allocated statically. +tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE is only true if it is possible for a +task to be created using either statically or dynamically allocated RAM. Note +that if portUSING_MPU_WRAPPERS is 1 then a protected task can be created with +a statically allocated stack and a dynamically allocated TCB. */ +#define tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE ( ( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) ) || ( portUSING_MPU_WRAPPERS == 1 ) ) +#define tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB ( ( uint8_t ) 0 ) +#define tskSTATICALLY_ALLOCATED_STACK_ONLY ( ( uint8_t ) 1 ) +#define tskSTATICALLY_ALLOCATED_STACK_AND_TCB ( ( uint8_t ) 2 ) + /* * Task control block. A task control block (TCB) is allocated for each task, * and stores task state information, including a pointer to the task's context @@ -148,7 +169,6 @@ typedef struct tskTaskControlBlock #if ( portUSING_MPU_WRAPPERS == 1 ) xMPU_SETTINGS xMPUSettings; /*< The MPU settings are defined as part of the port layer. THIS MUST BE THE SECOND MEMBER OF THE TCB STRUCT. */ - BaseType_t xUsingStaticallyAllocatedStack; /* Set to pdTRUE if the stack is a statically allocated array, and pdFALSE if the stack is dynamically allocated. */ #endif ListItem_t xGenericListItem; /*< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */ @@ -208,6 +228,12 @@ typedef struct tskTaskControlBlock volatile eNotifyValue eNotifyState; #endif + /* See the comments above the definition of + tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE. */ + #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) + uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the task is a statically allocated to ensure no attempt is made to free the memory. */ + #endif + } tskTCB; /* The old tskTCB name is maintained above then typedefed to the new TCB_t name @@ -455,12 +481,6 @@ to its original value when it is released. */ /* File private functions. --------------------------------*/ -/* - * Utility to ready a TCB for a given task. Mainly just copies the parameters - * into the TCB structure. - */ -static void prvInitialiseTCBVariables( TCB_t * const pxTCB, const char * const pcName, UBaseType_t uxPriority, const MemoryRegion_t * const xRegions, const uint16_t usStackDepth, const BaseType_t xCoreID ) PRIVILEGED_FUNCTION; /*lint !e971 Unqualified char types are allowed for strings and single characters only. */ - /** * Utility task that simply returns pdTRUE if the task referenced by xTask is * currently in the Suspended state, or pdFALSE if the task referenced by xTask @@ -515,12 +535,6 @@ static void prvCheckTasksWaitingTermination( void ) PRIVILEGED_FUNCTION; */ static void prvAddCurrentTaskToDelayedList( const portBASE_TYPE xCoreID, const TickType_t xTimeToWake ) PRIVILEGED_FUNCTION; -/* - * Allocates memory from the heap for a TCB and associated stack. Checks the - * allocation was successful. - */ -static TCB_t *prvAllocateTCBAndStack( const uint16_t usStackDepth, StackType_t * const puxStackBuffer ) PRIVILEGED_FUNCTION; - /* * Fills an TaskStatus_t structure with information on each task that is * referenced from the pxList list (which may be a ready list, a delayed list, @@ -577,6 +591,26 @@ static void prvResetNextTaskUnblockTime( void ); #endif +/* + * Called after a Task_t structure has been allocated either statically or + * dynamically to fill in the structure's members. + */ +static void prvInitialiseNewTask( TaskFunction_t pxTaskCode, + const char * const pcName, + const uint32_t ulStackDepth, + void * const pvParameters, + UBaseType_t uxPriority, + TaskHandle_t * const pxCreatedTask, + TCB_t *pxNewTCB, + const MemoryRegion_t * const xRegions ) PRIVILEGED_FUNCTION; /*lint !e971 Unqualified char types are allowed for strings and single characters only. */ + +/* + * Called after a new task has been created and initialised to place the task + * under the control of the scheduler. + */ +static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB, TaskFunction_t pxTaskCode, const BaseType_t xCoreID ) PRIVILEGED_FUNCTION; + + /*-----------------------------------------------------------*/ @@ -589,114 +623,403 @@ static void vTaskInitializeLocalMuxes( void ) /*-----------------------------------------------------------*/ -BaseType_t xTaskGenericCreate( TaskFunction_t pxTaskCode, const char * const pcName, const uint16_t usStackDepth, void * const pvParameters, UBaseType_t uxPriority, TaskHandle_t * const pxCreatedTask, StackType_t * const puxStackBuffer, const MemoryRegion_t * const xRegions, const BaseType_t xCoreID) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */ -{ -BaseType_t xReturn; -TCB_t * pxNewTCB; -StackType_t *pxTopOfStack; -BaseType_t i; - - /* Initialize mutexes, if they're not already initialized. */ - if (xMutexesInitialised == pdFALSE) vTaskInitializeLocalMuxes(); - - configASSERT( pxTaskCode ); - configASSERT( ( ( uxPriority & ( ~portPRIVILEGE_BIT ) ) < configMAX_PRIORITIES ) ); - configASSERT( (xCoreID>=0 && xCoreIDxUsingStaticallyAllocatedStack = pdTRUE; - } - else - { - /* The stack was allocated dynamically. Note this so it can be - deleted again if the task is deleted. */ - pxNewTCB->xUsingStaticallyAllocatedStack = pdFALSE; - } - #endif /* portUSING_MPU_WRAPPERS == 1 */ + configASSERT( puxStackBuffer != NULL ); + configASSERT( pxTaskBuffer != NULL ); + configASSERT( (xCoreID>=0 && xCoreIDpxStack + ( usStackDepth - ( uint16_t ) 1 ); - pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ( portPOINTER_SIZE_TYPE ) ~portBYTE_ALIGNMENT_MASK ) ); /*lint !e923 MISRA exception. Avoiding casts between pointers and integers is not practical. Size differences accounted for using portPOINTER_SIZE_TYPE type. */ + /* The memory used for the task's TCB and stack are passed into this + function - use them. */ + pxNewTCB = ( TCB_t * ) pxTaskBuffer; /*lint !e740 Unusual cast is ok as the structures are designed to have the same alignment, and the size is checked by an assert. */ + pxNewTCB->pxStack = ( StackType_t * ) puxStackBuffer; - /* Check the alignment of the calculated top of stack is correct. */ - configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) ); + #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) + { + /* Tasks can be created statically or dynamically, so note this + task was created statically in case the task is later deleted. */ + pxNewTCB->ucStaticallyAllocated = tskSTATICALLY_ALLOCATED_STACK_AND_TCB; + } + #endif /* configSUPPORT_DYNAMIC_ALLOCATION */ + + prvInitialiseNewTask( pxTaskCode, pcName, ulStackDepth, pvParameters, uxPriority, &xReturn, pxNewTCB, NULL ); + prvAddNewTaskToReadyList( pxNewTCB, pxTaskCode, xCoreID ); + } + else + { + xReturn = NULL; + } + + return xReturn; + } + +#endif /* SUPPORT_STATIC_ALLOCATION */ +/*-----------------------------------------------------------*/ + +#if( portUSING_MPU_WRAPPERS == 1 ) + + BaseType_t xTaskCreateRestricted( const TaskParameters_t * const pxTaskDefinition, TaskHandle_t *pxCreatedTask ) + { + TCB_t *pxNewTCB; + BaseType_t xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY; + + configASSERT( pxTaskDefinition->puxStackBuffer ); + + if( pxTaskDefinition->puxStackBuffer != NULL ) + { + /* Allocate space for the TCB. Where the memory comes from depends + on the implementation of the port malloc function and whether or + not static allocation is being used. */ + pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); + + if( pxNewTCB != NULL ) + { + /* Store the stack location in the TCB. */ + pxNewTCB->pxStack = pxTaskDefinition->puxStackBuffer; + + /* Tasks can be created statically or dynamically, so note + this task had a statically allocated stack in case it is + later deleted. The TCB was allocated dynamically. */ + pxNewTCB->ucStaticallyAllocated = tskSTATICALLY_ALLOCATED_STACK_ONLY; + + prvInitialiseNewTask( pxTaskDefinition->pvTaskCode, + pxTaskDefinition->pcName, + ( uint32_t ) pxTaskDefinition->usStackDepth, + pxTaskDefinition->pvParameters, + pxTaskDefinition->uxPriority, + pxCreatedTask, pxNewTCB, + pxTaskDefinition->xRegions ); + + prvAddNewTaskToReadyList( pxNewTCB, pxTaskDefinition->pvTaskCode, tskNO_AFFINITY ); + xReturn = pdPASS; + } + } + + return xReturn; + } + +#endif /* portUSING_MPU_WRAPPERS */ +/*-----------------------------------------------------------*/ + +#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) + + BaseType_t xTaskCreatePinnedToCore( TaskFunction_t pxTaskCode, + const char * const pcName, + const uint16_t usStackDepth, + void * const pvParameters, + UBaseType_t uxPriority, + TaskHandle_t * const pxCreatedTask, + const BaseType_t xCoreID ) + { + TCB_t *pxNewTCB; + BaseType_t xReturn; + + /* If the stack grows down then allocate the stack then the TCB so the stack + does not grow into the TCB. Likewise if the stack grows up then allocate + the TCB then the stack. */ + #if( portSTACK_GROWTH > 0 ) + { + /* Allocate space for the TCB. Where the memory comes from depends on + the implementation of the port malloc function and whether or not static + allocation is being used. */ + pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); + + if( pxNewTCB != NULL ) + { + /* Allocate space for the stack used by the task being created. + The base of the stack memory stored in the TCB so the task can + be deleted later if required. */ + pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */ + + if( pxNewTCB->pxStack == NULL ) + { + /* Could not allocate the stack. Delete the allocated TCB. */ + vPortFree( pxNewTCB ); + pxNewTCB = NULL; + } + } } #else /* portSTACK_GROWTH */ { - pxTopOfStack = pxNewTCB->pxStack; + StackType_t *pxStack; - /* Check the alignment of the stack buffer is correct. */ - configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxNewTCB->pxStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) ); + /* Allocate space for the stack used by the task being created. */ + pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */ - /* If we want to use stack checking on architectures that use - a positive stack growth direction then we also need to store the - other extreme of the stack space. */ - pxNewTCB->pxEndOfStack = pxNewTCB->pxStack + ( usStackDepth - 1 ); + if( pxStack != NULL ) + { + /* Allocate space for the TCB. */ + pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e961 MISRA exception as the casts are only redundant for some paths. */ + + if( pxNewTCB != NULL ) + { + /* Store the stack location in the TCB. */ + pxNewTCB->pxStack = pxStack; + } + else + { + /* The stack cannot be used as the TCB was not created. Free + it again. */ + vPortFree( pxStack ); + } + } + else + { + pxNewTCB = NULL; + } } #endif /* portSTACK_GROWTH */ - /* Setup the newly allocated TCB with the initial state of the task. */ - prvInitialiseTCBVariables( pxNewTCB, pcName, uxPriority, xRegions, usStackDepth, xCoreID ); + if( pxNewTCB != NULL ) + { + #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) + { + /* Tasks can be created statically or dynamically, so note this + task was created dynamically in case it is later deleted. */ + pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB; + } + #endif /* configSUPPORT_STATIC_ALLOCATION */ - /* Initialize the TCB stack to look as if the task was already running, - but had been interrupted by the scheduler. The return address is set - to the start of the task function. Once the stack has been initialised - the top of stack variable is updated. */ - #if( portUSING_MPU_WRAPPERS == 1 ) - { - pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters, xRunPrivileged ); + prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL ); + prvAddNewTaskToReadyList( pxNewTCB, pxTaskCode, xCoreID ); + xReturn = pdPASS; } - #else /* portUSING_MPU_WRAPPERS */ + else { - pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters ); + xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY; } - #endif /* portUSING_MPU_WRAPPERS */ - if( ( void * ) pxCreatedTask != NULL ) + return xReturn; + } + +#endif /* configSUPPORT_DYNAMIC_ALLOCATION */ +/*-----------------------------------------------------------*/ + +static void prvInitialiseNewTask( TaskFunction_t pxTaskCode, + const char * const pcName, + const uint32_t ulStackDepth, + void * const pvParameters, + UBaseType_t uxPriority, + TaskHandle_t * const pxCreatedTask, + TCB_t *pxNewTCB, + const MemoryRegion_t * const xRegions ) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */ +{ +StackType_t *pxTopOfStack; +UBaseType_t x; + + #if( portUSING_MPU_WRAPPERS == 1 ) + /* Should the task be created in privileged mode? */ + BaseType_t xRunPrivileged; + if( ( uxPriority & portPRIVILEGE_BIT ) != 0U ) { - /* Pass the TCB out - in an anonymous way. The calling function/ - task can use this as a handle to delete the task later if - required.*/ - *pxCreatedTask = ( TaskHandle_t ) pxNewTCB; + xRunPrivileged = pdTRUE; + } + else + { + xRunPrivileged = pdFALSE; + } + uxPriority &= ~portPRIVILEGE_BIT; + #endif /* portUSING_MPU_WRAPPERS == 1 */ + + /* Avoid dependency on memset() if it is not required. */ + #if( ( configCHECK_FOR_STACK_OVERFLOW > 1 ) || ( configUSE_TRACE_FACILITY == 1 ) || ( INCLUDE_uxTaskGetStackHighWaterMark == 1 ) ) + { + /* Fill the stack with a known value to assist debugging. */ + ( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) ulStackDepth * sizeof( StackType_t ) ); + } + #endif /* ( ( configCHECK_FOR_STACK_OVERFLOW > 1 ) || ( ( configUSE_TRACE_FACILITY == 1 ) || ( INCLUDE_uxTaskGetStackHighWaterMark == 1 ) ) ) */ + + /* Calculate the top of stack address. This depends on whether the stack + grows from high memory to low (as per the 80x86) or vice versa. + portSTACK_GROWTH is used to make the result positive or negative as required + by the port. */ + #if( portSTACK_GROWTH < 0 ) + { + pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 ); + pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) ); /*lint !e923 MISRA exception. Avoiding casts between pointers and integers is not practical. Size differences accounted for using portPOINTER_SIZE_TYPE type. */ + + /* Check the alignment of the calculated top of stack is correct. */ + configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) ); + } + #else /* portSTACK_GROWTH */ + { + pxTopOfStack = pxNewTCB->pxStack; + + /* Check the alignment of the stack buffer is correct. */ + configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxNewTCB->pxStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) ); + + /* The other extreme of the stack space is required if stack checking is + performed. */ + pxNewTCB->pxEndOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 ); + } + #endif /* portSTACK_GROWTH */ + + /* Store the task name in the TCB. */ + for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ ) + { + pxNewTCB->pcTaskName[ x ] = pcName[ x ]; + + /* Don't copy all configMAX_TASK_NAME_LEN if the string is shorter than + configMAX_TASK_NAME_LEN characters just in case the memory after the + string is not accessible (extremely unlikely). */ + if( pcName[ x ] == 0x00 ) + { + break; } else { mtCOVERAGE_TEST_MARKER(); } + } - /* Ensure interrupts don't access the task lists while they are being - updated. */ - taskENTER_CRITICAL(&xTaskQueueMutex); + /* Ensure the name string is terminated in the case that the string length + was greater or equal to configMAX_TASK_NAME_LEN. */ + pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0'; + + /* This is used as an array index so must ensure it's not too large. First + remove the privilege bit if one is present. */ + if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES ) + { + uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U; + } + else + { + mtCOVERAGE_TEST_MARKER(); + } + + pxNewTCB->uxPriority = uxPriority; + #if ( configUSE_MUTEXES == 1 ) + { + pxNewTCB->uxBasePriority = uxPriority; + pxNewTCB->uxMutexesHeld = 0; + } + #endif /* configUSE_MUTEXES */ + + vListInitialiseItem( &( pxNewTCB->xGenericListItem ) ); + vListInitialiseItem( &( pxNewTCB->xEventListItem ) ); + + /* Set the pxNewTCB as a link back from the ListItem_t. This is so we can get + back to the containing TCB from a generic item in a list. */ + listSET_LIST_ITEM_OWNER( &( pxNewTCB->xGenericListItem ), pxNewTCB ); + + /* Event lists are always in priority order. */ + listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */ + listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB ); + + #if ( portCRITICAL_NESTING_IN_TCB == 1 ) + { + pxNewTCB->uxCriticalNesting = ( UBaseType_t ) 0U; + } + #endif /* portCRITICAL_NESTING_IN_TCB */ + + #if ( configUSE_APPLICATION_TASK_TAG == 1 ) + { + pxNewTCB->pxTaskTag = NULL; + } + #endif /* configUSE_APPLICATION_TASK_TAG */ + + #if ( configGENERATE_RUN_TIME_STATS == 1 ) + { + pxNewTCB->ulRunTimeCounter = 0UL; + } + #endif /* configGENERATE_RUN_TIME_STATS */ + + #if ( portUSING_MPU_WRAPPERS == 1 ) + { + vPortStoreTaskMPUSettings( &( pxNewTCB->xMPUSettings ), xRegions, pxNewTCB->pxStack, ulStackDepth ); + } + #else + { + /* Avoid compiler warning about unreferenced parameter. */ + ( void ) xRegions; + } + #endif + + #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 ) + { + for( x = 0; x < ( UBaseType_t ) configNUM_THREAD_LOCAL_STORAGE_POINTERS; x++ ) { - uxCurrentNumberOfTasks++; + pxNewTCB->pvThreadLocalStoragePointers[ x ] = NULL; + } + } + #endif + + #if ( configUSE_TASK_NOTIFICATIONS == 1 ) + { + pxNewTCB->ulNotifiedValue = 0; + pxNewTCB->eNotifyState = eNotWaitingNotification; + } + #endif + + #if ( configUSE_NEWLIB_REENTRANT == 1 ) + { + /* Initialise this task's Newlib reent structure. */ + _REENT_INIT_PTR( ( &( pxNewTCB->xNewLib_reent ) ) ); + } + #endif + + #if( INCLUDE_xTaskAbortDelay == 1 ) + { + pxNewTCB->ucDelayAborted = pdFALSE; + } + #endif + + /* Initialize the TCB stack to look as if the task was already running, + but had been interrupted by the scheduler. The return address is set + to the start of the task function. Once the stack has been initialised + the top of stack variable is updated. */ + #if( portUSING_MPU_WRAPPERS == 1 ) + { + pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters, xRunPrivileged ); + } + #else /* portUSING_MPU_WRAPPERS */ + { + pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters ); + } + #endif /* portUSING_MPU_WRAPPERS */ + + if( ( void * ) pxCreatedTask != NULL ) + { + /* Pass the handle out in an anonymous way. The handle can be used to + change the created task's priority, delete the created task, etc.*/ + *pxCreatedTask = ( TaskHandle_t ) pxNewTCB; + } + else + { + mtCOVERAGE_TEST_MARKER(); + } +} +/*-----------------------------------------------------------*/ + +static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB, TaskFunction_t pxTaskCode, const BaseType_t xCoreID ) +{ + BaseType_t i; + + /* Ensure interrupts don't access the task lists while the lists are being + updated. */ + taskENTER_CRITICAL(&xTaskQueueMutex); + { + uxCurrentNumberOfTasks++; + if( pxCurrentTCB[ xPortGetCoreID() ] == NULL ) + { + /* There are no other tasks, or all the other tasks are in + the suspended state - make this the current task. */ + pxCurrentTCB[ xPortGetCoreID() ] = pxNewTCB; + if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 ) { /* This is the first task to be created so do the preliminary @@ -704,6 +1027,16 @@ BaseType_t i; fails, but we will report the failure. */ prvInitialiseTaskLists(); } + else + { + mtCOVERAGE_TEST_MARKER(); + } + } + else + { + /* If the scheduler is not already running, make this task the + current task if it is the highest priority task to be created + so far. */ if( xSchedulerRunning == pdFALSE ) { /* Scheduler isn't running yet. We need to determine on which CPU to run this task. */ @@ -713,7 +1046,7 @@ BaseType_t i; if (xCoreID == tskNO_AFFINITY || xCoreID == i) { /* Schedule if nothing is scheduled yet, or overwrite a task of lower prio. */ - if ( pxCurrentTCB[i] == NULL || pxCurrentTCB[i]->uxPriority <= uxPriority ) + if ( pxCurrentTCB[i] == NULL || pxCurrentTCB[i]->uxPriority <= pxNewTCB->uxPriority ) { #if portFIRST_TASK_HOOK if ( i == 0) { @@ -731,56 +1064,45 @@ BaseType_t i; { mtCOVERAGE_TEST_MARKER(); } - - uxTaskNumber++; - - #if ( configUSE_TRACE_FACILITY == 1 ) - { - /* Add a counter into the TCB for tracing only. */ - pxNewTCB->uxTCBNumber = uxTaskNumber; - } - #endif /* configUSE_TRACE_FACILITY */ - traceTASK_CREATE( pxNewTCB ); - - prvAddTaskToReadyList( pxNewTCB ); - - xReturn = pdPASS; - portSETUP_TCB( pxNewTCB ); } - taskEXIT_CRITICAL(&xTaskQueueMutex); - } - else - { - xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY; - traceTASK_CREATE_FAILED(); - } - if( xReturn == pdPASS ) - { - if( xSchedulerRunning != pdFALSE ) + uxTaskNumber++; + + #if ( configUSE_TRACE_FACILITY == 1 ) { - /* Scheduler is running. If the created task is of a higher priority than an executing task - then it should run now. - ToDo: This only works for the current core. If a task is scheduled on an other processor, - the other processor will keep running the task it's working on, and only switch to the newer - task on a timer interrupt. */ - //No mux here, uxPriority is mostly atomic and there's not really any harm if this check misfires. - if( pxCurrentTCB[ xPortGetCoreID() ]->uxPriority < uxPriority ) - { - taskYIELD_IF_USING_PREEMPTION(); - } - else - { - mtCOVERAGE_TEST_MARKER(); - } + /* Add a counter into the TCB for tracing only. */ + pxNewTCB->uxTCBNumber = uxTaskNumber; + } + #endif /* configUSE_TRACE_FACILITY */ + traceTASK_CREATE( pxNewTCB ); + + prvAddTaskToReadyList( pxNewTCB ); + + portSETUP_TCB( pxNewTCB ); + } + taskEXIT_CRITICAL(&xTaskQueueMutex); + + if( xSchedulerRunning != pdFALSE ) + { + /* Scheduler is running. If the created task is of a higher priority than an executing task + then it should run now. + ToDo: This only works for the current core. If a task is scheduled on an other processor, + the other processor will keep running the task it's working on, and only switch to the newer + task on a timer interrupt. */ + //No mux here, uxPriority is mostly atomic and there's not really any harm if this check misfires. + if( pxCurrentTCB[ xPortGetCoreID() ]->uxPriority < pxNewTCB->uxPriority ) + { + taskYIELD_IF_USING_PREEMPTION(); } else { mtCOVERAGE_TEST_MARKER(); } } - - return xReturn; + else + { + mtCOVERAGE_TEST_MARKER(); + } } /*-----------------------------------------------------------*/ @@ -2971,120 +3293,6 @@ static portTASK_FUNCTION( prvIdleTask, pvParameters ) #endif /* configUSE_TICKLESS_IDLE */ /*-----------------------------------------------------------*/ -static void prvInitialiseTCBVariables( TCB_t * const pxTCB, const char * const pcName, UBaseType_t uxPriority, const MemoryRegion_t * const xRegions, const uint16_t usStackDepth, const BaseType_t xCoreID ) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */ -{ -UBaseType_t x; - - /* Store the task name in the TCB. */ - for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ ) - { - pxTCB->pcTaskName[ x ] = pcName[ x ]; - - /* Don't copy all configMAX_TASK_NAME_LEN if the string is shorter than - configMAX_TASK_NAME_LEN characters just in case the memory after the - string is not accessible (extremely unlikely). */ - if( pcName[ x ] == 0x00 ) - { - break; - } - else - { - mtCOVERAGE_TEST_MARKER(); - } - } - - /* Ensure the name string is terminated in the case that the string length - was greater or equal to configMAX_TASK_NAME_LEN. */ - pxTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0'; - - /* This is used as an array index so must ensure it's not too large. First - remove the privilege bit if one is present. */ - if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES ) - { - uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U; - } - else - { - mtCOVERAGE_TEST_MARKER(); - } - - pxTCB->uxPriority = uxPriority; - pxTCB->xCoreID = xCoreID; - #if ( configUSE_MUTEXES == 1 ) - { - pxTCB->uxBasePriority = uxPriority; - pxTCB->uxMutexesHeld = 0; - } - #endif /* configUSE_MUTEXES */ - - vListInitialiseItem( &( pxTCB->xGenericListItem ) ); - vListInitialiseItem( &( pxTCB->xEventListItem ) ); - - /* Set the pxTCB as a link back from the ListItem_t. This is so we can get - back to the containing TCB from a generic item in a list. */ - listSET_LIST_ITEM_OWNER( &( pxTCB->xGenericListItem ), pxTCB ); - - /* Event lists are always in priority order. */ - listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */ - listSET_LIST_ITEM_OWNER( &( pxTCB->xEventListItem ), pxTCB ); - - #if ( portCRITICAL_NESTING_IN_TCB == 1 ) - { - pxTCB->uxCriticalNesting = ( UBaseType_t ) 0U; - } - #endif /* portCRITICAL_NESTING_IN_TCB */ - - #if ( configUSE_APPLICATION_TASK_TAG == 1 ) - { - pxTCB->pxTaskTag = NULL; - } - #endif /* configUSE_APPLICATION_TASK_TAG */ - - #if ( configGENERATE_RUN_TIME_STATS == 1 ) - { - pxTCB->ulRunTimeCounter = 0UL; - } - #endif /* configGENERATE_RUN_TIME_STATS */ - - #if ( portUSING_MPU_WRAPPERS == 1 ) - { - vPortStoreTaskMPUSettings( &( pxTCB->xMPUSettings ), xRegions, pxTCB->pxStack, usStackDepth ); - } - #else /* portUSING_MPU_WRAPPERS */ - { - ( void ) xRegions; - ( void ) usStackDepth; - } - #endif /* portUSING_MPU_WRAPPERS */ - - #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 ) - { - for( x = 0; x < ( UBaseType_t ) configNUM_THREAD_LOCAL_STORAGE_POINTERS; x++ ) - { - pxTCB->pvThreadLocalStoragePointers[ x ] = NULL; - #if ( configTHREAD_LOCAL_STORAGE_DELETE_CALLBACKS ) - pxTCB->pvThreadLocalStoragePointersDelCallback[ x ] = (TlsDeleteCallbackFunction_t)NULL; - #endif - } - } - #endif - - - #if ( configUSE_TASK_NOTIFICATIONS == 1 ) - { - pxTCB->ulNotifiedValue = 0; - pxTCB->eNotifyState = eNotWaitingNotification; - } - #endif - - #if ( configUSE_NEWLIB_REENTRANT == 1 ) - { - /* Initialise this task's Newlib reent structure. */ - _REENT_INIT_PTR( ( &( pxTCB->xNewLib_reent ) ) ); - } - #endif /* configUSE_NEWLIB_REENTRANT */ -} -/*-----------------------------------------------------------*/ #if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 ) #if ( configTHREAD_LOCAL_STORAGE_DELETE_CALLBACKS ) @@ -3280,81 +3488,6 @@ static void prvAddCurrentTaskToDelayedList( const BaseType_t xCoreID, const Tick } /*-----------------------------------------------------------*/ -static TCB_t *prvAllocateTCBAndStack( const uint16_t usStackDepth, StackType_t * const puxStackBuffer ) -{ -TCB_t *pxNewTCB; - - /* If the stack grows down then allocate the stack then the TCB so the stack - does not grow into the TCB. Likewise if the stack grows up then allocate - the TCB then the stack. */ - #if( portSTACK_GROWTH > 0 ) - { - /* Allocate space for the TCB. Where the memory comes from depends on - the implementation of the port malloc function. */ - pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); - - if( pxNewTCB != NULL ) - { - /* Allocate space for the stack used by the task being created. - The base of the stack memory stored in the TCB so the task can - be deleted later if required. */ - pxNewTCB->pxStack = ( StackType_t * ) pvPortMallocAligned( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ), puxStackBuffer ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */ - - if( pxNewTCB->pxStack == NULL ) - { - /* Could not allocate the stack. Delete the allocated TCB. */ - vPortFree( pxNewTCB ); - pxNewTCB = NULL; - } - } - } - #else /* portSTACK_GROWTH */ - { - StackType_t *pxStack; - - /* Allocate space for the stack used by the task being created. */ - pxStack = ( StackType_t * ) pvPortMallocAligned( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ), puxStackBuffer ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */ - - if( pxStack != NULL ) - { - /* Allocate space for the TCB. Where the memory comes from depends - on the implementation of the port malloc function. */ - pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); - - if( pxNewTCB != NULL ) - { - /* Store the stack location in the TCB. */ - pxNewTCB->pxStack = pxStack; - } - else - { - /* The stack cannot be used as the TCB was not created. Free it - again. */ - vPortFree( pxStack ); - } - } - else - { - pxNewTCB = NULL; - } - } - #endif /* portSTACK_GROWTH */ - - if( pxNewTCB != NULL ) - { - /* Avoid dependency on memset() if it is not required. */ - #if( ( configCHECK_FOR_STACK_OVERFLOW > 1 ) || ( configUSE_TRACE_FACILITY == 1 ) || ( INCLUDE_uxTaskGetStackHighWaterMark == 1 ) ) - { - /* Just to help debugging. */ - ( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) usStackDepth * sizeof( StackType_t ) ); - } - #endif /* ( ( configCHECK_FOR_STACK_OVERFLOW > 1 ) || ( ( configUSE_TRACE_FACILITY == 1 ) || ( INCLUDE_uxTaskGetStackHighWaterMark == 1 ) ) ) */ - } - - return pxNewTCB; -} -/*-----------------------------------------------------------*/ - #if ( configUSE_TRACE_FACILITY == 1 ) static UBaseType_t prvListTaskWithinSingleList( TaskStatus_t *pxTaskStatusArray, List_t *pxList, eTaskState eState ) @@ -3509,22 +3642,40 @@ TCB_t *pxNewTCB; } #endif /* configUSE_NEWLIB_REENTRANT */ - #if( portUSING_MPU_WRAPPERS == 1 ) + #if( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 0 ) && ( portUSING_MPU_WRAPPERS == 0 ) ) { - /* Only free the stack if it was allocated dynamically in the first - place. */ - if( pxTCB->xUsingStaticallyAllocatedStack == pdFALSE ) + /* The task can only have been allocated dynamically - free both + the stack and TCB. */ + vPortFreeAligned( pxTCB->pxStack ); + vPortFree( pxTCB ); + } + #elif( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE == 1 ) + { + /* The task could have been allocated statically or dynamically, so + check what was statically allocated before trying to free the + memory. */ + if( pxTCB->ucStaticallyAllocated == tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB ) { + /* Both the stack and TCB were allocated dynamically, so both + must be freed. */ vPortFreeAligned( pxTCB->pxStack ); + vPortFree( pxTCB ); + } + else if( pxTCB->ucStaticallyAllocated == tskSTATICALLY_ALLOCATED_STACK_ONLY ) + { + /* Only the stack was statically allocated, so the TCB is the + only memory that must be freed. */ + vPortFree( pxTCB ); + } + else + { + /* Neither the stack nor the TCB were allocated dynamically, so + nothing needs to be freed. */ + configASSERT( pxTCB->ucStaticallyAllocated == tskSTATICALLY_ALLOCATED_STACK_AND_TCB ) + mtCOVERAGE_TEST_MARKER(); } } - #else - { - vPortFreeAligned( pxTCB->pxStack ); - } - #endif - - vPortFree( pxTCB ); + #endif /* configSUPPORT_DYNAMIC_ALLOCATION */ } #endif /* INCLUDE_vTaskDelete */ @@ -3932,7 +4083,9 @@ scheduler will re-enable the interrupts instead. */ function is executing. */ uxArraySize = uxCurrentNumberOfTasks; - /* Allocate an array index for each task. */ + /* Allocate an array index for each task. NOTE! if + configSUPPORT_DYNAMIC_ALLOCATION is set to 0 then pvPortMalloc() will + equate to NULL. */ pxTaskStatusArray = pvPortMalloc( uxCurrentNumberOfTasks * sizeof( TaskStatus_t ) ); if( pxTaskStatusArray != NULL ) @@ -3972,7 +4125,8 @@ scheduler will re-enable the interrupts instead. */ pcWriteBuffer += strlen( pcWriteBuffer ); } - /* Free the array again. */ + /* Free the array again. NOTE! If configSUPPORT_DYNAMIC_ALLOCATION + is 0 then vPortFree() will be #defined to nothing. */ vPortFree( pxTaskStatusArray ); } else @@ -4030,7 +4184,9 @@ scheduler will re-enable the interrupts instead. */ function is executing. */ uxArraySize = uxCurrentNumberOfTasks; - /* Allocate an array index for each task. */ + /* Allocate an array index for each task. NOTE! If + configSUPPORT_DYNAMIC_ALLOCATION is set to 0 then pvPortMalloc() will + equate to NULL. */ pxTaskStatusArray = pvPortMalloc( uxCurrentNumberOfTasks * sizeof( TaskStatus_t ) ); if( pxTaskStatusArray != NULL ) @@ -4096,7 +4252,8 @@ scheduler will re-enable the interrupts instead. */ mtCOVERAGE_TEST_MARKER(); } - /* Free the array again. */ + /* Free the array again. NOTE! If configSUPPORT_DYNAMIC_ALLOCATION + is 0 then vPortFree() will be #defined to nothing. */ vPortFree( pxTaskStatusArray ); } else From 9c0cd10d4885a03d4cb97a323c88ca7dd96555eb Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 4 Oct 2016 12:37:10 +1100 Subject: [PATCH 002/149] Build system tests: Add test cases for out-of-tree builds (currently failing) See github #38 --- make/test_build_system.sh | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/make/test_build_system.sh b/make/test_build_system.sh index 5be1504e3..b6aaa9e58 100755 --- a/make/test_build_system.sh +++ b/make/test_build_system.sh @@ -62,7 +62,7 @@ function run_tests() print_status "Partition CSV file rebuilds partitions" take_build_snapshot touch ${IDF_PATH}/components/partition_table/partitions_singleapp.csv - make partition_table + make partition_table || failure "Failed to build partition table" assert_rebuilt partitions_singleapp.bin assert_not_rebuilt app-template.bin app-template.elf ${BOOTLOADER_BINS} @@ -70,18 +70,31 @@ function run_tests() take_build_snapshot # verify no build files are refreshed by a partial make ALL_BUILD_FILES=$(find ${BUILD} -type f | sed "s@${BUILD}/@@") - make + make || failure "Partial build failed" assert_not_rebuilt ${ALL_BUILD_FILES} print_status "Cleaning should remove all files from build" - make clean + make clean || failure "Failed to make clean" ALL_BUILD_FILES=$(find ${BUILD} -type f) if [ -n "${ALL_BUILD_FILES}" ]; then failure "Files weren't cleaned: ${ALL_BUILD_FILES}" fi + print_status "Moving BUILD_DIR_BASE out of tree should still build OK" + rm -rf --preserve-root ${BUILD}/* + OUTOFTREE_BUILD=${TESTDIR}/alt_build + make BUILD_DIR_BASE=${OUTOFTREE_BUILD} || failure "Failed to build with BUILD_DIR_BASE overriden" + NEW_BUILD_FILES=$(find ${OUTOFREE_BUILD} -type f) + if [ -z "${NEW_BUILD_FILES}" ]; then + failure "No files found in new build directory!" + fi + DEFAULT_BUILD_FILES=$(find ${BUILD} -mindepth 1) + if [ -n "${DEFAULT_BUILD_FILES}" ]; then + failure "Some files were incorrectly put into the default build directory: ${DEFAULT_BUILD_FILES}" + fi + print_status "Can still clean build if all text files are CRLFs" - make clean + make clean || failure "Unexpected failure to make clean" find . -exec unix2dos {} \; # CRLFify template dir # make a copy of esp-idf and CRLFify it CRLF_ESPIDF=${TESTDIR}/esp-idf-crlf @@ -89,12 +102,11 @@ function run_tests() cp -rv ${IDF_PATH}/* ${CRLF_ESPIDF} # don't CRLFify executable files, as Linux will fail to execute them find ${CRLF_ESPIDF} -type f ! -perm 755 -exec unix2dos {} \; - make IDF_PATH=${CRLF_ESPIDF} + make IDF_PATH=${CRLF_ESPIDF} || failure "Failed to build with CRLFs in source" # do the same checks we do for the clean build assert_built ${APP_BINS} ${BOOTLOADER_BINS} partitions_singleapp.bin [ -f ${BUILD}/partition*.bin ] || failure "A partition table should have been built in CRLF mode" - # NOTE: If adding new tests, add them above this CRLF test... print_status "All tests completed" if [ -n "${FAILURES}" ]; then From 66882347e86e142ba0c72c8c3e6fe1a946a25a78 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 4 Oct 2016 15:03:48 +1100 Subject: [PATCH 003/149] build system: Fix out-of-tree building via BUILD_DIR_BASE Closes #38 --- components/bootloader/Makefile.projbuild | 16 +++++----- components/esp32/component.mk | 4 ++- make/common.mk | 2 +- make/project.mk | 3 +- make/project_config.mk | 38 ++++++++++-------------- 5 files changed, 31 insertions(+), 32 deletions(-) diff --git a/components/bootloader/Makefile.projbuild b/components/bootloader/Makefile.projbuild index d45cf144e..7c5cde3b8 100644 --- a/components/bootloader/Makefile.projbuild +++ b/components/bootloader/Makefile.projbuild @@ -14,17 +14,18 @@ BOOTLOADER_COMPONENT_PATH := $(COMPONENT_PATH) BOOTLOADER_BUILD_DIR=$(BUILD_DIR_BASE)/bootloader BOOTLOADER_BIN=$(BOOTLOADER_BUILD_DIR)/bootloader.bin +# Custom recursive make for bootloader sub-project +BOOTLOADER_MAKE=$(MAKE) -C $(BOOTLOADER_COMPONENT_PATH)/src \ + MAKEFLAGS= V=$(V) \ + BUILD_DIR_BASE=$(BOOTLOADER_BUILD_DIR) \ + .PHONY: bootloader-clean bootloader-flash bootloader $(BOOTLOADER_BIN) $(BOOTLOADER_BIN): $(COMPONENT_PATH)/src/sdkconfig - $(Q) PROJECT_PATH= \ - BUILD_DIR_BASE=$(BOOTLOADER_BUILD_DIR) \ - $(MAKE) -C $(BOOTLOADER_COMPONENT_PATH)/src MAKEFLAGS= V=$(V) TARGET_BIN_LAYOUT="$(BOOTLOADER_TARGET_BIN_LAYOUT)" $(BOOTLOADER_BIN) + $(Q) $(BOOTLOADER_MAKE) $@ bootloader-clean: - $(Q) PROJECT_PATH= \ - BUILD_DIR_BASE=$(BOOTLOADER_BUILD_DIR) \ - $(MAKE) -C $(BOOTLOADER_COMPONENT_PATH)/src app-clean MAKEFLAGS= V=$(V) + $(Q) $(BOOTLOADER_MAKE) app-clean clean: bootloader-clean @@ -43,7 +44,8 @@ $(COMPONENT_PATH)/src/sdkconfig: $(PROJECT_PATH)/sdkconfig # bootloader-flash calls flash in the bootloader dummy project bootloader-flash: $(BOOTLOADER_BIN) - $(MAKE) -C $(BOOTLOADER_COMPONENT_PATH)/src flash MAKEFLAGS= V=$(V) + $(BOOTLOADER_MAKE) flash + else CFLAGS += -D BOOTLOADER_BUILD=1 -I $(IDF_PATH)/components/esp32/include diff --git a/components/esp32/component.mk b/components/esp32/component.mk index 6eac77afd..85040d77a 100644 --- a/components/esp32/component.mk +++ b/components/esp32/component.mk @@ -6,7 +6,7 @@ # lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable, # please read the esp-idf build system document if you need to do this. # --include $(PROJECT_PATH)/build/include/config/auto.conf +-include $(BUILD_DIR_BASE)/include/config/auto.conf COMPONENT_SRCDIRS := . hwcrypto @@ -44,6 +44,8 @@ $(COMPONENT_LIBRARY): $(ALL_LIB_FILES) # saves us from having to add the target to a Makefile.projbuild $(COMPONENT_LIBRARY): esp32_out.ld +# .. is BUILD_DIR_BASE here, as component makefiles +# are evaluated with CWD=component build dir esp32_out.ld: $(COMPONENT_PATH)/ld/esp32.ld ../include/sdkconfig.h $(CC) -I ../include -C -P -x c -E $< -o $@ diff --git a/make/common.mk b/make/common.mk index a515584a9..b2917d5c9 100644 --- a/make/common.mk +++ b/make/common.mk @@ -6,7 +6,7 @@ # # (Note that we only rebuild auto.conf automatically for some targets, # see project_config.mk for details.) --include $(PROJECT_PATH)/build/include/config/auto.conf +-include $(BUILD_DIR_BASE)/include/config/auto.conf #Handling of V=1/VERBOSE=1 flag # diff --git a/make/project.mk b/make/project.mk index 35dccaf24..74943334a 100644 --- a/make/project.mk +++ b/make/project.mk @@ -50,6 +50,7 @@ endif #The directory where we put all objects/libraries/binaries. The project Makefile can #configure this if needed. BUILD_DIR_BASE ?= $(PROJECT_PATH)/build +export BUILD_DIR_BASE #Component directories. These directories are searched for components. #The project Makefile can override these component dirs, or define extra component directories. @@ -105,7 +106,7 @@ COMPONENT_INCLUDES := $(abspath $(foreach comp,$(COMPONENT_PATHS_BUILDABLE),$(ad $(call GetVariable,$(comp),COMPONENT_ADD_INCLUDEDIRS)))) #Also add project include path, for sdk includes -COMPONENT_INCLUDES += $(PROJECT_PATH)/build/include/ +COMPONENT_INCLUDES += $(BUILD_DIR_BASE)/include/ export COMPONENT_INCLUDES #COMPONENT_LDFLAGS has a list of all flags that are needed to link the components together. It's collected diff --git a/make/project_config.mk b/make/project_config.mk index d2909bb30..92937e8bf 100644 --- a/make/project_config.mk +++ b/make/project_config.mk @@ -13,13 +13,15 @@ $(KCONFIG_TOOL_DIR)/mconf $(KCONFIG_TOOL_DIR)/conf: CC=$(HOSTCC) LD=$(HOSTLD) \ $(MAKE) -C $(KCONFIG_TOOL_DIR) -menuconfig: $(KCONFIG_TOOL_DIR)/mconf $(IDF_PATH)/Kconfig $(BUILD_DIR_BASE) - $(summary) MENUCONFIG - $(Q) KCONFIG_AUTOHEADER=$(PROJECT_PATH)/build/include/sdkconfig.h \ +# use a wrapper environment for where we run Kconfig tools +KCONFIG_TOOL_ENV=KCONFIG_AUTOHEADER=$(BUILD_DIR_BASE)/include/sdkconfig.h \ KCONFIG_CONFIG=$(PROJECT_PATH)/sdkconfig \ COMPONENT_KCONFIGS="$(COMPONENT_KCONFIGS)" \ - COMPONENT_KCONFIGS_PROJBUILD="$(COMPONENT_KCONFIGS_PROJBUILD)" \ - $(KCONFIG_TOOL_DIR)/mconf $(IDF_PATH)/Kconfig + COMPONENT_KCONFIGS_PROJBUILD="$(COMPONENT_KCONFIGS_PROJBUILD)" + +menuconfig: $(KCONFIG_TOOL_DIR)/mconf $(IDF_PATH)/Kconfig $(BUILD_DIR_BASE) + $(summary) MENUCONFIG + $(Q) $(KCONFIG_TOOL_ENV) $(KCONFIG_TOOL_DIR)/mconf $(IDF_PATH)/Kconfig ifeq ("$(wildcard $(PROJECT_PATH)/sdkconfig)","") #No sdkconfig found. Need to run menuconfig to make this if we need it. @@ -28,17 +30,13 @@ endif defconfig: $(KCONFIG_TOOL_DIR)/mconf $(IDF_PATH)/Kconfig $(BUILD_DIR_BASE) $(summary) DEFCONFIG - $(Q) mkdir -p $(PROJECT_PATH)/build/include/config - $(Q) KCONFIG_AUTOHEADER=$(PROJECT_PATH)/build/include/sdkconfig.h \ - KCONFIG_CONFIG=$(PROJECT_PATH)/sdkconfig \ - COMPONENT_KCONFIGS="$(COMPONENT_KCONFIGS)" \ - COMPONENT_KCONFIGS_PROJBUILD="$(COMPONENT_KCONFIGS_PROJBUILD)" \ - $(KCONFIG_TOOL_DIR)/conf --olddefconfig $(IDF_PATH)/Kconfig + $(Q) mkdir -p $(BUILD_DIR_BASE)/include/config + $(Q) $(KCONFIG_TOOL_ENV) $(KCONFIG_TOOL_DIR)/conf --olddefconfig $(IDF_PATH)/Kconfig # Work out of whether we have to build the Kconfig makefile # (auto.conf), or if we're in a situation where we don't need it NON_CONFIG_TARGETS := clean %-clean get_variable help menuconfig defconfig -AUTO_CONF_REGEN_TARGET := $(PROJECT_PATH)/build/include/config/auto.conf +AUTO_CONF_REGEN_TARGET := $(BUILD_DIR_BASE)/include/config/auto.conf # disable AUTO_CONF_REGEN_TARGET if all targets are non-config targets # (and not building default target) @@ -46,19 +44,15 @@ ifneq ("$(MAKECMDGOALS)","") ifeq ($(filter $(NON_CONFIG_TARGETS), $(MAKECMDGOALS)),$(MAKECMDGOALS)) AUTO_CONF_REGEN_TARGET := # dummy target -$(PROJECT_PATH)/build/include/config/auto.conf: +$(BUILD_DIR_BASE)/include/config/auto.conf: endif endif -$(AUTO_CONF_REGEN_TARGET) $(PROJECT_PATH)/build/include/sdkconfig.h: $(PROJECT_PATH)/sdkconfig $(KCONFIG_TOOL_DIR)/conf $(COMPONENT_KCONFIGS) $(COMPONENT_KCONFIGS_PROJBUILD) +$(AUTO_CONF_REGEN_TARGET) $(BUILD_DIR_BASE)/include/sdkconfig.h: $(PROJECT_PATH)/sdkconfig $(KCONFIG_TOOL_DIR)/conf $(COMPONENT_KCONFIGS) $(COMPONENT_KCONFIGS_PROJBUILD) $(summary) GENCONFIG - $(Q) mkdir -p $(PROJECT_PATH)/build/include/config - $(Q) cd build; KCONFIG_AUTOHEADER="$(PROJECT_PATH)/build/include/sdkconfig.h" \ - KCONFIG_CONFIG=$(PROJECT_PATH)/sdkconfig \ - COMPONENT_KCONFIGS="$(COMPONENT_KCONFIGS)" \ - COMPONENT_KCONFIGS_PROJBUILD="$(COMPONENT_KCONFIGS_PROJBUILD)" \ - $(KCONFIG_TOOL_DIR)/conf --silentoldconfig $(IDF_PATH)/Kconfig - $(Q) touch $(AUTO_CONF_REGEN_TARGET) $(PROJECT_PATH)/build/include/sdkconfig.h + $(Q) mkdir -p $(BUILD_DIR_BASE)/include/config + $(Q) cd $(BUILD_DIR_BASE); $(KCONFIG_TOOL_ENV) $(KCONFIG_TOOL_DIR)/conf --silentoldconfig $(IDF_PATH)/Kconfig + $(Q) touch $(AUTO_CONF_REGEN_TARGET) $(BUILD_DIR_BASE)/include/sdkconfig.h # touch to ensure both output files are newer - as 'conf' can also update sdkconfig (a dependency). Without this, # sometimes you can get an infinite make loop on Windows where sdkconfig always gets regenerated newer # than the target(!) @@ -68,4 +62,4 @@ clean: config-clean config-clean: $(summary RM CONFIG) $(MAKE) -C $(KCONFIG_TOOL_DIR) clean - $(Q) rm -rf $(PROJECT_PATH)/build/include/config $(PROJECT_PATH)/build/include/sdkconfig.h + $(Q) rm -rf $(BUILD_DIR_BASE)/include/config $(BUILD_DIR_BASE)/include/sdkconfig.h From 477d71e589a18af96472199caf918097c22ed0bc Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 4 Oct 2016 15:38:20 +1100 Subject: [PATCH 004/149] config system: Fix configuration when BUILD_DIR_BASE out-of-tree Ref #38. Also no longer generates bootloader sdkconfig in source tree. --- .gitignore | 3 --- components/bootloader/Makefile.projbuild | 20 ++++++++++++-------- make/project_config.mk | 13 ++++++++----- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index 85027773b..5ec57a167 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,3 @@ GPATH examples/*/sdkconfig examples/*/sdkconfig.old examples/*/build - -# Bootloader files -components/bootloader/src/sdkconfig.old \ No newline at end of file diff --git a/components/bootloader/Makefile.projbuild b/components/bootloader/Makefile.projbuild index 7c5cde3b8..559769957 100644 --- a/components/bootloader/Makefile.projbuild +++ b/components/bootloader/Makefile.projbuild @@ -13,19 +13,21 @@ ifeq ("$(IS_BOOTLOADER_BUILD)","") BOOTLOADER_COMPONENT_PATH := $(COMPONENT_PATH) BOOTLOADER_BUILD_DIR=$(BUILD_DIR_BASE)/bootloader BOOTLOADER_BIN=$(BOOTLOADER_BUILD_DIR)/bootloader.bin +BOOTLOADER_SDKCONFIG=$(BOOTLOADER_BUILD_DIR)/sdkconfig # Custom recursive make for bootloader sub-project BOOTLOADER_MAKE=$(MAKE) -C $(BOOTLOADER_COMPONENT_PATH)/src \ - MAKEFLAGS= V=$(V) \ + MAKEFLAGS= V=$(V) SDKCONFIG=$(BOOTLOADER_SDKCONFIG) \ BUILD_DIR_BASE=$(BOOTLOADER_BUILD_DIR) \ .PHONY: bootloader-clean bootloader-flash bootloader $(BOOTLOADER_BIN) -$(BOOTLOADER_BIN): $(COMPONENT_PATH)/src/sdkconfig +$(BOOTLOADER_BIN): | $(BOOTLOADER_BUILD_DIR)/sdkconfig $(Q) $(BOOTLOADER_MAKE) $@ bootloader-clean: - $(Q) $(BOOTLOADER_MAKE) app-clean + $(Q) $(BOOTLOADER_MAKE) app-clean config-clean + $(Q) rm -f $(BOOTLOADER_SDKCONFIG) $(BOOTLOADER_SDKCONFIG).old clean: bootloader-clean @@ -37,15 +39,17 @@ all_binaries: $(BOOTLOADER_BIN) ESPTOOL_ALL_FLASH_ARGS += 0x1000 $(BOOTLOADER_BIN) -# synchronise the project level config to the component's -# config -$(COMPONENT_PATH)/src/sdkconfig: $(PROJECT_PATH)/sdkconfig - $(Q) cp $< $@ - # bootloader-flash calls flash in the bootloader dummy project bootloader-flash: $(BOOTLOADER_BIN) $(BOOTLOADER_MAKE) flash +# synchronise the project level config to the bootloader's +# config +$(BOOTLOADER_SDKCONFIG): $(PROJECT_PATH)/sdkconfig | $(BOOTLOADER_BUILD_DIR) + $(Q) cp $< $@ + +$(BOOTLOADER_BUILD_DIR): + $(Q) mkdir -p $@ else CFLAGS += -D BOOTLOADER_BUILD=1 -I $(IDF_PATH)/components/esp32/include diff --git a/make/project_config.mk b/make/project_config.mk index 92937e8bf..40f51ff8f 100644 --- a/make/project_config.mk +++ b/make/project_config.mk @@ -7,6 +7,10 @@ COMPONENT_KCONFIGS_PROJBUILD := $(foreach component,$(COMPONENT_PATHS),$(wildcar #For doing make menuconfig etc KCONFIG_TOOL_DIR=$(IDF_PATH)/tools/kconfig +# set SDKCONFIG to the project's sdkconfig, +# unless it's overriden (happens for bootloader) +SDKCONFIG ?= $(PROJECT_PATH)/sdkconfig + # clear MAKEFLAGS as the menuconfig makefile uses implicit compile rules $(KCONFIG_TOOL_DIR)/mconf $(KCONFIG_TOOL_DIR)/conf: MAKEFLAGS="" \ @@ -15,17 +19,16 @@ $(KCONFIG_TOOL_DIR)/mconf $(KCONFIG_TOOL_DIR)/conf: # use a wrapper environment for where we run Kconfig tools KCONFIG_TOOL_ENV=KCONFIG_AUTOHEADER=$(BUILD_DIR_BASE)/include/sdkconfig.h \ - KCONFIG_CONFIG=$(PROJECT_PATH)/sdkconfig \ - COMPONENT_KCONFIGS="$(COMPONENT_KCONFIGS)" \ + COMPONENT_KCONFIGS="$(COMPONENT_KCONFIGS)" KCONFIG_CONFIG=$(SDKCONFIG) \ COMPONENT_KCONFIGS_PROJBUILD="$(COMPONENT_KCONFIGS_PROJBUILD)" menuconfig: $(KCONFIG_TOOL_DIR)/mconf $(IDF_PATH)/Kconfig $(BUILD_DIR_BASE) $(summary) MENUCONFIG $(Q) $(KCONFIG_TOOL_ENV) $(KCONFIG_TOOL_DIR)/mconf $(IDF_PATH)/Kconfig -ifeq ("$(wildcard $(PROJECT_PATH)/sdkconfig)","") +ifeq ("$(wildcard $(SDKCONFIG))","") #No sdkconfig found. Need to run menuconfig to make this if we need it. -$(PROJECT_PATH)/sdkconfig: menuconfig +$(SDKCONFIG): menuconfig endif defconfig: $(KCONFIG_TOOL_DIR)/mconf $(IDF_PATH)/Kconfig $(BUILD_DIR_BASE) @@ -48,7 +51,7 @@ $(BUILD_DIR_BASE)/include/config/auto.conf: endif endif -$(AUTO_CONF_REGEN_TARGET) $(BUILD_DIR_BASE)/include/sdkconfig.h: $(PROJECT_PATH)/sdkconfig $(KCONFIG_TOOL_DIR)/conf $(COMPONENT_KCONFIGS) $(COMPONENT_KCONFIGS_PROJBUILD) +$(AUTO_CONF_REGEN_TARGET) $(BUILD_DIR_BASE)/include/sdkconfig.h: $(SDKCONFIG) $(KCONFIG_TOOL_DIR)/conf $(COMPONENT_KCONFIGS) $(COMPONENT_KCONFIGS_PROJBUILD) $(summary) GENCONFIG $(Q) mkdir -p $(BUILD_DIR_BASE)/include/config $(Q) cd $(BUILD_DIR_BASE); $(KCONFIG_TOOL_ENV) $(KCONFIG_TOOL_DIR)/conf --silentoldconfig $(IDF_PATH)/Kconfig From eccf54b93969396d991012e453fa187cb8684c15 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 4 Oct 2016 15:54:27 +1100 Subject: [PATCH 005/149] build system tests: Add some more notes about test internals --- make/test_build_system.sh | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/make/test_build_system.sh b/make/test_build_system.sh index b6aaa9e58..401c8376c 100755 --- a/make/test_build_system.sh +++ b/make/test_build_system.sh @@ -2,7 +2,7 @@ # # Test the build system for basic consistency # -# Just a bash script that tests some likely make failure scenarios in a row +# A bash script that tests some likely make failure scenarios in a row # Creates its own test build directory under TMP and cleans it up when done. # # Environment variables: @@ -11,6 +11,17 @@ # ESP_IDF_TEMPLATE_GIT - Can override git clone source for template app. Otherwise github. # NOCLEANUP - Set to '1' if you want the script to leave its temporary directory when done, for post-mortem. # +# +# Internals: +# * The tests run in sequence & the system keeps track of all failures to print at the end. +# * BUILD directory is set to default BUILD_DIR_BASE +# * The "print_status" function both prints a status line to the log and keeps track of which test is running. +# * Calling the "failure" function prints a failure message to the log and also adds to the list of failures to print at the end. +# * The function "assert_built" tests for a file relative to the BUILD directory. +# * The function "take_build_snapshot" can be paired with the functions "assert_rebuilt" and "assert_not_rebuilt" to compare file timestamps and verify if they were rebuilt or not since the snapshot was taken. +# +# To add a new test case, add it to the end of the run_tests function. Note that not all test cases do comprehensive cleanup +# (although very invasive ones like appending CRLFs to all files take a copy of the esp-idf tree.) # Set up some variables # From f720e82d403a8a09cf94a176067aea71263c307f Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 6 Oct 2016 18:05:51 +1100 Subject: [PATCH 006/149] build system: Allow BUILD_DIR_BASE to be a relative directory (see github #38) --- components/bootloader/Makefile.projbuild | 2 +- components/esp32/component.mk | 2 +- make/project.mk | 2 +- make/project_config.mk | 2 +- make/test_build_system.sh | 10 +++++++++- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/components/bootloader/Makefile.projbuild b/components/bootloader/Makefile.projbuild index 559769957..02135e1c6 100644 --- a/components/bootloader/Makefile.projbuild +++ b/components/bootloader/Makefile.projbuild @@ -11,7 +11,7 @@ ifeq ("$(IS_BOOTLOADER_BUILD)","") BOOTLOADER_COMPONENT_PATH := $(COMPONENT_PATH) -BOOTLOADER_BUILD_DIR=$(BUILD_DIR_BASE)/bootloader +BOOTLOADER_BUILD_DIR=$(abspath $(BUILD_DIR_BASE)/bootloader) BOOTLOADER_BIN=$(BOOTLOADER_BUILD_DIR)/bootloader.bin BOOTLOADER_SDKCONFIG=$(BOOTLOADER_BUILD_DIR)/sdkconfig diff --git a/components/esp32/component.mk b/components/esp32/component.mk index 85040d77a..b3d95f1b1 100644 --- a/components/esp32/component.mk +++ b/components/esp32/component.mk @@ -6,7 +6,7 @@ # lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable, # please read the esp-idf build system document if you need to do this. # --include $(BUILD_DIR_BASE)/include/config/auto.conf +-include include/config/auto.conf COMPONENT_SRCDIRS := . hwcrypto diff --git a/make/project.mk b/make/project.mk index 74943334a..c4bf68db6 100644 --- a/make/project.mk +++ b/make/project.mk @@ -106,7 +106,7 @@ COMPONENT_INCLUDES := $(abspath $(foreach comp,$(COMPONENT_PATHS_BUILDABLE),$(ad $(call GetVariable,$(comp),COMPONENT_ADD_INCLUDEDIRS)))) #Also add project include path, for sdk includes -COMPONENT_INCLUDES += $(BUILD_DIR_BASE)/include/ +COMPONENT_INCLUDES += $(abspath $(BUILD_DIR_BASE)/include/) export COMPONENT_INCLUDES #COMPONENT_LDFLAGS has a list of all flags that are needed to link the components together. It's collected diff --git a/make/project_config.mk b/make/project_config.mk index 40f51ff8f..00452dda4 100644 --- a/make/project_config.mk +++ b/make/project_config.mk @@ -18,7 +18,7 @@ $(KCONFIG_TOOL_DIR)/mconf $(KCONFIG_TOOL_DIR)/conf: $(MAKE) -C $(KCONFIG_TOOL_DIR) # use a wrapper environment for where we run Kconfig tools -KCONFIG_TOOL_ENV=KCONFIG_AUTOHEADER=$(BUILD_DIR_BASE)/include/sdkconfig.h \ +KCONFIG_TOOL_ENV=KCONFIG_AUTOHEADER=$(abspath $(BUILD_DIR_BASE)/include/sdkconfig.h) \ COMPONENT_KCONFIGS="$(COMPONENT_KCONFIGS)" KCONFIG_CONFIG=$(SDKCONFIG) \ COMPONENT_KCONFIGS_PROJBUILD="$(COMPONENT_KCONFIGS_PROJBUILD)" diff --git a/make/test_build_system.sh b/make/test_build_system.sh index 401c8376c..b7004eece 100755 --- a/make/test_build_system.sh +++ b/make/test_build_system.sh @@ -91,7 +91,7 @@ function run_tests() failure "Files weren't cleaned: ${ALL_BUILD_FILES}" fi - print_status "Moving BUILD_DIR_BASE out of tree should still build OK" + print_status "Moving BUILD_DIR_BASE out of tree" rm -rf --preserve-root ${BUILD}/* OUTOFTREE_BUILD=${TESTDIR}/alt_build make BUILD_DIR_BASE=${OUTOFTREE_BUILD} || failure "Failed to build with BUILD_DIR_BASE overriden" @@ -104,6 +104,14 @@ function run_tests() failure "Some files were incorrectly put into the default build directory: ${DEFAULT_BUILD_FILES}" fi + print_status "BUILD_DIR_BASE inside default build directory" + rm -rf --preserve-root ${BUILD}/* + make BUILD_DIR_BASE=build/subdirectory || failure "Failed to build with BUILD_DIR_BASE as subdir" + NEW_BUILD_FILES=$(find ${BUILD}/subdirectory -type f) + if [ -z "${NEW_BUILD_FILES}" ]; then + failure "No files found in new build directory!" + fi + print_status "Can still clean build if all text files are CRLFs" make clean || failure "Unexpected failure to make clean" find . -exec unix2dos {} \; # CRLFify template dir From 6a890e6c49d1b4b00080fe9544f730b4b517331e Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 6 Oct 2016 18:06:52 +1100 Subject: [PATCH 007/149] build system tests: Untabify shell script --- make/test_build_system.sh | 260 +++++++++++++++++++------------------- 1 file changed, 130 insertions(+), 130 deletions(-) diff --git a/make/test_build_system.sh b/make/test_build_system.sh index b7004eece..f72158838 100755 --- a/make/test_build_system.sh +++ b/make/test_build_system.sh @@ -32,123 +32,123 @@ export V=1 function run_tests() { - FAILURES= - STATUS="Starting" - print_status "Checking prerequisites" - [ -z ${IDF_PATH} ] && echo "IDF_PATH is not set. Need path to esp-idf installation." && exit 2 + FAILURES= + STATUS="Starting" + print_status "Checking prerequisites" + [ -z ${IDF_PATH} ] && echo "IDF_PATH is not set. Need path to esp-idf installation." && exit 2 - print_status "Cloning template from ${ESP_IDF_TEMPLATE_GIT}..." - git clone ${ESP_IDF_TEMPLATE_GIT} template - cd template - git checkout ${CI_BUILD_REF_NAME} || echo "Using esp-idf-template default branch..." + print_status "Cloning template from ${ESP_IDF_TEMPLATE_GIT}..." + git clone ${ESP_IDF_TEMPLATE_GIT} template + cd template + git checkout ${CI_BUILD_REF_NAME} || echo "Using esp-idf-template default branch..." - print_status "Updating template config..." - make defconfig || exit $? + print_status "Updating template config..." + make defconfig || exit $? - BOOTLOADER_BINS="bootloader/bootloader.elf bootloader/bootloader.bin" - APP_BINS="app-template.elf app-template.bin" + BOOTLOADER_BINS="bootloader/bootloader.elf bootloader/bootloader.bin" + APP_BINS="app-template.elf app-template.bin" - print_status "Initial clean build" - # if make fails here, everything fails - make || exit $? - # check all the expected build artifacts from the clean build - assert_built ${APP_BINS} ${BOOTLOADER_BINS} partitions_singleapp.bin - [ -f ${BUILD}/partition*.bin ] || failure "A partition table should have been built" + print_status "Initial clean build" + # if make fails here, everything fails + make || exit $? + # check all the expected build artifacts from the clean build + assert_built ${APP_BINS} ${BOOTLOADER_BINS} partitions_singleapp.bin + [ -f ${BUILD}/partition*.bin ] || failure "A partition table should have been built" - print_status "Updating component source file rebuilds component" - # touch a file & do a build - take_build_snapshot - touch ${IDF_PATH}/components/esp32/syscalls.c - make || failure "Failed to partial build" - assert_rebuilt ${APP_BINS} esp32/libesp32.a esp32/syscalls.o - assert_not_rebuilt lwip/liblwip.a freertos/libfreertos.a ${BOOTLOADER_BINS} partitions_singleapp.bin + print_status "Updating component source file rebuilds component" + # touch a file & do a build + take_build_snapshot + touch ${IDF_PATH}/components/esp32/syscalls.c + make || failure "Failed to partial build" + assert_rebuilt ${APP_BINS} esp32/libesp32.a esp32/syscalls.o + assert_not_rebuilt lwip/liblwip.a freertos/libfreertos.a ${BOOTLOADER_BINS} partitions_singleapp.bin - print_status "Bootloader source file rebuilds bootloader" - take_build_snapshot - touch ${IDF_PATH}/components/bootloader/src/main/bootloader_start.c - make bootloader || failure "Failed to partial build bootloader" - assert_rebuilt ${BOOTLOADER_BINS} bootloader/main/bootloader_start.o - assert_not_rebuilt ${APP_BINS} partitions_singleapp.bin + print_status "Bootloader source file rebuilds bootloader" + take_build_snapshot + touch ${IDF_PATH}/components/bootloader/src/main/bootloader_start.c + make bootloader || failure "Failed to partial build bootloader" + assert_rebuilt ${BOOTLOADER_BINS} bootloader/main/bootloader_start.o + assert_not_rebuilt ${APP_BINS} partitions_singleapp.bin - print_status "Partition CSV file rebuilds partitions" - take_build_snapshot - touch ${IDF_PATH}/components/partition_table/partitions_singleapp.csv - make partition_table || failure "Failed to build partition table" - assert_rebuilt partitions_singleapp.bin - assert_not_rebuilt app-template.bin app-template.elf ${BOOTLOADER_BINS} + print_status "Partition CSV file rebuilds partitions" + take_build_snapshot + touch ${IDF_PATH}/components/partition_table/partitions_singleapp.csv + make partition_table || failure "Failed to build partition table" + assert_rebuilt partitions_singleapp.bin + assert_not_rebuilt app-template.bin app-template.elf ${BOOTLOADER_BINS} - print_status "Partial build doesn't compile anything by default" - take_build_snapshot - # verify no build files are refreshed by a partial make - ALL_BUILD_FILES=$(find ${BUILD} -type f | sed "s@${BUILD}/@@") - make || failure "Partial build failed" - assert_not_rebuilt ${ALL_BUILD_FILES} + print_status "Partial build doesn't compile anything by default" + take_build_snapshot + # verify no build files are refreshed by a partial make + ALL_BUILD_FILES=$(find ${BUILD} -type f | sed "s@${BUILD}/@@") + make || failure "Partial build failed" + assert_not_rebuilt ${ALL_BUILD_FILES} - print_status "Cleaning should remove all files from build" - make clean || failure "Failed to make clean" - ALL_BUILD_FILES=$(find ${BUILD} -type f) - if [ -n "${ALL_BUILD_FILES}" ]; then - failure "Files weren't cleaned: ${ALL_BUILD_FILES}" - fi + print_status "Cleaning should remove all files from build" + make clean || failure "Failed to make clean" + ALL_BUILD_FILES=$(find ${BUILD} -type f) + if [ -n "${ALL_BUILD_FILES}" ]; then + failure "Files weren't cleaned: ${ALL_BUILD_FILES}" + fi - print_status "Moving BUILD_DIR_BASE out of tree" - rm -rf --preserve-root ${BUILD}/* - OUTOFTREE_BUILD=${TESTDIR}/alt_build - make BUILD_DIR_BASE=${OUTOFTREE_BUILD} || failure "Failed to build with BUILD_DIR_BASE overriden" - NEW_BUILD_FILES=$(find ${OUTOFREE_BUILD} -type f) - if [ -z "${NEW_BUILD_FILES}" ]; then - failure "No files found in new build directory!" - fi - DEFAULT_BUILD_FILES=$(find ${BUILD} -mindepth 1) - if [ -n "${DEFAULT_BUILD_FILES}" ]; then - failure "Some files were incorrectly put into the default build directory: ${DEFAULT_BUILD_FILES}" - fi + print_status "Moving BUILD_DIR_BASE out of tree" + rm -rf --preserve-root ${BUILD}/* + OUTOFTREE_BUILD=${TESTDIR}/alt_build + make BUILD_DIR_BASE=${OUTOFTREE_BUILD} || failure "Failed to build with BUILD_DIR_BASE overriden" + NEW_BUILD_FILES=$(find ${OUTOFREE_BUILD} -type f) + if [ -z "${NEW_BUILD_FILES}" ]; then + failure "No files found in new build directory!" + fi + DEFAULT_BUILD_FILES=$(find ${BUILD} -mindepth 1) + if [ -n "${DEFAULT_BUILD_FILES}" ]; then + failure "Some files were incorrectly put into the default build directory: ${DEFAULT_BUILD_FILES}" + fi - print_status "BUILD_DIR_BASE inside default build directory" - rm -rf --preserve-root ${BUILD}/* - make BUILD_DIR_BASE=build/subdirectory || failure "Failed to build with BUILD_DIR_BASE as subdir" - NEW_BUILD_FILES=$(find ${BUILD}/subdirectory -type f) - if [ -z "${NEW_BUILD_FILES}" ]; then - failure "No files found in new build directory!" - fi + print_status "BUILD_DIR_BASE inside default build directory" + rm -rf --preserve-root ${BUILD}/* + make BUILD_DIR_BASE=build/subdirectory || failure "Failed to build with BUILD_DIR_BASE as subdir" + NEW_BUILD_FILES=$(find ${BUILD}/subdirectory -type f) + if [ -z "${NEW_BUILD_FILES}" ]; then + failure "No files found in new build directory!" + fi - print_status "Can still clean build if all text files are CRLFs" - make clean || failure "Unexpected failure to make clean" - find . -exec unix2dos {} \; # CRLFify template dir - # make a copy of esp-idf and CRLFify it - CRLF_ESPIDF=${TESTDIR}/esp-idf-crlf - mkdir -p ${CRLF_ESPIDF} - cp -rv ${IDF_PATH}/* ${CRLF_ESPIDF} - # don't CRLFify executable files, as Linux will fail to execute them - find ${CRLF_ESPIDF} -type f ! -perm 755 -exec unix2dos {} \; - make IDF_PATH=${CRLF_ESPIDF} || failure "Failed to build with CRLFs in source" - # do the same checks we do for the clean build - assert_built ${APP_BINS} ${BOOTLOADER_BINS} partitions_singleapp.bin - [ -f ${BUILD}/partition*.bin ] || failure "A partition table should have been built in CRLF mode" + print_status "Can still clean build if all text files are CRLFs" + make clean || failure "Unexpected failure to make clean" + find . -exec unix2dos {} \; # CRLFify template dir + # make a copy of esp-idf and CRLFify it + CRLF_ESPIDF=${TESTDIR}/esp-idf-crlf + mkdir -p ${CRLF_ESPIDF} + cp -rv ${IDF_PATH}/* ${CRLF_ESPIDF} + # don't CRLFify executable files, as Linux will fail to execute them + find ${CRLF_ESPIDF} -type f ! -perm 755 -exec unix2dos {} \; + make IDF_PATH=${CRLF_ESPIDF} || failure "Failed to build with CRLFs in source" + # do the same checks we do for the clean build + assert_built ${APP_BINS} ${BOOTLOADER_BINS} partitions_singleapp.bin + [ -f ${BUILD}/partition*.bin ] || failure "A partition table should have been built in CRLF mode" - print_status "All tests completed" - if [ -n "${FAILURES}" ]; then - echo "Some failures were detected:" - echo -e "${FAILURES}" - exit 1 - else - echo "Build tests passed." - fi + print_status "All tests completed" + if [ -n "${FAILURES}" ]; then + echo "Some failures were detected:" + echo -e "${FAILURES}" + exit 1 + else + echo "Build tests passed." + fi } function print_status() { - echo "******** $1" - STATUS="$1" + echo "******** $1" + STATUS="$1" } function failure() { - echo "!!!!!!!!!!!!!!!!!!!" - echo "FAILURE: $1" - echo "!!!!!!!!!!!!!!!!!!!" - FAILURES="${FAILURES}${STATUS} :: $1\n" + echo "!!!!!!!!!!!!!!!!!!!" + echo "FAILURE: $1" + echo "!!!!!!!!!!!!!!!!!!!" + FAILURES="${FAILURES}${STATUS} :: $1\n" } TESTDIR=${TMP}/build_system_tests_$$ @@ -164,62 +164,62 @@ BUILD=${TESTDIR}/template/build # copy all the build output to a snapshot directory function take_build_snapshot() { - rm -rf ${SNAPSHOT} - cp -ap ${TESTDIR}/template/build ${SNAPSHOT} + rm -rf ${SNAPSHOT} + cp -ap ${TESTDIR}/template/build ${SNAPSHOT} } # verify that all the arguments are present in the build output directory function assert_built() { - until [ -z "$1" ]; do - if [ ! -f "${BUILD}/$1" ]; then - failure "File $1 should be in the build output directory" - fi - shift - done + until [ -z "$1" ]; do + if [ ! -f "${BUILD}/$1" ]; then + failure "File $1 should be in the build output directory" + fi + shift + done } # Test if a file has been rebuilt. function file_was_rebuilt() { - # can't use [ a -ot b ] here as -ot only gives second resolution - # but stat -c %y seems to be microsecond at least for tmpfs, ext4.. - if [ "$(stat -c %y ${SNAPSHOT}/$1)" != "$(stat -c %y ${BUILD}/$1)" ]; then - return 0 - else - return 1 - fi + # can't use [ a -ot b ] here as -ot only gives second resolution + # but stat -c %y seems to be microsecond at least for tmpfs, ext4.. + if [ "$(stat -c %y ${SNAPSHOT}/$1)" != "$(stat -c %y ${BUILD}/$1)" ]; then + return 0 + else + return 1 + fi } # verify all the arguments passed in were rebuilt relative to the snapshot function assert_rebuilt() { - until [ -z "$1" ]; do - assert_built "$1" - if [ ! -f "${SNAPSHOT}/$1" ]; then - failure "File $1 should have been original build snapshot" - fi - if ! file_was_rebuilt "$1"; then - failure "File $1 should have been rebuilt" - fi - shift - done + until [ -z "$1" ]; do + assert_built "$1" + if [ ! -f "${SNAPSHOT}/$1" ]; then + failure "File $1 should have been original build snapshot" + fi + if ! file_was_rebuilt "$1"; then + failure "File $1 should have been rebuilt" + fi + shift + done } # verify all the arguments are in the build directory & snapshot, # but were not rebuilt function assert_not_rebuilt() { - until [ -z "$1" ]; do - assert_built "$1" - if [ ! -f "${SNAPSHOT}/$1" ]; then - failure "File $1 should be in snapshot build directory" - fi - if file_was_rebuilt "$1"; then - failure "File $1 should not have been rebuilt" - fi - shift - done + until [ -z "$1" ]; do + assert_built "$1" + if [ ! -f "${SNAPSHOT}/$1" ]; then + failure "File $1 should be in snapshot build directory" + fi + if file_was_rebuilt "$1"; then + failure "File $1 should not have been rebuilt" + fi + shift + done } cd ${TESTDIR} From 305bc9fd9c532286a23a281231c23e4673566a23 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 6 Oct 2016 18:29:34 +1100 Subject: [PATCH 008/149] build system: Run parallel builds without warnings Ref github #38 --- components/bootloader/Makefile.projbuild | 4 ++-- make/project.mk | 3 ++- make/project_config.mk | 5 ++--- make/test_build_system.sh | 17 +++++++++++++++-- 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/components/bootloader/Makefile.projbuild b/components/bootloader/Makefile.projbuild index 02135e1c6..91be3a6d6 100644 --- a/components/bootloader/Makefile.projbuild +++ b/components/bootloader/Makefile.projbuild @@ -16,8 +16,8 @@ BOOTLOADER_BIN=$(BOOTLOADER_BUILD_DIR)/bootloader.bin BOOTLOADER_SDKCONFIG=$(BOOTLOADER_BUILD_DIR)/sdkconfig # Custom recursive make for bootloader sub-project -BOOTLOADER_MAKE=$(MAKE) -C $(BOOTLOADER_COMPONENT_PATH)/src \ - MAKEFLAGS= V=$(V) SDKCONFIG=$(BOOTLOADER_SDKCONFIG) \ +BOOTLOADER_MAKE=+$(MAKE) -C $(BOOTLOADER_COMPONENT_PATH)/src \ + V=$(V) SDKCONFIG=$(BOOTLOADER_SDKCONFIG) \ BUILD_DIR_BASE=$(BOOTLOADER_BUILD_DIR) \ .PHONY: bootloader-clean bootloader-flash bootloader $(BOOTLOADER_BIN) diff --git a/make/project.mk b/make/project.mk index c4bf68db6..579788986 100644 --- a/make/project.mk +++ b/make/project.mk @@ -37,6 +37,7 @@ help: @echo "'make partition_table', etc, etc." # disable built-in make rules, makes debugging saner +MAKEFLAGS_OLD := $(MAKEFLAGS) MAKEFLAGS +=-rR # Figure out PROJECT_PATH if not set @@ -231,7 +232,7 @@ define GenerateComponentPhonyTarget # $(2) - target to generate (build, clean) .PHONY: $(notdir $(1))-$(2) $(notdir $(1))-$(2): | $(BUILD_DIR_BASE)/$(notdir $(1)) - @+$(MAKE) -C $(BUILD_DIR_BASE)/$(notdir $(1)) -f $(1)/component.mk COMPONENT_BUILD_DIR=$(BUILD_DIR_BASE)/$(notdir $(1)) $(2) + $(Q) +$(MAKE) -C $(BUILD_DIR_BASE)/$(notdir $(1)) -f $(1)/component.mk COMPONENT_BUILD_DIR=$(BUILD_DIR_BASE)/$(notdir $(1)) $(2) endef define GenerateComponentTargets diff --git a/make/project_config.mk b/make/project_config.mk index 00452dda4..7ca83ce5a 100644 --- a/make/project_config.mk +++ b/make/project_config.mk @@ -11,10 +11,9 @@ KCONFIG_TOOL_DIR=$(IDF_PATH)/tools/kconfig # unless it's overriden (happens for bootloader) SDKCONFIG ?= $(PROJECT_PATH)/sdkconfig -# clear MAKEFLAGS as the menuconfig makefile uses implicit compile rules +# reset MAKEFLAGS as the menuconfig makefile uses implicit compile rules $(KCONFIG_TOOL_DIR)/mconf $(KCONFIG_TOOL_DIR)/conf: - MAKEFLAGS="" \ - CC=$(HOSTCC) LD=$(HOSTLD) \ + MAKEFLAGS=$(ORIGINAL_MAKEFLAGS) CC=$(HOSTCC) LD=$(HOSTLD) \ $(MAKE) -C $(KCONFIG_TOOL_DIR) # use a wrapper environment for where we run Kconfig tools diff --git a/make/test_build_system.sh b/make/test_build_system.sh index f72158838..c6e54d922 100755 --- a/make/test_build_system.sh +++ b/make/test_build_system.sh @@ -92,7 +92,7 @@ function run_tests() fi print_status "Moving BUILD_DIR_BASE out of tree" - rm -rf --preserve-root ${BUILD}/* + clean_build_dir OUTOFTREE_BUILD=${TESTDIR}/alt_build make BUILD_DIR_BASE=${OUTOFTREE_BUILD} || failure "Failed to build with BUILD_DIR_BASE overriden" NEW_BUILD_FILES=$(find ${OUTOFREE_BUILD} -type f) @@ -105,13 +105,20 @@ function run_tests() fi print_status "BUILD_DIR_BASE inside default build directory" - rm -rf --preserve-root ${BUILD}/* + clean_build_dir make BUILD_DIR_BASE=build/subdirectory || failure "Failed to build with BUILD_DIR_BASE as subdir" NEW_BUILD_FILES=$(find ${BUILD}/subdirectory -type f) if [ -z "${NEW_BUILD_FILES}" ]; then failure "No files found in new build directory!" fi + print_status "Parallel builds should work OK" + clean_build_dir + (make -j5 2>&1 | tee ${TESTDIR}/parallel_build.log) || failure "Failed to build in parallel" + if grep -q "warning: jobserver unavailable" ${TESTDIR}/parallel_build.log; then + failure "Parallel build prints 'warning: jobserver unavailable' errors" + fi + print_status "Can still clean build if all text files are CRLFs" make clean || failure "Unexpected failure to make clean" find . -exec unix2dos {} \; # CRLFify template dir @@ -222,5 +229,11 @@ function assert_not_rebuilt() done } +# do a "clean" that doesn't depend on 'make clean' +function clean_build_dir() +{ + rm -rf --preserve-root ${BUILD}/* +} + cd ${TESTDIR} run_tests From 924fea7cc0f78832bfdd711d285c8d756912e336 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Tue, 11 Oct 2016 00:05:15 -0600 Subject: [PATCH 009/149] freertos: fix setting xCoreID for new task --- components/freertos/tasks.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/freertos/tasks.c b/components/freertos/tasks.c index 63a659b5d..7fb40e771 100644 --- a/components/freertos/tasks.c +++ b/components/freertos/tasks.c @@ -629,7 +629,7 @@ static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB, TaskFunction_t pxTaskCode configASSERT( puxStackBuffer != NULL ); configASSERT( pxTaskBuffer != NULL ); - configASSERT( (xCoreID>=0 && xCoreID=0 && xCoreIDpxStack = ( StackType_t * ) puxStackBuffer; + pxNewTCB->xCoreID = xCoreID; #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) { From 89097d5f113bc670de96380bb686fe709c8e068d Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Wed, 12 Oct 2016 17:46:15 +0800 Subject: [PATCH 010/149] Add xCoreID arg to prvInitialiseNewTask code; initialize pvThreadLocalStoragePointersDelCallback array to NULL --- components/freertos/tasks.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/components/freertos/tasks.c b/components/freertos/tasks.c index 7fb40e771..b93841842 100644 --- a/components/freertos/tasks.c +++ b/components/freertos/tasks.c @@ -601,7 +601,7 @@ static void prvInitialiseNewTask( TaskFunction_t pxTaskCode, UBaseType_t uxPriority, TaskHandle_t * const pxCreatedTask, TCB_t *pxNewTCB, - const MemoryRegion_t * const xRegions ) PRIVILEGED_FUNCTION; /*lint !e971 Unqualified char types are allowed for strings and single characters only. */ + const MemoryRegion_t * const xRegions, const BaseType_t xCoreID) PRIVILEGED_FUNCTION; /*lint !e971 Unqualified char types are allowed for strings and single characters only. */ /* * Called after a new task has been created and initialised to place the task @@ -637,7 +637,6 @@ static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB, TaskFunction_t pxTaskCode function - use them. */ pxNewTCB = ( TCB_t * ) pxTaskBuffer; /*lint !e740 Unusual cast is ok as the structures are designed to have the same alignment, and the size is checked by an assert. */ pxNewTCB->pxStack = ( StackType_t * ) puxStackBuffer; - pxNewTCB->xCoreID = xCoreID; #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) { @@ -647,7 +646,7 @@ static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB, TaskFunction_t pxTaskCode } #endif /* configSUPPORT_DYNAMIC_ALLOCATION */ - prvInitialiseNewTask( pxTaskCode, pcName, ulStackDepth, pvParameters, uxPriority, &xReturn, pxNewTCB, NULL ); + prvInitialiseNewTask( pxTaskCode, pcName, ulStackDepth, pvParameters, uxPriority, &xReturn, pxNewTCB, NULL, xCoreID ); prvAddNewTaskToReadyList( pxNewTCB, pxTaskCode, xCoreID ); } else @@ -693,7 +692,8 @@ static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB, TaskFunction_t pxTaskCode pxTaskDefinition->pvParameters, pxTaskDefinition->uxPriority, pxCreatedTask, pxNewTCB, - pxTaskDefinition->xRegions ); + pxTaskDefinition->xRegions, + tskNO_AFFINITY ); prvAddNewTaskToReadyList( pxNewTCB, pxTaskDefinition->pvTaskCode, tskNO_AFFINITY ); xReturn = pdPASS; @@ -785,7 +785,7 @@ static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB, TaskFunction_t pxTaskCode } #endif /* configSUPPORT_STATIC_ALLOCATION */ - prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL ); + prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL, tskNO_AFFINITY ); prvAddNewTaskToReadyList( pxNewTCB, pxTaskCode, xCoreID ); xReturn = pdPASS; } @@ -807,7 +807,7 @@ static void prvInitialiseNewTask( TaskFunction_t pxTaskCode, UBaseType_t uxPriority, TaskHandle_t * const pxCreatedTask, TCB_t *pxNewTCB, - const MemoryRegion_t * const xRegions ) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */ + const MemoryRegion_t * const xRegions, const BaseType_t xCoreID ) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */ { StackType_t *pxTopOfStack; UBaseType_t x; @@ -893,6 +893,7 @@ UBaseType_t x; } pxNewTCB->uxPriority = uxPriority; + pxNewTCB->xCoreID = xCoreID; #if ( configUSE_MUTEXES == 1 ) { pxNewTCB->uxBasePriority = uxPriority; @@ -945,6 +946,9 @@ UBaseType_t x; for( x = 0; x < ( UBaseType_t ) configNUM_THREAD_LOCAL_STORAGE_POINTERS; x++ ) { pxNewTCB->pvThreadLocalStoragePointers[ x ] = NULL; + #if ( configTHREAD_LOCAL_STORAGE_DELETE_CALLBACKS == 1) + pxNewTCB->pvThreadLocalStoragePointersDelCallback[ x ] = NULL; + #endif } } #endif From 03bd5b6d220c86da983736dbffac4d52e66ed502 Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Wed, 12 Oct 2016 18:17:58 +0800 Subject: [PATCH 011/149] Fix offset of coreid in tasktcb --- components/freertos/xtensa_vectors.S | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/freertos/xtensa_vectors.S b/components/freertos/xtensa_vectors.S index f0d874a59..0e83cb085 100644 --- a/components/freertos/xtensa_vectors.S +++ b/components/freertos/xtensa_vectors.S @@ -96,7 +96,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Define for workaround: pin no-cpu-affinity tasks to a cpu when fpu is used. Please change this when the tcb structure is changed */ -#define TASKTCB_XCOREID_OFFSET (0x3C+configMAX_TASK_NAME_LEN+3)&~3 +#define TASKTCB_XCOREID_OFFSET (0x38+configMAX_TASK_NAME_LEN+3)&~3 .extern pxCurrentTCB /* Enable stack backtrace across exception/interrupt - see below */ From 20b508e62ed1606c5e515b30902bc3ea106074bd Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 13 Oct 2016 11:01:30 +1100 Subject: [PATCH 012/149] build system tests: Verify bootloader doesn't build any files outside build/bootloader & config See TW7505. Looks like bug was fixed via prior refactors, but adding the test ensures it will stay fixed. --- make/test_build_system.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/make/test_build_system.sh b/make/test_build_system.sh index 5be1504e3..86be1f82e 100755 --- a/make/test_build_system.sh +++ b/make/test_build_system.sh @@ -80,6 +80,13 @@ function run_tests() failure "Files weren't cleaned: ${ALL_BUILD_FILES}" fi + print_status "Bootloader build shouldn't leave build output anywhere else" + rm -rf --preserve-root ${BUILD} + make bootloader + # find wizardry: find any file not named sdkconfig.h that + # isn't in the "bootloader" or "config" directories + find ${BUILD} -type d \( -name bootloader -o -name config \) -prune , -type f ! -name sdkconfig.h || failure "Bootloader built files outside the bootloader or config directories" + print_status "Can still clean build if all text files are CRLFs" make clean find . -exec unix2dos {} \; # CRLFify template dir From 72712c00a760bd43620d3619c4f19157f33e2783 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Fri, 14 Oct 2016 21:24:58 +0800 Subject: [PATCH 013/149] freertos: forward task affinity argument to prvInitializeNewTask --- components/freertos/tasks.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/freertos/tasks.c b/components/freertos/tasks.c index b93841842..0e2bf6e8e 100644 --- a/components/freertos/tasks.c +++ b/components/freertos/tasks.c @@ -785,7 +785,7 @@ static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB, TaskFunction_t pxTaskCode } #endif /* configSUPPORT_STATIC_ALLOCATION */ - prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL, tskNO_AFFINITY ); + prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL, xCoreID ); prvAddNewTaskToReadyList( pxNewTCB, pxTaskCode, xCoreID ); xReturn = pdPASS; } From 4370794d07847c3253fe305c0cd4a30882b9c777 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Fri, 14 Oct 2016 21:52:43 +0800 Subject: [PATCH 014/149] wifi libraries: update to match new FreeRTOS header files --- components/esp32/lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp32/lib b/components/esp32/lib index a1e5f8b95..9c71f406d 160000 --- a/components/esp32/lib +++ b/components/esp32/lib @@ -1 +1 @@ -Subproject commit a1e5f8b953c7934677ba7a6ed0a6dd2da0e6bd0f +Subproject commit 9c71f406de7be4774e1049b247c11a1983e6dfaa From 0aab006bb799242bad310cd9745678fd099577ff Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Mon, 17 Oct 2016 12:18:17 +0800 Subject: [PATCH 015/149] Add Trax-support to esp-idf --- components/esp32/Kconfig | 14 +++- components/esp32/cpu_start.c | 14 ++++ components/esp32/heap_alloc_caps.c | 4 + components/freertos/tasks.c | 5 +- components/xtensa-debug-module/component.mk | 5 ++ components/xtensa-debug-module/eri.c | 19 +++++ components/xtensa-debug-module/include/eri.h | 31 ++++++++ components/xtensa-debug-module/include/trax.h | 62 +++++++++++++++ .../include/xtensa-debug-module.h | 75 +++++++++++++++++++ components/xtensa-debug-module/trax.c | 64 ++++++++++++++++ 10 files changed, 289 insertions(+), 4 deletions(-) create mode 100755 components/xtensa-debug-module/component.mk create mode 100644 components/xtensa-debug-module/eri.c create mode 100644 components/xtensa-debug-module/include/eri.h create mode 100644 components/xtensa-debug-module/include/trax.h create mode 100644 components/xtensa-debug-module/include/xtensa-debug-module.h create mode 100644 components/xtensa-debug-module/trax.c diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index 535df23eb..4aacd937a 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -63,10 +63,22 @@ config MEMMAP_TRACEMEM of memory that can't be used for general purposes anymore. Disable this if you do not know what this is. +config MEMMAP_TRACEMEM_TWOBANKS + bool "Reserve memory for tracing both pro as well as app cpu execution" + default "n" + depends on MEMMAP_TRACEMEM && MEMMAP_SMP + help + The ESP32 contains a feature which allows you to trace the execution path the processor + has taken through the program. This is stored in a chunk of 32K (16K for single-processor) + of memory that can't be used for general purposes anymore. Disable this if you do not know + what this is. + + # Memory to reverse for trace, used in linker script config TRACEMEM_RESERVE_DRAM hex - default 0x8000 if MEMMAP_TRACEMEM + default 0x8000 if MEMMAP_TRACEMEM && MEMMAP_TRACEMEM_TWOBANKS + default 0x4000 if MEMMAP_TRACEMEM && !MEMMAP_TRACEMEM_TWOBANKS default 0x0 config MEMMAP_SPISRAM diff --git a/components/esp32/cpu_start.c b/components/esp32/cpu_start.c index 7b2ccdc60..5c7a411c8 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -43,6 +43,8 @@ #include "esp_ipc.h" #include "esp_log.h" +#include "trax.h" + void start_cpu0(void) __attribute__((weak, alias("start_cpu0_default"))); void start_cpu0_default(void) IRAM_ATTR; #if !CONFIG_FREERTOS_UNICORE @@ -131,6 +133,15 @@ void IRAM_ATTR call_start_cpu1() void start_cpu0_default(void) { +//Enable trace memory and immediately start trace. +#if CONFIG_MEMMAP_TRACEMEM +#if CONFIG_MEMMAP_TRACEMEM_TWOBANKS + trax_enable(TRAX_ENA_PRO_APP); +#else + trax_enable(TRAX_ENA_PRO); +#endif + trax_start_trace(TRAX_DOWNCOUNT_WORDS); +#endif esp_set_cpu_freq(); // set CPU frequency configured in menuconfig uart_div_modify(0, (APB_CLK_FREQ << 4) / 115200); ets_setup_syscalls(); @@ -147,6 +158,9 @@ void start_cpu0_default(void) #if !CONFIG_FREERTOS_UNICORE void start_cpu1_default(void) { +#if CONFIG_MEMMAP_TRACEMEM_TWOBANKS + trax_start_trace(TRAX_DOWNCOUNT_WORDS); +#endif // Wait for FreeRTOS initialization to finish on PRO CPU while (port_xSchedulerRunning[0] == 0) { ; diff --git a/components/esp32/heap_alloc_caps.c b/components/esp32/heap_alloc_caps.c index 46b1125cc..04e2dc8c8 100644 --- a/components/esp32/heap_alloc_caps.c +++ b/components/esp32/heap_alloc_caps.c @@ -186,7 +186,11 @@ void heap_alloc_caps_init() { #endif #if CONFIG_MEMMAP_TRACEMEM +#if CONFIG_MEMMAP_TRACEMEM_TWOBANKS disable_mem_region((void*)0x3fff8000, (void*)0x40000000); //knock out trace mem region +#else + disable_mem_region((void*)0x3fff8000, (void*)0x3fffc000); //knock out trace mem region +#endif #endif #if 0 diff --git a/components/freertos/tasks.c b/components/freertos/tasks.c index 3cde3bf13..95e7811dd 100644 --- a/components/freertos/tasks.c +++ b/components/freertos/tasks.c @@ -3755,9 +3755,8 @@ In fact, nothing below this line has/is. /* Gotcha (which seems to be deliberate in FreeRTOS, according to http://www.freertos.org/FreeRTOS_Support_Forum_Archive/December_2012/freertos_PIC32_Bug_-_vTaskEnterCritical_6400806.html -) is that calling vTaskEnterCritical followed by vTaskExitCritical will leave the interrupts DISABLED! Re-enabling the -scheduler will re-enable the interrupts instead. */ - +) is that calling vTaskEnterCritical followed by vTaskExitCritical will leave the interrupts DISABLED when the scheduler +is not running. Re-enabling the scheduler will re-enable the interrupts instead. */ #if ( portCRITICAL_NESTING_IN_TCB == 1 ) diff --git a/components/xtensa-debug-module/component.mk b/components/xtensa-debug-module/component.mk new file mode 100755 index 000000000..a57ae0b12 --- /dev/null +++ b/components/xtensa-debug-module/component.mk @@ -0,0 +1,5 @@ +# +# Component Makefile +# + +include $(IDF_PATH)/make/component_common.mk diff --git a/components/xtensa-debug-module/eri.c b/components/xtensa-debug-module/eri.c new file mode 100644 index 000000000..e2c7e41eb --- /dev/null +++ b/components/xtensa-debug-module/eri.c @@ -0,0 +1,19 @@ +#include +#include "eri.h" + +uint32_t eri_read(int addr) { + uint32_t ret; + asm( + "RER %0,%1" + :"=r"(ret):"r"(addr) + ); + return ret; +} + +void eri_write(int addr, uint32_t data) { + asm volatile ( + "WER %0,%1" + ::"r"(data),"r"(addr) + ); +} + diff --git a/components/xtensa-debug-module/include/eri.h b/components/xtensa-debug-module/include/eri.h new file mode 100644 index 000000000..33e4dd091 --- /dev/null +++ b/components/xtensa-debug-module/include/eri.h @@ -0,0 +1,31 @@ +#ifndef ERI_H +#define ERI_H + +#include + +/* + The ERI is a bus internal to each Xtensa core. It connects, amongst others, to the debug interface, where it + allows reading/writing the same registers as available over JTAG. +*/ + + +/** + * @brief Perform an ERI read + * @param addr : ERI register to read from + * + * @return Value read + */ +uint32_t eri_read(int addr); + + +/** + * @brief Perform an ERI write + * @param addr : ERI register to write to + * @param data : Value to write + * + * @return Value read + */ +void eri_write(int addr, uint32_t data); + + +#endif \ No newline at end of file diff --git a/components/xtensa-debug-module/include/trax.h b/components/xtensa-debug-module/include/trax.h new file mode 100644 index 000000000..c1b3608eb --- /dev/null +++ b/components/xtensa-debug-module/include/trax.h @@ -0,0 +1,62 @@ +#include "soc/dport_reg.h" +#include "sdkconfig.h" +#include "esp_err.h" +#include "eri.h" +#include "xtensa-debug-module.h" + + +typedef enum { + TRAX_DOWNCOUNT_WORDS, + TRAX_DOWNCOUNT_INSTRUCTIONS +} trax_downcount_unit_t; + +typedef enum { + TRAX_ENA_NONE = 0, + TRAX_ENA_PRO, + TRAX_ENA_APP, + TRAX_ENA_PRO_APP, + TRAX_ENA_PRO_APP_SWAP +} trax_ena_select_t; + + +/** + * @brief Enable the trax memory blocks to be used as Trax memory. + * + * @param pro_cpu_enable : true if Trax needs to be enabled for the pro CPU + * @param app_cpu_enable : true if Trax needs to be enabled for the pro CPU + * @param swap_regions : Normally, the pro CPU writes to Trax mem block 0 while + * the app cpu writes to block 1. Setting this to true + * inverts this. + * + * @return esp_err_t. Fails with ESP_ERR_NO_MEM if Trax enable is requested for 2 CPUs + * but memmap only has room for 1, or if Trax memmap is disabled + * entirely. + */ +int trax_enable(trax_ena_select_t ena); + +/** + * @brief Start a Trax trace on the current CPU + * + * @param units_until_stop : Set the units of the delay that gets passed to + * trax_trigger_traceend_after_delay. One of TRAX_DOWNCOUNT_WORDS + * or TRAX_DOWNCOUNT_INSTRUCTIONS. + * + * @return esp_err_t. Fails with ESP_ERR_NO_MEM if Trax is disabled. + */ +int trax_start_trace(trax_downcount_unit_t units_until_stop); + + +/** + * @brief Trigger a Trax trace stop after the indicated delay. If this is called + * before and the previous delay hasn't ended yet, this will overwrite + * that delay with the new value. The delay will always start at the time + * the function is called. + * + * @param delay : The delay to stop the trace in, in the unit indicated to + * trax_start_trace. Note: the trace memory has 4K words available. + * + * @return esp_err_t + */ +int trax_trigger_traceend_after_delay(int delay); + + diff --git a/components/xtensa-debug-module/include/xtensa-debug-module.h b/components/xtensa-debug-module/include/xtensa-debug-module.h new file mode 100644 index 000000000..61b218253 --- /dev/null +++ b/components/xtensa-debug-module/include/xtensa-debug-module.h @@ -0,0 +1,75 @@ +#ifndef XTENSA_DEBUG_MODULE_H +#define XTENSA_DEBUG_MODULE_H + +/* +ERI registers / OCD offsets and field definitions +*/ + +#define ERI_DEBUG_OFFSET 0x100000 + +#define ERI_TRAX_OFFSET (ERI_DEBUG_OFFSET+0) +#define ERI_PERFMON_OFFSET (ERI_DEBUG_OFFSET+0x1000) +#define ERI_OCDREG_OFFSET (ERI_DEBUG_OFFSET+0x2000) +#define ERI_MISCDBG_OFFSET (ERI_DEBUG_OFFSET+0x3000) +#define ERI_CORESIGHT_OFFSET (ERI_DEBUG_OFFSET+0x3F00) + +#define ERI_TRAX_TRAXID (ERI_TRAX_OFFSET+0x00) +#define ERI_TRAX_TRAXCTRL (ERI_TRAX_OFFSET+0x04) +#define ERI_TRAX_TRAXSTAT (ERI_TRAX_OFFSET+0x08) +#define ERI_TRAX_TRAXDATA (ERI_TRAX_OFFSET+0x0C) +#define ERI_TRAX_TRAXADDR (ERI_TRAX_OFFSET+0x10) +#define ERI_TRAX_TRIGGERPC (ERI_TRAX_OFFSET+0x14) +#define ERI_TRAX_PCMATCHCTRL (ERI_TRAX_OFFSET+0x18) +#define ERI_TRAX_DELAYCNT (ERI_TRAX_OFFSET+0x1C) +#define ERI_TRAX_MEMADDRSTART (ERI_TRAX_OFFSET+0x20) +#define ERI_TRAX_MEMADDREND (ERI_TRAX_OFFSET+0x24) + +#define TRAXCTRL_TREN (1<<0) //Trace enable. Tracing starts on 0->1 +#define TRAXCTRL_TRSTP (1<<1) //Trace Stop. Make 1 to stop trace. +#define TRAXCTRL_PCMEN (1<<2) //PC match enable +#define TRAXCTRL_PTIEN (1<<4) //Processor-trigger enable +#define TRAXCTRL_CTIEN (1<<5) //Cross-trigger enable +#define TRAXCTRL_TMEN (1<<7) //Tracemem Enable. Always set. +#define TRAXCTRL_CNTU (1<<9) //Post-stop-trigger countdown units; selects when DelayCount-- happens. + //0 - every 32-bit word written to tracemem, 1 - every cpu instruction +#define TRAXCTRL_TSEN (1<<11) //Undocumented/deprecated? +#define TRAXCTRL_SMPER_SHIFT 12 //Send sync every 2^(9-smper) messages. 7=reserved, 0=no sync msg +#define TRAXCTRL_SMPER_MASK 0x7 //Synchronization message period +#define TRAXCTRL_PTOWT (1<<16) //Processor Trigger Out (OCD halt) enabled when stop triggered +#define TRAXCTRL_PTOWS (1<<17) //Processor Trigger Out (OCD halt) enabled when trace stop completes +#define TRAXCTRL_CTOWT (1<<20) //Cross-trigger Out enabled when stop triggered +#define TRAXCTRL_CTOWS (1<<21) //Cross-trigger Out enabled when trace stop completes +#define TRAXCTRL_ITCTO (1<<22) //Integration mode: cross-trigger output +#define TRAXCTRL_ITCTIA (1<<23) //Integration mode: cross-trigger ack +#define TRAXCTRL_ITATV (1<<24) //replaces ATID when in integration mode: ATVALID output +#define TRAXCTRL_ATID_MASK 0x7F //ARB source ID +#define TRAXCTRL_ATID_SHIFT 24 +#define TRAXCTRL_ATEN (1<<31) //ATB interface enable + +#define TRAXSTAT_TRACT (1<<0) //Trace active flag. +#define TRAXSTAT_TRIG (1<<1) //Trace stop trigger. Clears on TREN 1->0 +#define TRAXSTAT_PCMTG (1<<2) //Stop trigger caused by PC match. Clears on TREN 1->0 +#define TRAXSTAT_PJTR (1<<3) //JTAG transaction result. 1=err in preceding jtag transaction. +#define TRAXSTAT_PTITG (1<<4) //Stop trigger caused by Processor Trigger Input. Clears on TREN 1->0 +#define TRAXSTAT_CTITG (1<<5) //Stop trigger caused by Cross-Trigger Input. Clears on TREN 1->0 +#define TRAXSTAT_MEMSZ_SHIFT 8 //Traceram size inducator. Usable trace ram is 2^MEMSZ bytes. +#define TRAXSTAT_MEMSZ_MASK 0x1F +#define TRAXSTAT_PTO (1<<16) //Processor Trigger Output: current value +#define TRAXSTAT_CTO (1<<17) //Cross-Trigger Output: current value +#define TRAXSTAT_ITCTOA (1<<22) //Cross-Trigger Out Ack: current value +#define TRAXSTAT_ITCTI (1<<23) //Cross-Trigger Input: current value +#define TRAXSTAT_ITATR (1<<24) //ATREADY Input: current value + +#define TRAXADDR_TADDR_SHIFT 0 //Trax memory address, in 32-bit words. +#define TRAXADDR_TADDR_MASK 0x1FFFFF //Actually is only as big as the trace buffer size max addr. +#define TRAXADDR_TWRAP_SHIFT 21 //Amount of times TADDR has overflown +#define TRAXADDR_TWRAP_MASK 0x3FF +#define TRAXADDR_TWSAT (1<<31) //1 if TWRAP has overflown, clear by disabling tren. + +#define PCMATCHCTRL_PCML_SHIFT 0 //Amount of lower bits to ignore in pc trigger register +#define PCMATCHCTRL_PCML_MASK 0x1F +#define PCMATCHCTRL_PCMS (1<<31) //PC Match Sense, 0 - match when procs PC is in-range, 1 - match when + //out-of-range + + +#endif \ No newline at end of file diff --git a/components/xtensa-debug-module/trax.c b/components/xtensa-debug-module/trax.c new file mode 100644 index 000000000..18c260a97 --- /dev/null +++ b/components/xtensa-debug-module/trax.c @@ -0,0 +1,64 @@ +#include +#include "soc/dport_reg.h" +#include "sdkconfig.h" +#include "esp_err.h" +#include "eri.h" +#include "xtensa-debug-module.h" +#include "trax.h" +#include "esp_log.h" + +#define TRACEMEM_MUX_PROBLK0_APPBLK1 0 +#define TRACEMEM_MUX_BLK0_ONLY 1 +#define TRACEMEM_MUX_BLK1_ONLY 2 +#define TRACEMEM_MUX_PROBLK1_APPBLK0 3 + +static const char* TAG = "log"; + +int trax_enable(trax_ena_select_t which) { +#if !CONFIG_MEMMAP_TRACEMEM + return ESP_ERR_NO_MEM; +#endif +#if !CONFIG_MEMMAP_TRACEMEM_TWOBANKS + if (which == TRAX_ENA_PRO_APP || which == TRAX_ENA_PRO_APP_SWAP) return ESP_ERR_NO_MEM; +#endif + if (which == TRAX_ENA_PRO_APP || which == TRAX_ENA_PRO_APP_SWAP) { + WRITE_PERI_REG(DPORT_TRACEMEM_MUX_MODE_REG, (which == TRAX_ENA_PRO_APP_SWAP)?TRACEMEM_MUX_PROBLK1_APPBLK0:TRACEMEM_MUX_PROBLK0_APPBLK1); + } else { + WRITE_PERI_REG(DPORT_TRACEMEM_MUX_MODE_REG, TRACEMEM_MUX_BLK0_ONLY); + } + WRITE_PERI_REG(DPORT_PRO_TRACEMEM_ENA_REG, (which == TRAX_ENA_PRO_APP || which == TRAX_ENA_PRO_APP_SWAP || which == TRAX_ENA_PRO)); + WRITE_PERI_REG(DPORT_APP_TRACEMEM_ENA_REG, (which == TRAX_ENA_PRO_APP || which == TRAX_ENA_PRO_APP_SWAP || which == TRAX_ENA_APP)); + return ESP_OK; +} + + +int trax_start_trace(trax_downcount_unit_t units_until_stop) { +#if !CONFIG_MEMMAP_TRACEMEM + return ESP_ERR_NO_MEM; +#endif + uint32_t v; + if (eri_read(ERI_TRAX_TRAXSTAT)&TRAXSTAT_TRACT) { + ESP_LOGI(TAG, "Stopping active trace first."); + //Trace is active. Stop trace. + eri_write(ERI_TRAX_DELAYCNT, 0); + eri_write(ERI_TRAX_TRAXCTRL, eri_read(ERI_TRAX_TRAXCTRL)|TRAXCTRL_TRSTP); + //ToDo: This will probably trigger a trace done interrupt. ToDo: Fix, but how? -JD + eri_write(ERI_TRAX_TRAXCTRL, 0); + } + eri_write(ERI_TRAX_PCMATCHCTRL, 31); //do not stop at any pc match + v=TRAXCTRL_TREN | TRAXCTRL_TMEN | TRAXCTRL_PTOWS | (1< Date: Mon, 17 Oct 2016 12:38:17 +0800 Subject: [PATCH 016/149] build system: add menuconfig choice for optimization level, reorganize C*FLAGS This change adds two options (Debug/Release) for optimization level. Debug enables -O0, release enables -Os and adds -DNDEBUG (which removes all assert() statements). Debugging symbols are kept in both cases, although we may add an option to strip output file if necessary. Also we used to define all common compiler flags in CPPFLAGS, and then appended them to CFLAGS/CXXFLAGS. It makes it impossible to add preprocessor macros to CPPFLAGS at component level (one has to use CFLAGS/CXXFLAGS instead). Some third party libraries are not compatible with this approach. Changed to the more common way of using these variables. --- Kconfig | 11 +++++++++++ components/log/log.c | 2 +- make/component_common.mk | 10 +++++----- make/project.mk | 35 ++++++++++++++++++++++++++++------- 4 files changed, 45 insertions(+), 13 deletions(-) diff --git a/Kconfig b/Kconfig index 11ea099de..936181a9c 100644 --- a/Kconfig +++ b/Kconfig @@ -23,6 +23,17 @@ endmenu source "$COMPONENT_KCONFIGS_PROJBUILD" +choice OPTIMIZATION_LEVEL + prompt "Optimization level" + default OPTIMIZATION_LEVEL_DEBUG + help + This option sets compiler optimization level. +config OPTIMIZATION_LEVEL_DEBUG + bool "Debug" +config OPTIMIZATION_LEVEL_RELEASE + bool "Release" +endchoice + menu "Component config" source "$COMPONENT_KCONFIGS" endmenu diff --git a/components/log/log.c b/components/log/log.c index aae12a773..a2b41d7e6 100644 --- a/components/log/log.c +++ b/components/log/log.c @@ -284,7 +284,7 @@ static inline void heap_swap(int i, int j) } #endif //BOOTLOADER_BUILD -inline IRAM_ATTR uint32_t esp_log_early_timestamp() +IRAM_ATTR uint32_t esp_log_early_timestamp() { return xthal_get_ccount() / (CPU_CLK_FREQ_ROM / 1000); } diff --git a/make/component_common.mk b/make/component_common.mk index ebad525a7..bf2eace01 100644 --- a/make/component_common.mk +++ b/make/component_common.mk @@ -91,15 +91,15 @@ define GenerateCompileTargets # $(1) - directory containing source files, relative to $(COMPONENT_PATH) $(1)/%.o: $$(COMPONENT_PATH)/$(1)/%.c | $(1) $$(summary) CC $$@ - $$(Q) $$(CC) $$(CFLAGS) $$(addprefix -I ,$$(COMPONENT_INCLUDES)) $$(addprefix -I ,$$(COMPONENT_EXTRA_INCLUDES)) -I$(1) -c $$< -o $$@ + $$(Q) $$(CC) $$(CFLAGS) $(CPPFLAGS) $$(addprefix -I ,$$(COMPONENT_INCLUDES)) $$(addprefix -I ,$$(COMPONENT_EXTRA_INCLUDES)) -I$(1) -c $$< -o $$@ $(1)/%.o: $$(COMPONENT_PATH)/$(1)/%.cpp | $(1) - $$(summary) CC $$@ - $$(Q) $$(CXX) $$(CXXFLAGS) $$(addprefix -I,$$(COMPONENT_INCLUDES)) $$(addprefix -I,$$(COMPONENT_EXTRA_INCLUDES)) -I$(1) -c $$< -o $$@ + $$(summary) CXX $$@ + $$(Q) $$(CXX) $$(CXXFLAGS) $(CPPFLAGS) $$(addprefix -I,$$(COMPONENT_INCLUDES)) $$(addprefix -I,$$(COMPONENT_EXTRA_INCLUDES)) -I$(1) -c $$< -o $$@ $(1)/%.o: $$(COMPONENT_PATH)/$(1)/%.S | $(1) - $$(summary) CC $$@ - $$(Q) $$(CC) $$(CFLAGS) $$(addprefix -I ,$$(COMPONENT_INCLUDES)) $$(addprefix -I ,$$(COMPONENT_EXTRA_INCLUDES)) -I$(1) -c $$< -o $$@ + $$(summary) AS $$@ + $$(Q) $$(CC) $$(CFLAGS) $(CPPFLAGS) $$(addprefix -I ,$$(COMPONENT_INCLUDES)) $$(addprefix -I ,$$(COMPONENT_EXTRA_INCLUDES)) -I$(1) -c $$< -o $$@ # CWD is build dir, create the build subdirectory if it doesn't exist $(1): diff --git a/make/project.mk b/make/project.mk index d91165cfd..49c349946 100644 --- a/make/project.mk +++ b/make/project.mk @@ -157,15 +157,36 @@ LDFLAGS ?= -nostdlib \ # If you need your component to add CFLAGS/etc globally for all source # files, set CFLAGS += in your component's Makefile.projbuild -# CPPFLAGS used by an compile pass that uses the C preprocessor -CPPFLAGS = -DESP_PLATFORM -Og -g3 -Wpointer-arith -Werror -Wno-error=unused-function -Wno-error=unused-but-set-variable \ - -Wno-error=unused-variable -Wall -ffunction-sections -fdata-sections -mlongcalls -nostdlib -MMD -MP +# CPPFLAGS used by C preprocessor +CPPFLAGS = -DESP_PLATFORM -# C flags use by C only -CFLAGS = $(CPPFLAGS) -std=gnu99 -g3 -fstrict-volatile-bitfields +# Warnings-related flags relevant both for C and C++ +COMMON_WARNING_FLAGS = -Wall -Werror \ + -Wno-error=unused-function \ + -Wno-error=unused-but-set-variable \ + -Wno-error=unused-variable -# CXXFLAGS uses by C++ only -CXXFLAGS = $(CPPFLAGS) -Og -std=gnu++11 -g3 -fno-exceptions -fstrict-volatile-bitfields -fno-rtti +# Flags which control code generation and dependency generation, both for C and C++ +COMMON_FLAGS = \ + -ffunction-sections -fdata-sections \ + -fstrict-volatile-bitfields \ + -mlongcalls \ + -nostdlib \ + -MMD -MP + +# Optimization flags are set based on menuconfig choice +ifneq ("$(CONFIG_OPTIMIZATION_LEVEL_RELEASE)","") +OPTMIZATION_FLAGS = -Os +CPPFLAGS += -DNDEBUG +else +OPTMIZATION_FLAGS = -O0 +endif + +# List of flags to pass to C compiler +CFLAGS = -ggdb -std=gnu99 $(strip $(OPTMIZATION_FLAGS) $(COMMON_FLAGS) $(COMMON_WARNING_FLAGS)) + +# List of flags to pass to C++ compiler +CXXFLAGS = -ggdb -std=gnu++11 -fno-exceptions -fno-rtti $(strip $(OPTMIZATION_FLAGS) $(COMMON_FLAGS) $(COMMON_WARNING_FLAGS)) export CFLAGS CPPFLAGS CXXFLAGS From 1cd572c7b982cd876d2f16958c1b2d75cf5f0ea5 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 17 Oct 2016 12:45:42 +0800 Subject: [PATCH 017/149] Add test for compiling in release mode, fix warnings and errors which appeared --- .gitlab-ci.yml | 6 ++++++ components/nghttp/library/nghttp2_session.c | 2 +- components/nvs_flash/src/nvs_page.cpp | 2 +- components/nvs_flash/src/nvs_pagemanager.cpp | 4 +++- components/nvs_flash/src/nvs_storage.cpp | 3 +-- 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index dd4049358..aff9bea8d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -37,6 +37,12 @@ build_template_app: # branch - git checkout ${CI_BUILD_REF_NAME} || echo "Using esp-idf-template default branch..." - make defconfig + # Test debug build (default) + - make all V=1 + # Now test release build + - make clean + - sed -i.bak -e's/CONFIG_OPTIMIZATION_LEVEL_DEBUG\=y/CONFIG_OPTIMIZATION_LEVEL_RELEASE=y/' sdkconfig + - make defconfig - make all V=1 diff --git a/components/nghttp/library/nghttp2_session.c b/components/nghttp/library/nghttp2_session.c index f93cd1729..0100dd32c 100644 --- a/components/nghttp/library/nghttp2_session.c +++ b/components/nghttp/library/nghttp2_session.c @@ -7177,7 +7177,7 @@ uint32_t nghttp2_session_get_remote_settings(nghttp2_session *session, return session->remote_settings.max_header_list_size; } - assert(0); + abort(); } static int nghttp2_session_upgrade_internal(nghttp2_session *session, diff --git a/components/nvs_flash/src/nvs_page.cpp b/components/nvs_flash/src/nvs_page.cpp index 4f6a198c6..f4fc5430c 100644 --- a/components/nvs_flash/src/nvs_page.cpp +++ b/components/nvs_flash/src/nvs_page.cpp @@ -769,7 +769,7 @@ void Page::invalidateCache() void Page::debugDump() const { - printf("state=%x addr=%x seq=%d\nfirstUsed=%d nextFree=%d used=%d erased=%d\n", mState, mBaseAddress, mSeqNumber, static_cast(mFirstUsedEntry), static_cast(mNextFreeEntry), mUsedEntryCount, mErasedEntryCount); + printf("state=%x addr=%x seq=%d\nfirstUsed=%d nextFree=%d used=%d erased=%d\n", (int) mState, mBaseAddress, mSeqNumber, static_cast(mFirstUsedEntry), static_cast(mNextFreeEntry), mUsedEntryCount, mErasedEntryCount); size_t skip = 0; for (size_t i = 0; i < ENTRY_COUNT; ++i) { printf("%3d: ", static_cast(i)); diff --git a/components/nvs_flash/src/nvs_pagemanager.cpp b/components/nvs_flash/src/nvs_pagemanager.cpp index 790ab7e19..f4d02a7d4 100644 --- a/components/nvs_flash/src/nvs_pagemanager.cpp +++ b/components/nvs_flash/src/nvs_pagemanager.cpp @@ -49,7 +49,7 @@ esp_err_t PageManager::load(uint32_t baseSector, uint32_t sectorCount) return activatePage(); } else { uint32_t lastSeqNo; - assert(mPageList.back().getSeqNumber(lastSeqNo) == ESP_OK); + ESP_ERROR_CHECK( mPageList.back().getSeqNumber(lastSeqNo) ); mSeqNumber = lastSeqNo + 1; } @@ -142,7 +142,9 @@ esp_err_t PageManager::requestNewPage() Page* newPage = &mPageList.back(); Page* erasedPage = maxErasedItemsPageIt; +#ifndef NDEBUG size_t usedEntries = erasedPage->getUsedEntryCount(); +#endif err = erasedPage->markFreeing(); if (err != ESP_OK) { return err; diff --git a/components/nvs_flash/src/nvs_storage.cpp b/components/nvs_flash/src/nvs_storage.cpp index 4a217ebe1..eb90cac5b 100644 --- a/components/nvs_flash/src/nvs_storage.cpp +++ b/components/nvs_flash/src/nvs_storage.cpp @@ -123,8 +123,7 @@ esp_err_t Storage::writeItem(uint8_t nsIndex, ItemType datatype, const char* key if (findPage) { if (findPage->state() == Page::PageState::UNINITIALIZED || findPage->state() == Page::PageState::INVALID) { - auto err = findItem(nsIndex, datatype, key, findPage, item); - assert(err == ESP_OK); + ESP_ERROR_CHECK( findItem(nsIndex, datatype, key, findPage, item) ); } err = findPage->eraseItem(nsIndex, datatype, key); if (err == ESP_ERR_FLASH_OP_FAIL) { From 34fa6a60a9812b4a91ac16535ad5ee86d5755a22 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 17 Oct 2016 13:47:13 +0800 Subject: [PATCH 018/149] build system: fix typo, move -ggdb to OPTIMIZATION_FLAGS --- make/project.mk | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/make/project.mk b/make/project.mk index 49c349946..9088eda85 100644 --- a/make/project.mk +++ b/make/project.mk @@ -176,17 +176,20 @@ COMMON_FLAGS = \ # Optimization flags are set based on menuconfig choice ifneq ("$(CONFIG_OPTIMIZATION_LEVEL_RELEASE)","") -OPTMIZATION_FLAGS = -Os +OPTIMIZATION_FLAGS = -Os CPPFLAGS += -DNDEBUG else -OPTMIZATION_FLAGS = -O0 +OPTIMIZATION_FLAGS = -O0 endif +# Enable generation of debugging symbols +OPTIMIZATION_FLAGS += -ggdb + # List of flags to pass to C compiler -CFLAGS = -ggdb -std=gnu99 $(strip $(OPTMIZATION_FLAGS) $(COMMON_FLAGS) $(COMMON_WARNING_FLAGS)) +CFLAGS = -std=gnu99 $(strip $(OPTIMIZATION_FLAGS) $(COMMON_FLAGS) $(COMMON_WARNING_FLAGS)) # List of flags to pass to C++ compiler -CXXFLAGS = -ggdb -std=gnu++11 -fno-exceptions -fno-rtti $(strip $(OPTMIZATION_FLAGS) $(COMMON_FLAGS) $(COMMON_WARNING_FLAGS)) +CXXFLAGS = -std=gnu++11 -fno-exceptions -fno-rtti $(strip $(OPTIMIZATION_FLAGS) $(COMMON_FLAGS) $(COMMON_WARNING_FLAGS)) export CFLAGS CPPFLAGS CXXFLAGS From eaace9846af25f95dbfe06fff7c309748a3b651b Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 17 Oct 2016 14:12:16 +0800 Subject: [PATCH 019/149] smartconfig: update to match new FreeRTOS header files --- components/esp32/lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp32/lib b/components/esp32/lib index 9c71f406d..aac7d416a 160000 --- a/components/esp32/lib +++ b/components/esp32/lib @@ -1 +1 @@ -Subproject commit 9c71f406de7be4774e1049b247c11a1983e6dfaa +Subproject commit aac7d416a702215f2501a6237caf034ec7e8e80a From 28d83e766a64b52b3e5a1ed6e64aa6fe6797c667 Mon Sep 17 00:00:00 2001 From: Yinling Date: Mon, 17 Oct 2016 17:03:54 +0800 Subject: [PATCH 020/149] fix bug that deploy when test failed: test report will be a single stage. The result of test report will be calculated from the result of all test jobs in test stage. So it will only deploy when all test job passed. --- .gitlab-ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index dd4049358..5718cc542 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,6 +1,7 @@ stages: - build - test + - test_report - deploy before_script: @@ -106,13 +107,12 @@ test_build_system: - ./make/test_build_system.sh test_report: - stage: deploy + stage: test_report only: - master - triggers tags: - test_report - allow_failure: true variables: LOG_PATH: "$CI_PROJECT_DIR/$CI_BUILD_REF" TEST_CASE_FILE_PATH: "$CI_PROJECT_DIR/components/idf_test" @@ -121,7 +121,7 @@ test_report: when: always paths: - $REPORT_PATH - expire_in: 6 mos + expire_in: 12 mos script: # clone test bench - git clone $GITLAB_SSH_SERVER/yinling/auto_test_script.git From 0403d43b194f5c7c77dd246aece7d936d6985fce Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Mon, 17 Oct 2016 18:09:15 +0800 Subject: [PATCH 021/149] Optimize xPortGetCoreID to 2-instruction inline assembly. --- components/freertos/include/freertos/portable.h | 3 +-- .../freertos/include/freertos/xtensa_context.h | 7 +------ components/freertos/panic.c | 1 - components/freertos/portasm.S | 12 ------------ 4 files changed, 2 insertions(+), 21 deletions(-) diff --git a/components/freertos/include/freertos/portable.h b/components/freertos/include/freertos/portable.h index 4030ca0c0..58e690366 100644 --- a/components/freertos/include/freertos/portable.h +++ b/components/freertos/include/freertos/portable.h @@ -192,8 +192,7 @@ void vPortEndScheduler( void ) PRIVILEGED_FUNCTION; #endif /* Multi-core: get current core ID */ -int xPortGetCoreID( void ); - +#define xPortGetCoreID() __extension__({int id; asm volatile("rsr.prid %0; extui %0,%0,13,1":"=r"(id)); id;}) #ifdef __cplusplus } diff --git a/components/freertos/include/freertos/xtensa_context.h b/components/freertos/include/freertos/xtensa_context.h index 3167c8472..b748a0d1a 100644 --- a/components/freertos/include/freertos/xtensa_context.h +++ b/components/freertos/include/freertos/xtensa_context.h @@ -322,12 +322,7 @@ STRUCT_END(XtSolFrame) #ifdef __ASSEMBLER__ .macro getcoreid reg rsr.prid \reg - bbci \reg,1,1f - movi \reg,1 - j 2f -1: - movi \reg,0 -2: + extui \reg,\reg,13,1 .endm #endif diff --git a/components/freertos/panic.c b/components/freertos/panic.c index 940086735..6a87679d1 100644 --- a/components/freertos/panic.c +++ b/components/freertos/panic.c @@ -76,7 +76,6 @@ inline static void panicPutHex(int a) { } inline static void panicPutDec(int a) { } #endif -int xPortGetCoreID(); void __attribute__((weak)) vApplicationStackOverflowHook( TaskHandle_t xTask, signed char *pcTaskName ) { panicPutStr("***ERROR*** A stack overflow in task "); diff --git a/components/freertos/portasm.S b/components/freertos/portasm.S index 65406b0b0..3a45b19a3 100644 --- a/components/freertos/portasm.S +++ b/components/freertos/portasm.S @@ -51,18 +51,6 @@ port_switch_flag: .text - - -/* C function to get proc ID.*/ - .global xPortGetCoreID - .type xPortGetCoreID,@function - .align 4 -xPortGetCoreID: - ENTRY(16) - getcoreid a2 - RET(16) - - /* ******************************************************************************* * _frxt_setup_switch From c03549e1170f76871a5f66cbc709371e38dbf9d2 Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Mon, 17 Oct 2016 18:30:13 +0800 Subject: [PATCH 022/149] Make uxPortCompareSet into a macro. 25uS -> 24uS --- components/freertos/port.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/components/freertos/port.c b/components/freertos/port.c index 6117a2804..f42cba936 100644 --- a/components/freertos/port.c +++ b/components/freertos/port.c @@ -263,17 +263,14 @@ void vPortAssertIfInISR() * *bitwise inverse* of the old mem if the mem wasn't written. This doesn't seem to happen on the * ESP32, though. (Would show up directly if it did because the magic wouldn't match.) */ -uint32_t uxPortCompareSet(volatile uint32_t *mux, uint32_t compare, uint32_t set) -{ - __asm__ __volatile__ ( - "WSR %2,SCOMPARE1 \n" //initialize SCOMPARE1 - "ISYNC \n" //wait sync - "S32C1I %0, %1, 0 \n" //store id into the lock, if the lock is the same as comparel. Otherwise, no write-access - :"=r"(set) \ - :"r"(mux), "r"(compare), "0"(set) \ - ); - return set; -} +#define uxPortCompareSet(mux, compare, set) \ + __asm__ __volatile__( \ + "WSR %2,SCOMPARE1 \n" \ + "ISYNC \n" \ + "S32C1I %0, %1, 0 \n" \ + :"=r"(*set) \ + :"r"(mux), "r"(compare), "0"(*set) \ + ); \ /* * For kernel use: Initialize a per-CPU mux. Mux will be initialized unlocked. @@ -310,7 +307,8 @@ void vPortCPUAcquireMutex(portMUX_TYPE *mux) { irqStatus=portENTER_CRITICAL_NESTED(); do { //Lock mux if it's currently unlocked - res=uxPortCompareSet(&mux->mux, portMUX_FREE_VAL, (xPortGetCoreID()<mux, portMUX_FREE_VAL, &res); //If it wasn't free and we're the owner of the lock, we are locking recursively. if ( (res != portMUX_FREE_VAL) && (((res&portMUX_VAL_MASK)>>portMUX_VAL_SHIFT) == xPortGetCoreID()) ) { //Mux was already locked by us. Just bump the recurse count by one. @@ -362,7 +360,8 @@ portBASE_TYPE vPortCPUReleaseMutex(portMUX_TYPE *mux) { if ( (mux->mux & portMUX_MAGIC_MASK) != portMUX_MAGIC_VAL ) ets_printf("ERROR: vPortCPUReleaseMutex: mux %p is uninitialized (0x%X)!\n", mux, mux->mux); #endif //Unlock mux if it's currently locked with a recurse count of 0 - res=uxPortCompareSet(&mux->mux, (xPortGetCoreID()<mux, (xPortGetCoreID()< Date: Mon, 17 Oct 2016 18:49:19 +0800 Subject: [PATCH 023/149] Detect success before errors in vPortCPUReleaseMutex. Shaves off another half uS. --- components/freertos/port.c | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/components/freertos/port.c b/components/freertos/port.c index f42cba936..ef72b1cb5 100644 --- a/components/freertos/port.c +++ b/components/freertos/port.c @@ -363,27 +363,30 @@ portBASE_TYPE vPortCPUReleaseMutex(portMUX_TYPE *mux) { res=portMUX_FREE_VAL; uxPortCompareSet(&mux->mux, (xPortGetCoreID()<>portMUX_VAL_SHIFT) == xPortGetCoreID() ) { + //Lock is valid, we can return safely. Just need to check if it's a recursive lock; if so we need to decrease the refcount. + if ( ((res&portMUX_CNT_MASK)>>portMUX_CNT_SHIFT)!=0) { + //We locked this, but the reccount isn't zero. Decrease refcount and continue. + recCnt=(res&portMUX_CNT_MASK)>>portMUX_CNT_SHIFT; + recCnt--; +#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG_RECURSIVE + ets_printf("Recursive unlock: recCnt=%d last locked %s line %d, curr %s line %d\n", recCnt, lastLockedFn, lastLockedLine, fnName, line); +#endif + mux->mux=portMUX_MAGIC_VAL|(recCnt<>portMUX_VAL_SHIFT) != xPortGetCoreID() ) { + } else { #ifdef CONFIG_FREERTOS_PORTMUX_DEBUG ets_printf("ERROR: vPortCPUReleaseMutex: mux %p wasn't locked by this core (%d) but by core %d (ret=%x, mux=%x).\n", mux, xPortGetCoreID(), ((res&portMUX_VAL_MASK)>>portMUX_VAL_SHIFT), res, mux->mux); ets_printf("Last non-recursive lock %s line %d\n", lastLockedFn, lastLockedLine); ets_printf("Called by %s line %d\n", fnName, line); #endif ret=pdFALSE; - } else if ( ((res&portMUX_CNT_MASK)>>portMUX_CNT_SHIFT)!=0) { - //We locked this, but the reccount isn't zero. Decrease refcount and continue. - recCnt=(res&portMUX_CNT_MASK)>>portMUX_CNT_SHIFT; - recCnt--; -#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG_RECURSIVE - ets_printf("Recursive unlock: recCnt=%d last locked %s line %d, curr %s line %d\n", recCnt, lastLockedFn, lastLockedLine, fnName, line); -#endif - mux->mux=portMUX_MAGIC_VAL|(recCnt< Date: Tue, 18 Oct 2016 10:51:08 +0800 Subject: [PATCH 024/149] Some more optimizations, mostly in involuntary task switches. Doesn not really help here, but might in other cases. --- components/freertos/portasm.S | 48 +++++++++++----------------- components/freertos/xtensa_vectors.S | 7 ++-- 2 files changed, 21 insertions(+), 34 deletions(-) diff --git a/components/freertos/portasm.S b/components/freertos/portasm.S index 3a45b19a3..ad65a103e 100644 --- a/components/freertos/portasm.S +++ b/components/freertos/portasm.S @@ -69,9 +69,8 @@ _frxt_setup_switch: ENTRY(16) getcoreid a3 - slli a3, a3, 2 movi a2, port_switch_flag - add a2, a2, a3 + addx4 a2, a3, a2 movi a3, 1 s32i a3, a2, 0 @@ -116,12 +115,11 @@ _frxt_int_enter: Manage nesting directly rather than call the generic IntEnter() (in windowed ABI we can't call a C function here anyway because PS.EXCM is still set). */ - getcoreid a3 - slli a4, a3, 2 /* a4 = cpuid * 4 */ + getcoreid a4 movi a2, port_xSchedulerRunning - add a2, a2, a4 + addx4 a2, a4, a2 movi a3, port_interruptNesting - add a3, a3, a4 + addx4 a3, a4, a3 l32i a2, a2, 0 /* a2 = port_xSchedulerRunning */ beqz a2, 1f /* scheduler not running, no tasks */ l32i a2, a3, 0 /* a2 = port_interruptNesting */ @@ -130,14 +128,13 @@ _frxt_int_enter: bnei a2, 1, .Lnested /* !=0 before incr, so nested */ movi a2, pxCurrentTCB - add a2, a2, a4 + addx4 a2, a4, a2 l32i a2, a2, 0 /* a2 = current TCB */ beqz a2, 1f s32i a1, a2, TOPOFSTACK_OFFS /* pxCurrentTCB->pxTopOfStack = SP */ movi a1, port_IntStackTop /* a1 = top of intr stack */ movi a2, configISR_STACK_SIZE - getcoreid a3 - mull a2, a3, a2 + mull a2, a4, a2 add a1, a1, a2 /* for current proc */ .Lnested: @@ -165,12 +162,11 @@ _frxt_int_enter: .align 4 _frxt_int_exit: - getcoreid a3 - slli a4, a3, 2 /* a4 is core * 4 */ + getcoreid a4 movi a2, port_xSchedulerRunning - add a2, a2, a4 + addx4 a2, a4, a2 movi a3, port_interruptNesting - add a3, a3, a4 + addx4 a3, a4, a3 rsil a0, XCHAL_EXCM_LEVEL /* lock out interrupts */ l32i a2, a2, 0 /* a2 = port_xSchedulerRunning */ beqz a2, .Lnoswitch /* scheduler not running, no tasks */ @@ -180,13 +176,13 @@ _frxt_int_exit: bnez a2, .Lnesting /* !=0 after decr so still nested */ movi a2, pxCurrentTCB - add a2, a2, a4 + addx4 a2, a4, a2 l32i a2, a2, 0 /* a2 = current TCB */ beqz a2, 1f /* no task ? go to dispatcher */ l32i a1, a2, TOPOFSTACK_OFFS /* SP = pxCurrentTCB->pxTopOfStack */ movi a2, port_switch_flag /* address of switch flag */ - add a2, a2, a4 /* point to flag for this cpu */ + addx4 a2, a4, a2 /* point to flag for this cpu */ l32i a3, a2, 0 /* a3 = port_switch_flag */ beqz a3, .Lnoswitch /* flag = 0 means no switch reqd */ movi a3, 0 @@ -392,14 +388,12 @@ _frxt_dispatch: call0 vTaskSwitchContext // Get next TCB to resume movi a2, pxCurrentTCB getcoreid a3 - slli a3, a3, 2 - add a2, a2, a3 + addx4 a2, a3, a2 #else call4 vTaskSwitchContext // Get next TCB to resume movi a2, pxCurrentTCB getcoreid a3 - slli a3, a3, 2 - add a2, a2, a3 + addx4 a2, a3, a2 #endif l32i a3, a2, 0 l32i sp, a3, TOPOFSTACK_OFFS /* SP = next_TCB->pxTopOfStack; */ @@ -439,8 +433,7 @@ _frxt_dispatch: /* Restore CPENABLE from task's co-processor save area. */ movi a3, pxCurrentTCB /* cp_state = */ getcoreid a2 - slli a2, a2, 2 - add a3, a2, a3 + addx4 a3, a2, a3 l32i a3, a3, 0 l32i a2, a3, CP_TOPOFSTACK_OFFS /* StackType_t *pxStack; */ l16ui a3, a2, XT_CPENABLE /* CPENABLE = cp_state->cpenable; */ @@ -529,8 +522,7 @@ vPortYield: movi a2, pxCurrentTCB getcoreid a3 - slli a3, a3, 2 - add a2, a2, a3 + addx4 a2, a3, a2 l32i a2, a2, 0 /* a2 = pxCurrentTCB */ movi a3, 0 s32i a3, sp, XT_SOL_EXIT /* 0 to flag as solicited frame */ @@ -581,8 +573,7 @@ vPortYieldFromInt: /* Save CPENABLE in task's co-processor save area, and clear CPENABLE. */ movi a3, pxCurrentTCB /* cp_state = */ getcoreid a2 - slli a2, a2, 2 - add a3, a2, a3 + addx4 a3, a2, a3 l32i a3, a3, 0 l32i a2, a3, CP_TOPOFSTACK_OFFS @@ -625,18 +616,17 @@ _frxt_task_coproc_state: /* We can use a3 as a scratchpad, the instances of code calling XT_RTOS_CP_STATE don't seem to need it saved. */ getcoreid a3 - slli a3, a3, 2 /* a3=coreid*4 */ movi a15, port_xSchedulerRunning /* if (port_xSchedulerRunning */ - add a15, a15, a3 + addx4 a15, a3,a15 l32i a15, a15, 0 beqz a15, 1f movi a15, port_interruptNesting /* && port_interruptNesting == 0 */ - add a15, a15, a3 + addx4 a15, a3, a15 l32i a15, a15, 0 bnez a15, 1f movi a15, pxCurrentTCB - add a15, a3, a15 + addx4 a15, a3, a15 l32i a15, a15, 0 /* && pxCurrentTCB != 0) { */ diff --git a/components/freertos/xtensa_vectors.S b/components/freertos/xtensa_vectors.S index f0d874a59..7c2fc2960 100644 --- a/components/freertos/xtensa_vectors.S +++ b/components/freertos/xtensa_vectors.S @@ -904,16 +904,13 @@ _xt_coproc_exc: core we're running on now. */ movi a2, pxCurrentTCB getcoreid a3 - slli a3, a3, 2 - add a2, a2, a3 + addx4 a2, a3, a2 l32i a2, a2, 0 /* a2 = start of pxCurrentTCB[cpuid] */ addi a2, a2, TASKTCB_XCOREID_OFFSET /* offset to xCoreID in tcb struct */ - getcoreid a3 s32i a3, a2, 0 /* store current cpuid */ /* Grab correct xt_coproc_owner_sa for this core */ - getcoreid a2 - movi a3, XCHAL_CP_MAX << 2 + movi a2, XCHAL_CP_MAX << 2 mull a2, a2, a3 movi a3, _xt_coproc_owner_sa /* a3 = base of owner array */ add a3, a3, a2 From 340a722715c0710ee1058db88cf5c359f15b88c3 Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Tue, 18 Oct 2016 11:50:19 +0800 Subject: [PATCH 025/149] Warn user if trax is disabled in menuconfig but functions are called anyway. --- components/xtensa-debug-module/trax.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/components/xtensa-debug-module/trax.c b/components/xtensa-debug-module/trax.c index 18c260a97..d216a3df9 100644 --- a/components/xtensa-debug-module/trax.c +++ b/components/xtensa-debug-module/trax.c @@ -12,10 +12,12 @@ #define TRACEMEM_MUX_BLK1_ONLY 2 #define TRACEMEM_MUX_PROBLK1_APPBLK0 3 -static const char* TAG = "log"; +static const char* TAG = "trax"; -int trax_enable(trax_ena_select_t which) { +int trax_enable(trax_ena_select_t which) +{ #if !CONFIG_MEMMAP_TRACEMEM + ESP_LOGE(TAG, "Trax_enable called, but trax is disabled in menuconfig!"); return ESP_ERR_NO_MEM; #endif #if !CONFIG_MEMMAP_TRACEMEM_TWOBANKS @@ -32,8 +34,10 @@ int trax_enable(trax_ena_select_t which) { } -int trax_start_trace(trax_downcount_unit_t units_until_stop) { +int trax_start_trace(trax_downcount_unit_t units_until_stop) +{ #if !CONFIG_MEMMAP_TRACEMEM + ESP_LOGE(TAG, "Trax_start_trace called, but trax is disabled in menuconfig!"); return ESP_ERR_NO_MEM; #endif uint32_t v; @@ -54,8 +58,10 @@ int trax_start_trace(trax_downcount_unit_t units_until_stop) { } -int trax_trigger_traceend_after_delay(int delay) { +int trax_trigger_traceend_after_delay(int delay) +{ #if !CONFIG_MEMMAP_TRACEMEM + ESP_LOGE(TAG, "Trax_trigger_traceend_after_delay called, but trax is disabled in menuconfig!"); return ESP_ERR_NO_MEM; #endif eri_write(ERI_TRAX_DELAYCNT, delay); From 746ad41d890f8e2e60b5ace63a7910b7c60b89e6 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 18 Oct 2016 15:35:17 +1100 Subject: [PATCH 026/149] Build tests: Use & document clean_build_dir --- make/test_build_system.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/make/test_build_system.sh b/make/test_build_system.sh index a6de14987..7c8cbc1f1 100755 --- a/make/test_build_system.sh +++ b/make/test_build_system.sh @@ -21,7 +21,8 @@ # * The function "take_build_snapshot" can be paired with the functions "assert_rebuilt" and "assert_not_rebuilt" to compare file timestamps and verify if they were rebuilt or not since the snapshot was taken. # # To add a new test case, add it to the end of the run_tests function. Note that not all test cases do comprehensive cleanup -# (although very invasive ones like appending CRLFs to all files take a copy of the esp-idf tree.) +# (although very invasive ones like appending CRLFs to all files take a copy of the esp-idf tree), however the clean_build_dir +# function can be used to force-delete all files from the build output directory. # Set up some variables # @@ -92,7 +93,7 @@ function run_tests() fi print_status "Bootloader build shouldn't leave build output anywhere else" - rm -rf --preserve-root ${BUILD} + clean_build_dir make bootloader # find wizardry: find any file not named sdkconfig.h that # isn't in the "bootloader" or "config" directories From da706111965e471e9d0425e1b853aa63cc4e64d3 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 13 Oct 2016 11:46:51 +1100 Subject: [PATCH 027/149] Deep sleep: Any source named rtc_wake_stub* is linked as RTC wake stub code Also move esp_deepsleep.h documentation out to docs/deep-sleep-stub.rst --- components/esp32/cpu_start.c | 8 +++ components/esp32/deepsleep.c | 3 +- components/esp32/include/esp_attr.h | 9 ++- components/esp32/include/esp_deepsleep.h | 36 +--------- components/esp32/include/esp_system.h | 1 + components/esp32/ld/esp32.common.ld | 44 ++++++++---- docs/deep-sleep-stub.rst | 87 ++++++++++++++++++++++++ 7 files changed, 134 insertions(+), 54 deletions(-) create mode 100644 docs/deep-sleep-stub.rst diff --git a/components/esp32/cpu_start.c b/components/esp32/cpu_start.c index 7b2ccdc60..35f2efd47 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -19,6 +19,7 @@ #include "rom/ets_sys.h" #include "rom/uart.h" +#include "rom/rtc.h" #include "soc/cpu.h" #include "soc/dport_reg.h" @@ -59,6 +60,8 @@ extern void app_main(void); extern int _bss_start; extern int _bss_end; +extern int _rtc_bss_start; +extern int _rtc_bss_end; extern int _init_start; extern void (*__init_array_start)(void); extern void (*__init_array_end)(void); @@ -89,6 +92,11 @@ void IRAM_ATTR call_start_cpu0() memset(&_bss_start, 0, (&_bss_end - &_bss_start) * sizeof(_bss_start)); + /* Unless waking from deep sleep (implying RTC memory is intact), clear RTC bss */ + if (rtc_get_reset_reason(0) != DEEPSLEEP_RESET) { + memset(&_rtc_bss_start, 0, (&_rtc_bss_end - &_rtc_bss_start) * sizeof(_rtc_bss_start)); + } + // Initialize heap allocator heap_alloc_caps_init(); diff --git a/components/esp32/deepsleep.c b/components/esp32/deepsleep.c index 61268bce6..742ff8cf4 100644 --- a/components/esp32/deepsleep.c +++ b/components/esp32/deepsleep.c @@ -40,8 +40,7 @@ void esp_set_deep_sleep_wake_stub(esp_deep_sleep_wake_stub_fn_t new_stub) } void RTC_IRAM_ATTR esp_default_wake_deep_sleep(void) { - // - //mmu_init(0); + /* Clear MMU for CPU 0 */ REG_SET_BIT(DPORT_PRO_CACHE_CTRL1_REG, DPORT_PRO_CACHE_MMU_IA_CLR); REG_CLR_BIT(DPORT_PRO_CACHE_CTRL1_REG, DPORT_PRO_CACHE_MMU_IA_CLR); } diff --git a/components/esp32/include/esp_attr.h b/components/esp32/include/esp_attr.h index 156d2957f..78aa3bd19 100644 --- a/components/esp32/include/esp_attr.h +++ b/components/esp32/include/esp_attr.h @@ -20,22 +20,21 @@ //and all variables in shared RAM. These macros can be used to redirect //particular functions/variables to other memory regions. -// Forces code into IRAM instead of flash +// Forces code into IRAM instead of flash. #define IRAM_ATTR __attribute__((section(".iram1"))) // Forces data into DRAM instead of flash #define DRAM_ATTR __attribute__((section(".dram1"))) -// Forces code into RTC fast memory +// Forces code into RTC fast memory. See "docs/deep-sleep-stub.rst" #define RTC_IRAM_ATTR __attribute__((section(".rtc.text"))) -// Forces data into RTC slow memory +// Forces data into RTC slow memory. See "docs/deep-sleep-stub.rst" // Any variable marked with this attribute will keep its value // during a deep sleep / wake cycle. #define RTC_DATA_ATTR __attribute__((section(".rtc.data"))) -// Forces read-only data into RTC slow memory -// Makes constant data available to RTC wake stubs (see esp_deepsleep.h) +// Forces read-only data into RTC slow memory. See "docs/deep-sleep-stub.rst" #define RTC_RODATA_ATTR __attribute__((section(".rtc.rodata"))) #endif /* __ESP_ATTR_H__ */ diff --git a/components/esp32/include/esp_deepsleep.h b/components/esp32/include/esp_deepsleep.h index 3683a8eea..59b312918 100644 --- a/components/esp32/include/esp_deepsleep.h +++ b/components/esp32/include/esp_deepsleep.h @@ -54,37 +54,7 @@ void system_deep_sleep(uint64_t time_in_us); * to run code immediately when the chip wakes from * sleep. * - * For example: - * @code - * void RTC_IRAM_ATTR esp_wake_deep_sleep(void) { - * esp_default_wake_deep_sleep(); - * // Add additional functionality here - * } - * - * (Implementing this function is not required for normal operation, - * in the usual case your app will start normally when waking from - * deep sleep.) - * - * esp_wake_deep_sleep() functionality is limited: - * - * - Runs immediately on wake, so most of the SoC is freshly reset - - * flash is unmapped and hardware is otherwise uninitialised. - * - * - Can only call functions implemented in ROM, or marked RTC_IRAM_ATTR. - * - * - Static variables marked RTC_DATA_ATTR will have initial values on - * cold boot, and maintain these values between sleep/wake cycles. - * - * - Read-only data should be marked RTC_RODATA_ATTR. Strings must be - * declared as variables also using RTC_RODATA_ATTR, like this: - * RTC_RODATA_ATTR const char message[] = "Hello from very early boot!\n"; - * - * - Any other static memory will not be initialised (either to zero, - * or to any predefined value). - * - * - * - If you implement your own stub, the first call the stub makes - should be to esp_default_wake_deep_sleep(). + * See docs/deep-sleep-stub.rst for details. */ void esp_wake_deep_sleep(void); @@ -115,9 +85,7 @@ esp_deep_sleep_wake_stub_fn_t esp_get_deep_sleep_wake_stub(void); /* The default esp-idf-provided esp_wake_deep_sleep() stub. - If you replace esp_wake_deep_sleep() in your program, or use - esp_set_deep_sleep_wake_stub(), then it is recommended you call - esp_default_wake_deep_sleep() as the first function in your stub. + See docs/deep-sleep-stub.rst for details. */ void esp_default_wake_deep_sleep(void); diff --git a/components/esp32/include/esp_system.h b/components/esp32/include/esp_system.h index 84133366d..8c6564c55 100644 --- a/components/esp32/include/esp_system.h +++ b/components/esp32/include/esp_system.h @@ -16,6 +16,7 @@ #define __ESP_SYSTEM_H__ #include +#include #include "esp_err.h" #include "esp_deepsleep.h" diff --git a/components/esp32/ld/esp32.common.ld b/components/esp32/ld/esp32.common.ld index a3c636784..991259a5e 100644 --- a/components/esp32/ld/esp32.common.ld +++ b/components/esp32/ld/esp32.common.ld @@ -3,8 +3,38 @@ ENTRY(call_start_cpu0); SECTIONS { + /* RTC fast memory holds RTC wake stub code, + including from any source file named rtc_wake_stub*.c + */ + .rtc.text : + { + . = ALIGN(4); + *(.rtc.literal .rtc.text) + *rtc_wake_stub*.o(.literal .text .literal.* .text.*) + } >rtc_iram_seg + + /* RTC slow memory holds RTC wake stub + data/rodata, including from any source file + named rtc_wake_stub*.c + */ + .rtc.data : + { + *(.rtc.data) + *(.rtc.rodata) + *rtc_wake_stub*.o(.data .rodata .data.* .rodata.* .bss .bss.*) + } > rtc_slow_seg + + /* RTC bss, from any source file named rtc_wake_stub*.c */ + .rtc.bss (NOLOAD) : + { + _rtc_bss_start = ABSOLUTE(.); + *rtc_wake_stub*.o(.bss .bss.*) + *rtc_wake_stub*.o(COMMON) + _rtc_bss_end = ABSOLUTE(.); + } > rtc_slow_seg + /* Send .iram0 code to iram */ - .iram0.vectors : + .iram0.vectors : { /* Vectors go to IRAM */ _init_start = ABSOLUTE(.); @@ -153,16 +183,4 @@ SECTIONS _text_end = ABSOLUTE(.); _etext = .; } >iram0_2_seg - - .rtc.text : - { - . = ALIGN(4); - *(.rtc.literal .rtc.text) - } >rtc_iram_seg - - .rtc.data : - { - *(.rtc.data) - *(.rtc.rodata) - } > rtc_slow_seg } diff --git a/docs/deep-sleep-stub.rst b/docs/deep-sleep-stub.rst new file mode 100644 index 000000000..983f8bbf2 --- /dev/null +++ b/docs/deep-sleep-stub.rst @@ -0,0 +1,87 @@ +Deep Sleep Wake Stubs +--------------------- + +ESP32 supports running a "deep sleep wake stub" when coming out of deep sleep. This function runs immediately as soon as the chip wakes up - before any normal initialisation, bootloader, or ESP-IDF code has run. After the wake stub runs, the SoC can go back to sleep or continue to start ESP-IDF normally. + +Deep sleep wake stub code is loaded into "RTC Fast Memory" and any data which it uses must also be loaded into RTC memory. RTC memory regions hold their contents during deep sleep. + +Rules for Wake Stubs +==================== + +Wake stub code must be carefully written: + +* As the SoC has freshly woken from sleep, most of the peripherals are in reset states. The SPI flash is unmapped. + +* The wake stub code can only call functions implemented in ROM or loaded into RTC Fast Memory (see below.) + +* The wake stub code can only access data loaded in RTC memory. All other RAM will be unintiailised and have random contents. The wake stub can use other RAM for temporary storage, but the contents will be overwritten when the SoC goes back to sleep or starts ESP-IDF. + +* RTC memory must include any read-only data (.rodata) used by the stub. + +* Data in RTC memory is initialised whenever the SoC restarts, except when waking from deep sleep. When waking from deep sleep, the values which were present before going to sleep are kept. + +* Wake stub code is a part of the main esp-idf app. During normal running of esp-idf, functions can call the wake stub functions or access RTC memory. It is as if these were regular parts of the app. + +Implementing A Stub +=================== + +The wake stub in esp-idf is called ``esp_wake_deep_sleep()``. This function runs whenever the SoC wakes from deep sleep. There is a default version of this function provided in esp-idf, but the default function is weak-linked so if your app contains a function named ``esp_wake_deep_sleep()` then this will override the default. + +If supplying a custom wake stub, the first thing it does should be to call ``esp_default_wake_deep_sleep()``. + +It is not necessary to implement ``esp_wake_deep_sleep()`` in your app in order to use deep sleep. It is only necessary if you want to have special behaviour immediately on wake. + +If you want to swap between different deep sleep stubs at runtime, it is also possible to do this by calling the ``esp_set_deep_sleep_wake_stub()`` function. This is not necessary if you only use the default ``esp_wake_deep_sleep()`` function. + +All of these functions are declared in the ``esp_deepsleep.h`` header under components/esp32. + +Loading Code Into RTC Memory +============================ + +Wake stub code must be resident in RTC Fast Memory. This can be done in one of two ways. + +The first way is to use the ``RTC_IRAM_ATTR`` attribute to place a function into RTC memory:: + + void RTC_IRAM_ATTR esp_wake_deep_sleep(void) { + esp_default_wake_deep_sleep(); + // Add additional functionality here + } + +The second way is to place the function into any source file whose name starts with ``rtc_wake_stub``. Files names ``rtc_wake_stub*`` have their contents automatically put into RTC memory by the linker. + +The first way is simpler for very short and simple code, or for source files where you want to mix "normal" and "RTC" code. The second way is simpler when you want to write longer pieces of code for RTC memory. + + +Loading Data Into RTC Memory +============================ + +Data used by stub code must be resident in RTC Slow Memory. This memory is also used by the ULP. + +Specifying this data can be done in one of two ways: + +The first way is to use the ``RTC_DATA_ATTR`` and ``RTC_RODATA_ATTR`` to specify any data (writeable or read-only, respectivley) which should be loaded into RTC slow memory:: + + RTC_DATA_ATTR int wake_count; + + void RTC_IRAM_ATTR esp_wake_deep_sleep(void) { + esp_default_wake_deep_sleep(); + static RTC_RODATA_ATTR const char fmt_str[] = "Wake count %d\n"; + ets_printf(fmt_str, wake_count++); + } + +Unfortunately, any string constants used in this way must be declared as arrays and marked with RTC_RODATA_ATTR, as shown in the example above. + +The second way is to place the data into any source file whose name starts with ``rtc_wake_stub``. + +For example, the equivalent example in ``rtc_wake_stub_counter.c``:: + + int wake_count; + + void RTC_IRAM_ATTR esp_wake_deep_sleep(void) { + esp_default_wake_deep_sleep(); + ets_printf("Wake count %d\n", wake_count++); + } + +The second way is a better option if you need to use strings, or write other more complex code. + + From 3f521c4afae7ac1e1145ce6b7fa5f15211bb6ec7 Mon Sep 17 00:00:00 2001 From: Yinling Date: Wed, 19 Oct 2016 15:11:38 +0800 Subject: [PATCH 028/149] update KnowIssues: 1. unsupported cases by "phy" command 2. DHCP server behavior different from ESP8266 3. TCP failed cases causes by unstable TCP behavior 4. UDP failed cases by poor UDP performance --- .../idf_test/integration_test/KnownIssues | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/components/idf_test/integration_test/KnownIssues b/components/idf_test/integration_test/KnownIssues index 08bc48f18..0c48a811e 100644 --- a/components/idf_test/integration_test/KnownIssues +++ b/components/idf_test/integration_test/KnownIssues @@ -23,6 +23,24 @@ TCPIP_IGMP_0204 ^TCPIP_IGMP_0203 ^TCPIP_IGMP_0204 +# don't support PHY mode command +WIFI_SCAN_0201 +WIFI_SCAN_0302 +WIFI_PHY_0401 +WIFI_PHY_0402 +WIFI_PHY_0403 +WIFI_PHY_0404 +WIFI_PHY_0405 +WIFI_PHY_0407 +WIFI_PHY_0406 +WIFI_PHY_0408 +WIFI_PHY_0501 +WIFI_PHY_0502 +WIFI_PHY_0503 +WIFI_PHY_0504 +WIFI_PHY_0505 +WIFI_PHY_0506 + # BUG # auth change event @@ -46,6 +64,10 @@ TCPIP_DHCP_0207 ^TCPIP_DHCP_0207 TCPIP_DHCP_0208 ^TCPIP_DHCP_0208 +TCPIP_DHCP_0205 +^TCPIP_DHCP_0205 +TCPIP_DHCP_0209 +^TCPIP_DHCP_0209 # TCP issue TCPIP_TCP_0402 @@ -54,6 +76,9 @@ TCPIP_TCP_0402 TCPIP_TCP_0210 ^TCPIP_TCP_0210 TCPIP_TCP_0103 +^TCPIP_TCP_0103 +TCPIP_TCP_0112 +^TCPIP_TCP_0112 # UDP issue @@ -61,6 +86,8 @@ TCPIP_UDP_0103 ^TCPIP_UDP_0103 TCPIP_UDP_0110 ^TCPIP_UDP_0110 +TCPIP_UDP_0305 +^TCPIP_UDP_0305 From de7b2f5a09b3852cc92465af24f4f243bab9f5ee Mon Sep 17 00:00:00 2001 From: Yinling Date: Wed, 19 Oct 2016 15:16:47 +0800 Subject: [PATCH 029/149] fix following test case bugs: 1. (^)TCPIP_DHCP_0101, (^)TCPIP_DHCP_0301: IDF set IP 0.0.0.0 may return error, don't check the result of setting IP to 0.0.0.0; 2. rename (^)TCPIP_DHCP_0102 to (^)TCPIP_DHCP_0212 as it's a DHCP server test case; 3. (^)TCPIP_TCP_0204,(^)TCPIP_TCP_0210,(^)TCPIP_UDP_0201,(^)TCPIP_UDP_0202: recv thread can't be deleted, change case to not create recv thread when create socket ; 4. (^)TCPIP_TCP_0206,(^)TCPIP_TCP_0212: query TCP server status format changed. 5. WIFI_SCAN_0301: check command error and test environment not correct 6. WIFI_SCAN_0302, WIFI_SCAN_0303, WIFI_SCAN_0304: test environment not correct --- .../idf_test/integration_test/TestCaseAll.yml | 369 +++++++----------- 1 file changed, 143 insertions(+), 226 deletions(-) diff --git a/components/idf_test/integration_test/TestCaseAll.yml b/components/idf_test/integration_test/TestCaseAll.yml index 0835c146b..a7f63a185 100644 --- a/components/idf_test/integration_test/TestCaseAll.yml +++ b/components/idf_test/integration_test/TestCaseAll.yml @@ -87,7 +87,7 @@ test cases: - - SSC SSC1 dhcp -E -o 1 - ['R SSC1 C +DHCP:STA,OK'] - - SSC SSC1 ip -S -i 0.0.0.0 - - ['R SSC1 C +IP:OK'] + - [R SSC1 C +IP] - - SSC SSC1 sta -C -s -p - [''] - - DELAY 20 @@ -121,54 +121,7 @@ test cases: 1 SSC target connect with PC by UART.' test point 1: basic function test point 2: DHCP client function test - version: v1 (2016-8-15) -- CI ready: 'Yes' - ID: TCPIP_DHCP_0102 - SDK: '8266_NonOS - - 8266_RTOS - - ESP32_IDF' - Test App: SSC - allow fail: '' - auto test: 'Yes' - category: Function - cmd set: - - '' - - - SSC SSC1 ap -S -s -p -t - - ['R SSC1 C +SAP:OK'] - - - SSC SSC1 dhcp -E -o 2 - - ['R SSC1 C +DHCP:AP,OK'] - - - SSC SSC2 sta -C -s -p - - [''] - - - DELAY 20 - - [P PC_COM C +DELAYDONE, 'P SSC2 NC +JAP:CONNECTED'] - - - SSC SSC1 dhcp -S -o 2 - - ['R SSC1 C +DHCP:AP,OK'] - - - SSC SSC2 sta -C -s -p - - ['R SSC2 C +JAP:CONNECTED'] - comment: '' - execution time: 0.0 - expected result: "1.target1 set AP OK \n2.target1 关闭DHCP OK\n3.target2 jap target\ - \ 1,FAIL \n4.target1 打开DHCP OK\n5.target2 jap target 1,ok" - initial condition: T2_1 - initial condition description (auto): target 1 as SoftAP, target 2 as STA, will - autogen a TC with initial condition T2_2 - level: Integration - module: TCPIP - steps: "1.target1 set AP OK \n2.target1 关闭DHCP OK\n3.target2 jap target 1,FAIL \n\ - 4.target1 打开DHCP OK\n5.target2 jap target 1,ok" - sub module: DHCP - summary: dhcp server function test - test environment: SSC_T2_1 - test environment description (auto): 'PC has 1 wired NIC connected to AP. - - PC has 1 WiFi NIC. - - 2 SSC target connect with PC by UART.' - test point 1: basic function - test point 2: DHCP client function test - version: v1 (2016-8-15) + version: v2 (2016-10-19) - CI ready: 'Yes' ID: TCPIP_DHCP_0103 SDK: '8266_NonOS @@ -967,6 +920,53 @@ test cases: test point 1: basic function test point 2: DHCP server function test version: v1 (2016-8-15) +- CI ready: 'Yes' + ID: TCPIP_DHCP_0212 + SDK: '8266_NonOS + + 8266_RTOS + + ESP32_IDF' + Test App: SSC + allow fail: '' + auto test: 'Yes' + category: Function + cmd set: + - '' + - - SSC SSC1 ap -S -s -p -t + - ['R SSC1 C +SAP:OK'] + - - SSC SSC1 dhcp -E -o 2 + - ['R SSC1 C +DHCP:AP,OK'] + - - SSC SSC2 sta -C -s -p + - [''] + - - DELAY 20 + - [P PC_COM C +DELAYDONE, 'P SSC2 NC +JAP:CONNECTED'] + - - SSC SSC1 dhcp -S -o 2 + - ['R SSC1 C +DHCP:AP,OK'] + - - SSC SSC2 sta -C -s -p + - ['R SSC2 C +JAP:CONNECTED'] + comment: '' + execution time: 0.0 + expected result: "1.target1 set AP OK \n2.target1 关闭DHCP OK\n3.target2 jap target\ + \ 1,FAIL \n4.target1 打开DHCP OK\n5.target2 jap target 1,ok" + initial condition: T2_1 + initial condition description (auto): target 1 as SoftAP, target 2 as STA, will + autogen a TC with initial condition T2_2 + level: Integration + module: TCPIP + steps: "1.target1 set AP OK \n2.target1 关闭DHCP OK\n3.target2 jap target 1,FAIL \n\ + 4.target1 打开DHCP OK\n5.target2 jap target 1,ok" + sub module: DHCP + summary: dhcp server function test + test environment: SSC_T2_1 + test environment description (auto): 'PC has 1 wired NIC connected to AP. + + PC has 1 WiFi NIC. + + 2 SSC target connect with PC by UART.' + test point 1: basic function + test point 2: DHCP server function test + version: v2 (2016-10-19) - CI ready: 'Yes' ID: TCPIP_DHCP_0301 SDK: '8266_NonOS @@ -987,7 +987,7 @@ test cases: - - SSC SSC1 sta -C -s -p - ['R SSC1 C +JAP:CONNECTED'] - - SSC SSC1 ip -S -i 0.0.0.0 -o 1 - - ['R SSC1 C +IP:OK'] + - [R SSC1 C +IP] - - SSC SSC1 sta -C -s -p - [''] - - DELAY 10 @@ -1027,7 +1027,7 @@ test cases: 1 SSC target connect with PC by UART.' test point 1: interaction test point 2: static IP and DHCP interaction test - version: v1 (2016-8-15) + version: v2 (2016-10-19) - CI ready: 'Yes' ID: TCPIP_DHCP_0302 SDK: '8266_NonOS @@ -3189,14 +3189,12 @@ test cases: - '' - - SOC SOC1 LISTEN - [R SOC_COM L OK] - - - SSC SSC1 soc -B -t TCP + - - SSC SSC1 soc -B -t TCP -w 0 - ['R SSC1 A :BIND:(\d+),OK'] - - SSC SSC1 soc -C -s -i -p - ['R SSC1 RE CONNECT:\d+,OK'] - - SOC SOC1 ACCEPT SOC2 - [R SOC_COM L OK] - - - SSC SSC1 soc -W -s -o 0 - - ['R SSC1 RE WORKTHREAD:\d+,OK'] - - SOC SOC2 SEND 146000 - [P SOC_COM R *] - - SSC SSC1 soc -W -s -o 1 @@ -3246,7 +3244,7 @@ test cases: 1 SSC target connect with PC by UART.' test point 1: basic function test point 2: use TCP SAP (socket/espconn API) in different state - version: v1 (2016-8-15) + version: v2 (2016-10-19) - CI ready: 'Yes' ID: TCPIP_TCP_0206 SDK: '8266_NonOS @@ -3280,7 +3278,7 @@ test cases: - ['R SSC1 A :ACCEPT:(\d+),\d+,.+,\d+'] - - SSC SSC1 soc -I - ['P SSC1 RE "SOCINFO:%%s,2,%%s,\d+,%%s,%%d"%%(,,,)', - 'P SSC1 RE "SOCINFO:%%s,2,.+,\d+,.+,\d+"%%()', 'P SSC1 RE "SOCINFO:%%s,82,.+,%%d,.+,\d+"%%(,)', + 'P SSC1 RE "SOCINFO:%%s,2,.+,\d+,.+,\d+"%%()', 'P SSC1 RE "SOCINFO:%%s,82,.+,%%d"%%(,)', 'P SSC1 RE "SOCINFO:%%s,2,%%s,%%d,%%s,\d+"%%(,,,)'] comment: '' execution time: 0.0 @@ -3531,14 +3529,12 @@ test cases: - '' - - SOC SOC1 LISTEN - [R SOC_COM L OK] - - - SSC SSC1 soc -B -t TCP + - - SSC SSC1 soc -B -t TCP -w 0 - ['R SSC1 A :BIND:(\d+),OK'] - - SSC SSC1 soc -C -s -i -p - ['R SSC1 RE CONNECT:\d+,OK'] - - SOC SOC1 ACCEPT SOC2 - [R SOC_COM L OK] - - - SSC SSC1 soc -W -s -o 0 - - ['R SSC1 RE WORKTHREAD:\d+,OK'] - - SOC SOC2 SEND 146000 - [P SOC_COM R *] - - SSC SSC1 soc -W -s -o 1 @@ -3586,7 +3582,7 @@ test cases: 1 SSC target connect with PC by UART.' test point 1: basic function test point 2: use TCP SAP (socket/espconn API) in different state - version: v1 (2016-8-15) + version: v2 (2016-10-19) - CI ready: 'Yes' ID: TCPIP_TCP_0212 SDK: '8266_NonOS @@ -3620,7 +3616,7 @@ test cases: - ['R SSC1 A :ACCEPT:(\d+),\d+,.+,\d+'] - - SSC SSC1 soc -I - ['P SSC1 RE "SOCINFO:%%s,2,%%s,\d+,%%s,%%d"%%(,,,)', - 'P SSC1 RE "SOCINFO:%%s,2,.+,\d+,.+,\d+"%%()', 'P SSC1 RE "SOCINFO:%%s,82,.+,%%d,.+,\d+"%%(,)', + 'P SSC1 RE "SOCINFO:%%s,2,.+,\d+,.+,\d+"%%()', 'P SSC1 RE "SOCINFO:%%s,82,.+,%%d"%%(,)', 'P SSC1 RE "SOCINFO:%%s,2,%%s,%%d,%%s,\d+"%%(,,,)'] comment: '' execution time: 0.0 @@ -5119,10 +5115,8 @@ test cases: - '' - - SOC SOC1 BIND - [R SOC_COM L OK] - - - SSC SSC1 soc -B -t UDP -p + - - SSC SSC1 soc -B -t UDP -p -w 0 - ['R SSC1 A :BIND:(\d+),OK'] - - - SSC SSC1 soc -W -s -o 0 - - ['R SSC1 RE WORKTHREAD:\d+,OK'] - - SOC SOC1 SENDTO 1472 - [''] - - SOC SOC1 SENDTO 1472 @@ -5180,7 +5174,7 @@ test cases: 1 SSC target connect with PC by UART.' test point 1: abnormal/special use test point 2: use UDP SAP (socket/espconn API) in different state - version: v1 (2016-8-15) + version: v2 (2016-10-19) - CI ready: 'Yes' ID: TCPIP_UDP_0202 SDK: '8266_NonOS @@ -5198,8 +5192,8 @@ test cases: - [R SOC_COM L OK] - - SSC SSC1 soc -B -t UDP -p - ['R SSC1 A :BIND:(\d+),OK'] - - - SSC SSC1 soc -W -s -o 0 - - ['R SSC1 RE WORKTHREAD:\d+,OK'] + - - SOC SOC1 SENDTO 1472 + - [''] - - SOC SOC1 SENDTO 1472 - [''] - - SOC SOC1 SENDTO 1472 @@ -5257,7 +5251,7 @@ test cases: 1 SSC target connect with PC by UART.' test point 1: abnormal/special use test point 2: use UDP SAP (socket/espconn API) in different state - version: v1 (2016-8-15) + version: v2 (2016-10-19) - CI ready: 'Yes' ID: TCPIP_UDP_0301 SDK: '8266_NonOS @@ -7915,7 +7909,7 @@ test cases: - - SSC SSC1 sta -S - [''] - - SSC SSC1 sta -S - - [P SSC1 C +SCANFAIL, 'P SSC1 P +SCAN:', R SSC1 C +SCANDONE] + - [P SSC1 C +SCANFAIL, 'P SSC1 C +SCAN:', R SSC1 C +SCANDONE] comment: '' execution time: 0.0 expected result: '1. second scan failed @@ -7931,14 +7925,12 @@ test cases: 2. do scan before scan finished' sub module: WIFI Scan summary: reject scan request before scan finished - test environment: SSC_T2_PhyMode - test environment description (auto): '2 SSC target connect with PC by UART. + test environment: SSC_T1_1 + test environment description (auto): 'PC has 2 wired NIC connected to AP. - PC has one WiFi NIC support capture wlan packet using libpcap. + PC has 1 WiFi NIC. - Set 4 AP with phy mode 11b, 11g, 11n HT20, 11n HT40. - - Put 4 APs near SSC targets.' + 1 SSC target connect with PC by UART.' test point 1: interaction test point 2: Scan interact with other WiFi operation version: v1 (2015-8-15) @@ -7987,14 +7979,12 @@ test cases: 3. target 2 scan in AP channel in 11b.g,n,ht40 mode' sub module: WIFI Scan summary: scan in congest channel - test environment: SSC_T2_PhyMode - test environment description (auto): '2 SSC target connect with PC by UART. + test environment: SSC_T2_1 + test environment description (auto): 'PC has 1 wired NIC connected to AP. - PC has one WiFi NIC support capture wlan packet using libpcap. + PC has 1 WiFi NIC. - Set 4 AP with phy mode 11b, 11g, 11n HT20, 11n HT40. - - Put 4 APs near SSC targets.' + 2 SSC target connect with PC by UART.' test point 1: interaction test point 2: Scan interact with other WiFi operation version: v1 (2015-8-15) @@ -8022,8 +8012,9 @@ test cases: expected result: '2. scan succeed, JAP succeed 5. JAP succeed, scan succeed' - initial condition: T2_2 - initial condition description (auto): target 1 as AP+STA, target 2 as AP+STA (autogen) + initial condition: STAM1 + initial condition description (auto): sta mode, quit AP, DHCP on, will autogen a + TC with initial condition STAAP1 level: Integration module: WIFI MAC steps: '1. target 1 STA join AP @@ -8037,14 +8028,12 @@ test cases: 5. target 1 JAP before scan succeed' sub module: WIFI Scan summary: scan during JAP - test environment: SSC_T2_PhyMode - test environment description (auto): '2 SSC target connect with PC by UART. + test environment: SSC_T1_1 + test environment description (auto): 'PC has 2 wired NIC connected to AP. - PC has one WiFi NIC support capture wlan packet using libpcap. + PC has 1 WiFi NIC. - Set 4 AP with phy mode 11b, 11g, 11n HT20, 11n HT40. - - Put 4 APs near SSC targets.' + 1 SSC target connect with PC by UART.' test point 1: interaction test point 2: Scan interact with other WiFi operation version: v1 (2015-8-15) @@ -8087,14 +8076,12 @@ test cases: 5. target 2 STA JAP before target 1 STA scan succeed' sub module: WIFI Scan summary: scan during ext STA join SoftAP - test environment: SSC_T2_PhyMode - test environment description (auto): '2 SSC target connect with PC by UART. + test environment: SSC_T2_1 + test environment description (auto): 'PC has 1 wired NIC connected to AP. - PC has one WiFi NIC support capture wlan packet using libpcap. + PC has 1 WiFi NIC. - Set 4 AP with phy mode 11b, 11g, 11n HT20, 11n HT40. - - Put 4 APs near SSC targets.' + 2 SSC target connect with PC by UART.' test point 1: interaction test point 2: Scan interact with other WiFi operation version: v1 (2015-8-15) @@ -8114,7 +8101,7 @@ test cases: - - SSC SSC1 dhcp -E -o 1 - ['R SSC1 C +DHCP:STA,OK'] - - SSC SSC1 ip -S -i 0.0.0.0 - - ['R SSC1 C +IP:OK'] + - [R SSC1 C +IP] - - SSC SSC1 sta -C -s -p - [''] - - DELAY 20 @@ -8148,53 +8135,7 @@ test cases: 1 SSC target connect with PC by UART.' test point 1: basic function test point 2: DHCP client function test - version: v1 (2016-8-15) -- CI ready: 'Yes' - ID: ^TCPIP_DHCP_0102 - SDK: '8266_NonOS - - 8266_RTOS - - ESP32_IDF' - Test App: SSC - allow fail: '' - auto test: 'Yes' - category: Function - cmd set: - - '' - - - SSC SSC1 ap -S -s -p -t - - ['R SSC1 C +SAP:OK'] - - - SSC SSC1 dhcp -E -o 2 - - ['R SSC1 C +DHCP:AP,OK'] - - - SSC SSC2 sta -C -s -p - - [''] - - - DELAY 20 - - [P PC_COM C +DELAYDONE, 'P SSC2 NC +JAP:CONNECTED'] - - - SSC SSC1 dhcp -S -o 2 - - ['R SSC1 C +DHCP:AP,OK'] - - - SSC SSC2 sta -C -s -p - - ['R SSC2 C +JAP:CONNECTED'] - comment: '' - execution time: 0.0 - expected result: "1.target1 set AP OK \n2.target1 关闭DHCP OK\n3.target2 jap target\ - \ 1,FAIL \n4.target1 打开DHCP OK\n5.target2 jap target 1,ok" - initial condition: T2_2 - initial condition description (auto): target 1 as AP+STA, target 2 as AP+STA (autogen) - level: Integration - module: TCPIP - steps: "1.target1 set AP OK \n2.target1 关闭DHCP OK\n3.target2 jap target 1,FAIL \n\ - 4.target1 打开DHCP OK\n5.target2 jap target 1,ok" - sub module: DHCP - summary: dhcp server function test - test environment: SSC_T2_1 - test environment description (auto): 'PC has 1 wired NIC connected to AP. - - PC has 1 WiFi NIC. - - 2 SSC target connect with PC by UART.' - test point 1: basic function - test point 2: DHCP client function test - version: v1 (2016-8-15) + version: v2 (2016-10-19) - CI ready: 'Yes' ID: ^TCPIP_DHCP_0103 SDK: '8266_NonOS @@ -8982,6 +8923,52 @@ test cases: test point 1: basic function test point 2: DHCP server function test version: v1 (2016-8-15) +- CI ready: 'Yes' + ID: ^TCPIP_DHCP_0212 + SDK: '8266_NonOS + + 8266_RTOS + + ESP32_IDF' + Test App: SSC + allow fail: '' + auto test: 'Yes' + category: Function + cmd set: + - '' + - - SSC SSC1 ap -S -s -p -t + - ['R SSC1 C +SAP:OK'] + - - SSC SSC1 dhcp -E -o 2 + - ['R SSC1 C +DHCP:AP,OK'] + - - SSC SSC2 sta -C -s -p + - [''] + - - DELAY 20 + - [P PC_COM C +DELAYDONE, 'P SSC2 NC +JAP:CONNECTED'] + - - SSC SSC1 dhcp -S -o 2 + - ['R SSC1 C +DHCP:AP,OK'] + - - SSC SSC2 sta -C -s -p + - ['R SSC2 C +JAP:CONNECTED'] + comment: '' + execution time: 0.0 + expected result: "1.target1 set AP OK \n2.target1 关闭DHCP OK\n3.target2 jap target\ + \ 1,FAIL \n4.target1 打开DHCP OK\n5.target2 jap target 1,ok" + initial condition: T2_2 + initial condition description (auto): target 1 as AP+STA, target 2 as AP+STA (autogen) + level: Integration + module: TCPIP + steps: "1.target1 set AP OK \n2.target1 关闭DHCP OK\n3.target2 jap target 1,FAIL \n\ + 4.target1 打开DHCP OK\n5.target2 jap target 1,ok" + sub module: DHCP + summary: dhcp server function test + test environment: SSC_T2_1 + test environment description (auto): 'PC has 1 wired NIC connected to AP. + + PC has 1 WiFi NIC. + + 2 SSC target connect with PC by UART.' + test point 1: basic function + test point 2: DHCP server function test + version: v2 (2016-10-19) - CI ready: 'Yes' ID: ^TCPIP_DHCP_0301 SDK: '8266_NonOS @@ -9002,7 +8989,7 @@ test cases: - - SSC SSC1 sta -C -s -p - ['R SSC1 C +JAP:CONNECTED'] - - SSC SSC1 ip -S -i 0.0.0.0 -o 1 - - ['R SSC1 C +IP:OK'] + - [R SSC1 C +IP] - - SSC SSC1 sta -C -s -p - [''] - - DELAY 10 @@ -9042,7 +9029,7 @@ test cases: 1 SSC target connect with PC by UART.' test point 1: interaction test point 2: static IP and DHCP interaction test - version: v1 (2016-8-15) + version: v2 (2016-10-19) - CI ready: 'Yes' ID: ^TCPIP_DHCP_0302 SDK: '8266_NonOS @@ -11199,14 +11186,12 @@ test cases: - '' - - SOC SOC1 LISTEN - [R SOC_COM L OK] - - - SSC SSC1 soc -B -t TCP + - - SSC SSC1 soc -B -t TCP -w 0 - ['R SSC1 A :BIND:(\d+),OK'] - - SSC SSC1 soc -C -s -i -p - ['R SSC1 RE CONNECT:\d+,OK'] - - SOC SOC1 ACCEPT SOC2 - [R SOC_COM L OK] - - - SSC SSC1 soc -W -s -o 0 - - ['R SSC1 RE WORKTHREAD:\d+,OK'] - - SOC SOC2 SEND 146000 - [P SOC_COM R *] - - SSC SSC1 soc -W -s -o 1 @@ -11254,7 +11239,7 @@ test cases: 1 SSC target connect with PC by UART.' test point 1: basic function test point 2: use TCP SAP (socket/espconn API) in different state - version: v1 (2016-8-15) + version: v2 (2016-10-19) - CI ready: 'Yes' ID: ^TCPIP_TCP_0206 SDK: '8266_NonOS @@ -11288,7 +11273,7 @@ test cases: - ['R SSC1 A :ACCEPT:(\d+),\d+,.+,\d+'] - - SSC SSC1 soc -I - ['P SSC1 RE "SOCINFO:%%s,2,%%s,\d+,%%s,%%d"%%(,,,)', - 'P SSC1 RE "SOCINFO:%%s,2,.+,\d+,.+,\d+"%%()', 'P SSC1 RE "SOCINFO:%%s,82,.+,%%d,.+,\d+"%%(,)', + 'P SSC1 RE "SOCINFO:%%s,2,.+,\d+,.+,\d+"%%()', 'P SSC1 RE "SOCINFO:%%s,82,.+,%%d"%%(,)', 'P SSC1 RE "SOCINFO:%%s,2,%%s,%%d,%%s,\d+"%%(,,,)'] comment: '' execution time: 0.0 @@ -11539,14 +11524,12 @@ test cases: - '' - - SOC SOC1 LISTEN - [R SOC_COM L OK] - - - SSC SSC1 soc -B -t TCP + - - SSC SSC1 soc -B -t TCP -w 0 - ['R SSC1 A :BIND:(\d+),OK'] - - SSC SSC1 soc -C -s -i -p - ['R SSC1 RE CONNECT:\d+,OK'] - - SOC SOC1 ACCEPT SOC2 - [R SOC_COM L OK] - - - SSC SSC1 soc -W -s -o 0 - - ['R SSC1 RE WORKTHREAD:\d+,OK'] - - SOC SOC2 SEND 146000 - [P SOC_COM R *] - - SSC SSC1 soc -W -s -o 1 @@ -11592,7 +11575,7 @@ test cases: 1 SSC target connect with PC by UART.' test point 1: basic function test point 2: use TCP SAP (socket/espconn API) in different state - version: v1 (2016-8-15) + version: v2 (2016-10-19) - CI ready: 'Yes' ID: ^TCPIP_TCP_0212 SDK: '8266_NonOS @@ -11626,7 +11609,7 @@ test cases: - ['R SSC1 A :ACCEPT:(\d+),\d+,.+,\d+'] - - SSC SSC1 soc -I - ['P SSC1 RE "SOCINFO:%%s,2,%%s,\d+,%%s,%%d"%%(,,,)', - 'P SSC1 RE "SOCINFO:%%s,2,.+,\d+,.+,\d+"%%()', 'P SSC1 RE "SOCINFO:%%s,82,.+,%%d,.+,\d+"%%(,)', + 'P SSC1 RE "SOCINFO:%%s,2,.+,\d+,.+,\d+"%%()', 'P SSC1 RE "SOCINFO:%%s,82,.+,%%d"%%(,)', 'P SSC1 RE "SOCINFO:%%s,2,%%s,%%d,%%s,\d+"%%(,,,)'] comment: '' execution time: 0.0 @@ -12981,10 +12964,8 @@ test cases: - '' - - SOC SOC1 BIND - [R SOC_COM L OK] - - - SSC SSC1 soc -B -t UDP -p + - - SSC SSC1 soc -B -t UDP -p -w 0 - ['R SSC1 A :BIND:(\d+),OK'] - - - SSC SSC1 soc -W -s -o 0 - - ['R SSC1 RE WORKTHREAD:\d+,OK'] - - SOC SOC1 SENDTO 1472 - [''] - - SOC SOC1 SENDTO 1472 @@ -13042,7 +13023,7 @@ test cases: 1 SSC target connect with PC by UART.' test point 1: abnormal/special use test point 2: use UDP SAP (socket/espconn API) in different state - version: v1 (2016-8-15) + version: v2 (2016-10-19) - CI ready: 'Yes' ID: ^TCPIP_UDP_0202 SDK: '8266_NonOS @@ -13060,8 +13041,8 @@ test cases: - [R SOC_COM L OK] - - SSC SSC1 soc -B -t UDP -p - ['R SSC1 A :BIND:(\d+),OK'] - - - SSC SSC1 soc -W -s -o 0 - - ['R SSC1 RE WORKTHREAD:\d+,OK'] + - - SOC SOC1 SENDTO 1472 + - [''] - - SOC SOC1 SENDTO 1472 - [''] - - SOC SOC1 SENDTO 1472 @@ -13119,7 +13100,7 @@ test cases: 1 SSC target connect with PC by UART.' test point 1: abnormal/special use test point 2: use UDP SAP (socket/espconn API) in different state - version: v1 (2016-8-15) + version: v2 (2016-10-19) - CI ready: 'Yes' ID: ^TCPIP_UDP_0301 SDK: '8266_NonOS @@ -14652,67 +14633,3 @@ test cases: test point 1: basic function test point 2: scan with different config version: v1 (2016-8-15) -- CI ready: 'Yes' - ID: ^WIFI_SCAN_0105 - SDK: '8266_NonOS - - 8266_RTOS - - ESP32_IDF' - Test App: SSC - allow fail: '' - auto test: 'Yes' - category: Function - cmd set: - - '' - - - SSC SSC1 sta -D - - ['R SSC1 C +QAP:'] - - - SSC SSC1 ap -S -s -p 123456789 -t 3 -h 0 -n 11 - - ['R SSC1 C +SAP:OK'] - - - SSC SSC2 sta -S -s -b -n 11 - - [R SSC2 P C +SCANDONE] - - - SSC SSC2 sta -S -s -b -n 11 - - [R SSC2 NP C +SCANDONE] - - - SSC SSC2 sta -S -s -b ff:ff:ff:ff:ff:11 -n 11 - - [R SSC2 P , R SSC2 NP C +SCANDONE] - - - SSC SSC2 sta -S -s -b -n 10 - - [R SSC2 P , R SSC2 NP C +SCANDONE] - comment: '' - execution time: 0.0 - expected result: '1.target1 QAP - - 2. target1 set AP,set ssid broad cast,set channel 11 - - 3.target2 上查询到 - - 4.target2 上查询不到 - - 5.target2 上查询不到 - - 6.target2 上查询不到' - initial condition: T2_2 - initial condition description (auto): target 1 as AP+STA, target 2 as AP+STA (autogen) - level: Integration - module: WIFI MAC - steps: '1.target1 QAP - - 2. target1 set AP,set ssid broad cast,set channel 11 - - 3.target2 上查询到 - - 4.target2 上查询不到 - - 5.target2 上查询不到 - - 6.target2 上查询不到' - sub module: WIFI Scan - summary: scan with several configs - test environment: SSC_T2_1 - test environment description (auto): 'PC has 1 wired NIC connected to AP. - - PC has 1 WiFi NIC. - - 2 SSC target connect with PC by UART.' - test point 1: basic function - test point 2: scan with different config - version: v1 (2016-8-15) From c2b63a614ee110759ffc211f2fd571fa4c50fdf0 Mon Sep 17 00:00:00 2001 From: Yinling Date: Wed, 19 Oct 2016 15:22:13 +0800 Subject: [PATCH 030/149] sync test env from auto_test_script (add SSC_T5_IOT1) --- components/idf_test/integration_test/TestEnvAll.yml | 4 ++++ components/idf_test/uint_test/TestEnvAll.yml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/components/idf_test/integration_test/TestEnvAll.yml b/components/idf_test/integration_test/TestEnvAll.yml index afa6cb812..2e59961d9 100644 --- a/components/idf_test/integration_test/TestEnvAll.yml +++ b/components/idf_test/integration_test/TestEnvAll.yml @@ -229,6 +229,10 @@ test environment: Put 4 APs near SSC targets.', test script: EnvBase} - {PC OS: '', Special: N, Target Count: 5.0, script path: EnvBase.py, tag: SSC_T5_1, test environment detail: 5 SSC target connect with PC by UART., test script: EnvBase} +- {PC OS: '', Special: Y, Target Count: 5.0, script path: EnvBase.py, tag: SSC_T5_IOT1, + test environment detail: '5 SSC targets connect with PC by UART. + + some Android smart phone are placed near SSC targets.', test script: EnvBase} - {PC OS: '', Special: N, Target Count: 1.0, script path: EnvBase.py, tag: SSC_T6_1, test environment detail: 'PC has 1 wired NIC connected to AP. diff --git a/components/idf_test/uint_test/TestEnvAll.yml b/components/idf_test/uint_test/TestEnvAll.yml index afa6cb812..2e59961d9 100644 --- a/components/idf_test/uint_test/TestEnvAll.yml +++ b/components/idf_test/uint_test/TestEnvAll.yml @@ -229,6 +229,10 @@ test environment: Put 4 APs near SSC targets.', test script: EnvBase} - {PC OS: '', Special: N, Target Count: 5.0, script path: EnvBase.py, tag: SSC_T5_1, test environment detail: 5 SSC target connect with PC by UART., test script: EnvBase} +- {PC OS: '', Special: Y, Target Count: 5.0, script path: EnvBase.py, tag: SSC_T5_IOT1, + test environment detail: '5 SSC targets connect with PC by UART. + + some Android smart phone are placed near SSC targets.', test script: EnvBase} - {PC OS: '', Special: N, Target Count: 1.0, script path: EnvBase.py, tag: SSC_T6_1, test environment detail: 'PC has 1 wired NIC connected to AP. From 0324373cd1c89570dc3b454410d124504df8e10f Mon Sep 17 00:00:00 2001 From: Yinling Date: Wed, 19 Oct 2016 15:23:07 +0800 Subject: [PATCH 031/149] update auto generated CI job config --- .../integration_test/CIConfigs/IT_Function_TCPIP_02.yml | 8 ++++---- .../integration_test/CIConfigs/IT_Function_TCPIP_03.yml | 2 +- .../integration_test/CIConfigs/IT_Function_TCPIP_04.yml | 2 +- .../integration_test/CIConfigs/IT_Function_WIFI_01.yml | 8 ++++---- .../integration_test/CIConfigs/IT_Function_WIFI_02.yml | 6 +++--- .../integration_test/CIConfigs/IT_Function_WIFI_06.yml | 5 ++--- 6 files changed, 15 insertions(+), 16 deletions(-) diff --git a/components/idf_test/integration_test/CIConfigs/IT_Function_TCPIP_02.yml b/components/idf_test/integration_test/CIConfigs/IT_Function_TCPIP_02.yml index 61adea982..73618e0d7 100644 --- a/components/idf_test/integration_test/CIConfigs/IT_Function_TCPIP_02.yml +++ b/components/idf_test/integration_test/CIConfigs/IT_Function_TCPIP_02.yml @@ -4,7 +4,7 @@ Filter: - Add: ID: [TCPIP_IGMP_0203, ^TCPIP_TCP_0403, ^TCPIP_TCP_0408, TCPIP_UDP_0201, TCPIP_UDP_0202, ^TCPIP_DHCP_0301, ^TCPIP_TCP_0101, ^TCPIP_TCP_0103, ^TCPIP_TCP_0105, ^TCPIP_TCP_0104, - ^TCPIP_TCP_0107, ^TCPIP_TCP_0106, ^TCPIP_DHCP_0210, ^TCPIP_DHCP_0211, ^TCPIP_TCP_0404, - TCPIP_TCP_0212, TCPIP_TCP_0210, ^TCPIP_TCP_0406, ^TCPIP_TCP_0407, ^TCPIP_TCP_0401, - ^TCPIP_TCP_0210, ^TCPIP_TCP_0212, TCPIP_DHCP_0211, TCPIP_DHCP_0210, TCPIP_DHCP_0101, - TCPIP_DHCP_0103, TCPIP_DHCP_0102, TCPIP_DHCP_0206, TCPIP_DHCP_0207, ^TCPIP_IP_0102] + ^TCPIP_TCP_0107, ^TCPIP_TCP_0106, ^TCPIP_DHCP_0210, ^TCPIP_DHCP_0211, ^TCPIP_DHCP_0212, + ^TCPIP_TCP_0404, TCPIP_TCP_0212, TCPIP_TCP_0210, ^TCPIP_TCP_0406, ^TCPIP_TCP_0407, + ^TCPIP_TCP_0401, ^TCPIP_TCP_0210, ^TCPIP_TCP_0212, TCPIP_DHCP_0211, TCPIP_DHCP_0210, + TCPIP_DHCP_0212, TCPIP_DHCP_0101, TCPIP_DHCP_0103, TCPIP_DHCP_0206, TCPIP_DHCP_0207] diff --git a/components/idf_test/integration_test/CIConfigs/IT_Function_TCPIP_03.yml b/components/idf_test/integration_test/CIConfigs/IT_Function_TCPIP_03.yml index 9d64630e9..b326ed721 100644 --- a/components/idf_test/integration_test/CIConfigs/IT_Function_TCPIP_03.yml +++ b/components/idf_test/integration_test/CIConfigs/IT_Function_TCPIP_03.yml @@ -2,7 +2,7 @@ Config: {execute count: 1, execute order: in order} DUT: [SSC2, SSC1] Filter: - Add: - ID: [^TCPIP_UDP_0105, ^TCPIP_UDP_0107, ^TCPIP_UDP_0106, ^TCPIP_UDP_0101, TCPIP_TCP_0203, + ID: [^TCPIP_IP_0102, ^TCPIP_UDP_0105, ^TCPIP_UDP_0107, ^TCPIP_UDP_0106, ^TCPIP_UDP_0101, TCPIP_TCP_0202, ^TCPIP_UDP_0108, ^TCPIP_IGMP_0201, ^TCPIP_IGMP_0203, ^TCPIP_IGMP_0202, ^TCPIP_IGMP_0103, TCPIP_UDP_0114, TCPIP_UDP_0113, TCPIP_UDP_0112, TCPIP_DHCP_0205, TCPIP_DHCP_0202, TCPIP_DHCP_0203, ^TCPIP_TCP_0102, TCPIP_TCP_0106, TCPIP_TCP_0107, diff --git a/components/idf_test/integration_test/CIConfigs/IT_Function_TCPIP_04.yml b/components/idf_test/integration_test/CIConfigs/IT_Function_TCPIP_04.yml index 6be01f698..314de4a7e 100644 --- a/components/idf_test/integration_test/CIConfigs/IT_Function_TCPIP_04.yml +++ b/components/idf_test/integration_test/CIConfigs/IT_Function_TCPIP_04.yml @@ -5,6 +5,6 @@ Filter: ID: [^TCPIP_TCP_0110, ^TCPIP_TCP_0111, TCPIP_DHCP_0209, ^TCPIP_DHCP_0209, ^TCPIP_DHCP_0207, ^TCPIP_DHCP_0206, ^TCPIP_DHCP_0205, ^TCPIP_DHCP_0204, ^TCPIP_DHCP_0203, ^TCPIP_DHCP_0202, ^TCPIP_DHCP_0201, TCPIP_TCP_0204, TCPIP_TCP_0207, TCPIP_TCP_0206, TCPIP_TCP_0201, - ^TCPIP_DHCP_0101, ^TCPIP_DHCP_0102, ^TCPIP_DHCP_0103, ^TCPIP_DHCP_0208, TCPIP_TCP_0208, + ^TCPIP_DHCP_0101, TCPIP_TCP_0203, ^TCPIP_DHCP_0103, ^TCPIP_DHCP_0208, TCPIP_TCP_0208, ^TCPIP_TCP_0202, ^TCPIP_TCP_0203, TCPIP_DHCP_0204, ^TCPIP_TCP_0201, ^TCPIP_TCP_0206, ^TCPIP_TCP_0207, ^TCPIP_TCP_0204, TCPIP_DHCP_0201, ^TCPIP_TCP_0208, TCPIP_DHCP_0208] diff --git a/components/idf_test/integration_test/CIConfigs/IT_Function_WIFI_01.yml b/components/idf_test/integration_test/CIConfigs/IT_Function_WIFI_01.yml index c22bc59bd..9f8424f6d 100644 --- a/components/idf_test/integration_test/CIConfigs/IT_Function_WIFI_01.yml +++ b/components/idf_test/integration_test/CIConfigs/IT_Function_WIFI_01.yml @@ -4,7 +4,7 @@ Filter: - Add: ID: [^WIFI_CONN_0601, ^WIFI_ADDR_0101, WIFI_SCAN_0103, WIFI_SCAN_0102, WIFI_SCAN_0101, WIFI_SCAN_0105, WIFI_SCAN_0104, ^WIFI_CONN_0103, WIFI_CONN_0201, WIFI_CONN_0904, - ^WIFI_SCAN_0102, ^WIFI_SCAN_0103, ^WIFI_SCAN_0104, ^WIFI_SCAN_0105, WIFI_CONN_0401, - WIFI_ADDR_0101, WIFI_ADDR_0102, WIFI_CONN_0301, ^WIFI_CONN_0801, ^WIFI_CONN_0301, - WIFI_CONN_0501, WIFI_CONN_0502, ^WIFI_CONN_0401, WIFI_MODE_0101, WIFI_MODE_0103, - WIFI_MODE_0102, ^WIFI_CONN_0904, ^WIFI_CONN_0901, WIFI_CONN_0601, ^WIFI_CONN_0201] + ^WIFI_SCAN_0102, ^WIFI_SCAN_0103, ^WIFI_SCAN_0104, WIFI_CONN_0401, WIFI_ADDR_0101, + WIFI_ADDR_0102, WIFI_CONN_0301, WIFI_SCAN_0301, WIFI_SCAN_0303, ^WIFI_CONN_0801, + WIFI_SCAN_0304, ^WIFI_CONN_0301, WIFI_CONN_0501, WIFI_CONN_0502, ^WIFI_CONN_0401, + WIFI_MODE_0101, WIFI_MODE_0103, WIFI_MODE_0102, ^WIFI_CONN_0904, ^WIFI_CONN_0901] diff --git a/components/idf_test/integration_test/CIConfigs/IT_Function_WIFI_02.yml b/components/idf_test/integration_test/CIConfigs/IT_Function_WIFI_02.yml index 049054dba..74e6ca612 100644 --- a/components/idf_test/integration_test/CIConfigs/IT_Function_WIFI_02.yml +++ b/components/idf_test/integration_test/CIConfigs/IT_Function_WIFI_02.yml @@ -2,6 +2,6 @@ Config: {execute count: 1, execute order: in order} DUT: [SSC2, SSC1] Filter: - Add: - ID: [^WIFI_ADDR_0102, WIFI_CONN_0901, WIFI_CONN_0801, ^WIFI_CONN_0104, WIFI_CONN_0104, - WIFI_CONN_0101, WIFI_CONN_0102, WIFI_CONN_0103, ^WIFI_SCAN_0101, ^WIFI_CONN_0101, - ^WIFI_CONN_0502, ^WIFI_CONN_0501] + ID: [WIFI_SCAN_0302, WIFI_CONN_0601, ^WIFI_CONN_0201, ^WIFI_ADDR_0102, WIFI_CONN_0901, + WIFI_CONN_0801, ^WIFI_CONN_0104, WIFI_CONN_0104, WIFI_CONN_0101, WIFI_CONN_0102, + WIFI_CONN_0103, ^WIFI_SCAN_0101, ^WIFI_CONN_0101, ^WIFI_CONN_0502, ^WIFI_CONN_0501] diff --git a/components/idf_test/integration_test/CIConfigs/IT_Function_WIFI_06.yml b/components/idf_test/integration_test/CIConfigs/IT_Function_WIFI_06.yml index 9d639f912..dd42815a9 100644 --- a/components/idf_test/integration_test/CIConfigs/IT_Function_WIFI_06.yml +++ b/components/idf_test/integration_test/CIConfigs/IT_Function_WIFI_06.yml @@ -2,6 +2,5 @@ Config: {execute count: 1, execute order: in order} DUT: [SSC2, SSC1] Filter: - Add: - ID: [WIFI_SCAN_0301, WIFI_SCAN_0303, WIFI_SCAN_0304, WIFI_SCAN_0302, WIFI_SCAN_0201, - WIFI_PHY_0403, WIFI_PHY_0402, WIFI_PHY_0401, WIFI_PHY_0407, WIFI_PHY_0406, WIFI_PHY_0405, - WIFI_PHY_0404, WIFI_PHY_0408] + ID: [WIFI_SCAN_0201, WIFI_PHY_0403, WIFI_PHY_0402, WIFI_PHY_0401, WIFI_PHY_0407, + WIFI_PHY_0406, WIFI_PHY_0405, WIFI_PHY_0404, WIFI_PHY_0408] From 6eaf595a0e7e66ecebd64ab153e3651e657d5c5b Mon Sep 17 00:00:00 2001 From: Yinling Date: Wed, 19 Oct 2016 18:00:27 +0800 Subject: [PATCH 032/149] change test WAN server URL: form iot.espressif.cn to factory.espressif.cn --- .../idf_test/integration_test/TestCaseAll.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/components/idf_test/integration_test/TestCaseAll.yml b/components/idf_test/integration_test/TestCaseAll.yml index a7f63a185..315682c3f 100644 --- a/components/idf_test/integration_test/TestCaseAll.yml +++ b/components/idf_test/integration_test/TestCaseAll.yml @@ -1143,7 +1143,7 @@ test cases: category: Function cmd set: - '' - - - SSC SSC1 soc -H -d iot.espressif.cn + - - SSC SSC1 soc -H -d factory.espressif.cn - ['R SSC1 A :\+HOSTIP:OK,(.+)\r\n'] - - SSC SSC1 soc -B -t TCP - ['R SSC1 A :\+BIND:(\d+),OK'] @@ -1178,7 +1178,7 @@ test cases: 1 SSC target connect with PC by UART.' test point 1: basic function test point 2: DNS function test - version: v1 (2016-8-15) + version: v2 (2016-10-19) - CI ready: 'Yes' ID: TCPIP_DNS_0103 SDK: '8266_NonOS @@ -1192,7 +1192,7 @@ test cases: category: Function cmd set: - '' - - - SSC SSC1 soc -H -d iot.espressif.cn + - - SSC SSC1 soc -H -d factory.espressif.cn - ['R SSC1 A :\+HOSTIP:OK,(.+)\r\n'] - - SSC SSC1 soc -B -t UDP - ['R SSC1 A :\+BIND:(\d+),OK'] @@ -1223,7 +1223,7 @@ test cases: 1 SSC target connect with PC by UART.' test point 1: basic function test point 2: DNS function test - version: v1 (2016-8-15) + version: v2 (2016-10-19) - CI ready: 'Yes' ID: TCPIP_ICMP_0101 SDK: '8266_NonOS @@ -9144,7 +9144,7 @@ test cases: category: Function cmd set: - '' - - - SSC SSC1 soc -H -d iot.espressif.cn + - - SSC SSC1 soc -H -d factory.espressif.cn - ['R SSC1 A :\+HOSTIP:OK,(.+)\r\n'] - - SSC SSC1 soc -B -t TCP - ['R SSC1 A :\+BIND:(\d+),OK'] @@ -9179,7 +9179,7 @@ test cases: 1 SSC target connect with PC by UART.' test point 1: basic function test point 2: DNS function test - version: v1 (2016-8-15) + version: v2 (2016-10-19) - CI ready: 'Yes' ID: ^TCPIP_DNS_0103 SDK: '8266_NonOS @@ -9193,7 +9193,7 @@ test cases: category: Function cmd set: - '' - - - SSC SSC1 soc -H -d iot.espressif.cn + - - SSC SSC1 soc -H -d factory.espressif.cn - ['R SSC1 A :\+HOSTIP:OK,(.+)\r\n'] - - SSC SSC1 soc -B -t UDP - ['R SSC1 A :\+BIND:(\d+),OK'] @@ -9224,7 +9224,7 @@ test cases: 1 SSC target connect with PC by UART.' test point 1: basic function test point 2: DNS function test - version: v1 (2016-8-15) + version: v2 (2016-10-19) - CI ready: 'Yes' ID: ^TCPIP_ICMP_0101 SDK: '8266_NonOS From 18ebc6411e811393edcfbbb24e1d9ae9c936f5f0 Mon Sep 17 00:00:00 2001 From: Yinling Date: Wed, 19 Oct 2016 18:03:43 +0800 Subject: [PATCH 033/149] fix bug for WIFI_SCAN_0304: need to set AP config first --- components/idf_test/integration_test/TestCaseAll.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/idf_test/integration_test/TestCaseAll.yml b/components/idf_test/integration_test/TestCaseAll.yml index 315682c3f..a98b89b40 100644 --- a/components/idf_test/integration_test/TestCaseAll.yml +++ b/components/idf_test/integration_test/TestCaseAll.yml @@ -8046,6 +8046,8 @@ test cases: category: Function cmd set: - '' + - - SSC SSC1 ap -S -s -p -t + - ['R SSC1 C +SAP:OK'] - - SSC SSC2 sta -C -s -p - ['R SSC2 C +JAP:OK'] - - SSC SSC1 sta -S From c350972eacc85ae1ead7d71ffe8dc26b057b5b15 Mon Sep 17 00:00:00 2001 From: Yinling Date: Wed, 19 Oct 2016 20:22:43 +0800 Subject: [PATCH 034/149] update following know issues: 1. ^WIFI_CONN_0801: auth change event not correct 2. WIFI_SCAN_0303: scan interact with JAP not correct 3. ^WIFI_SCAN_0103,^WIFI_SCAN_0105: no scan done event 4. ^TCPIP_UDP_0304,TCPIP_UDP_0104: UDP poor performance --- components/idf_test/integration_test/KnownIssues | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/components/idf_test/integration_test/KnownIssues b/components/idf_test/integration_test/KnownIssues index 0c48a811e..5e8143040 100644 --- a/components/idf_test/integration_test/KnownIssues +++ b/components/idf_test/integration_test/KnownIssues @@ -45,6 +45,7 @@ WIFI_PHY_0506 # auth change event WIFI_CONN_0801 +^WIFI_CONN_0801 # disconnect reason WIFI_CONN_0904 @@ -56,6 +57,11 @@ WIFI_CONN_0901 WIFI_CONN_0104 ^WIFI_CONN_0104 +# Wifi scan issue +WIFI_SCAN_0303 +^WIFI_SCAN_0103 +^WIFI_SCAN_0105 + # DHCP issues ^TCPIP_DHCP_0301 TCPIP_DHCP_0301 @@ -88,6 +94,8 @@ TCPIP_UDP_0110 ^TCPIP_UDP_0110 TCPIP_UDP_0305 ^TCPIP_UDP_0305 +^TCPIP_UDP_0304 +TCPIP_UDP_0104 From 2d393f05300b6050788561ba1dada185c27917f8 Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Thu, 20 Oct 2016 11:23:59 +0800 Subject: [PATCH 035/149] Change inline assembly bits from macros to inline functions --- .../freertos/include/freertos/portable.h | 9 ++++++++- .../freertos/include/freertos/portmacro.h | 20 +++++++++++++++++++ components/freertos/port.c | 19 ------------------ 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/components/freertos/include/freertos/portable.h b/components/freertos/include/freertos/portable.h index 58e690366..a3d39bd5a 100644 --- a/components/freertos/include/freertos/portable.h +++ b/components/freertos/include/freertos/portable.h @@ -192,7 +192,14 @@ void vPortEndScheduler( void ) PRIVILEGED_FUNCTION; #endif /* Multi-core: get current core ID */ -#define xPortGetCoreID() __extension__({int id; asm volatile("rsr.prid %0; extui %0,%0,13,1":"=r"(id)); id;}) +inline uint32_t xPortGetCoreID() { + int id; + asm volatile( + "rsr.prid %0\n" + " extui %0,%0,13,1" + :"=r"(id)); + return id; +} #ifdef __cplusplus } diff --git a/components/freertos/include/freertos/portmacro.h b/components/freertos/include/freertos/portmacro.h index ab83b0e05..5e2386d72 100644 --- a/components/freertos/include/freertos/portmacro.h +++ b/components/freertos/include/freertos/portmacro.h @@ -225,6 +225,26 @@ static inline unsigned portENTER_CRITICAL_NESTED() { unsigned state = XTOS_SET_I #define portCLEAR_INTERRUPT_MASK_FROM_ISR(state) portEXIT_CRITICAL_NESTED(state) +/* + * Wrapper for the Xtensa compare-and-set instruction. This subroutine will atomically compare + * *mux to compare, and if it's the same, will set *mux to set. It will return the old value + * of *addr in *set. + * + * Warning: From the ISA docs: in some (unspecified) cases, the s32c1i instruction may return the + * *bitwise inverse* of the old mem if the mem wasn't written. This doesn't seem to happen on the + * ESP32, though. (Would show up directly if it did because the magic wouldn't match.) + */ +inline void uxPortCompareSet(volatile uint32_t *addr, uint32_t compare, uint32_t *set) { + __asm__ __volatile__( + "WSR %2,SCOMPARE1 \n" + "ISYNC \n" + "S32C1I %0, %1, 0 \n" + :"=r"(*set) + :"r"(addr), "r"(compare), "0"(*set) + ); +} + + /*-----------------------------------------------------------*/ /* Architecture specifics. */ diff --git a/components/freertos/port.c b/components/freertos/port.c index ef72b1cb5..a982db7d4 100644 --- a/components/freertos/port.c +++ b/components/freertos/port.c @@ -253,25 +253,6 @@ void vPortAssertIfInISR() configASSERT(port_interruptNesting[xPortGetCoreID()]==0) } - -/* - * Wrapper for the Xtensa compare-and-set instruction. This subroutine will atomically compare - * *mux to compare, and if it's the same, will set *mux to set. It will return the old value - * of *addr. - * - * Warning: From the ISA docs: in some (unspecified) cases, the s32c1i instruction may return the - * *bitwise inverse* of the old mem if the mem wasn't written. This doesn't seem to happen on the - * ESP32, though. (Would show up directly if it did because the magic wouldn't match.) - */ -#define uxPortCompareSet(mux, compare, set) \ - __asm__ __volatile__( \ - "WSR %2,SCOMPARE1 \n" \ - "ISYNC \n" \ - "S32C1I %0, %1, 0 \n" \ - :"=r"(*set) \ - :"r"(mux), "r"(compare), "0"(*set) \ - ); \ - /* * For kernel use: Initialize a per-CPU mux. Mux will be initialized unlocked. */ From 5e8849d03206b83f19532d38c6eabffeacd9b573 Mon Sep 17 00:00:00 2001 From: Yinling Date: Thu, 20 Oct 2016 11:28:06 +0800 Subject: [PATCH 036/149] modify TCPIP_DHCP_0211: delay 30s to make sure STA got enough time to disconnect and reconnect. --- components/idf_test/integration_test/TestCaseAll.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/idf_test/integration_test/TestCaseAll.yml b/components/idf_test/integration_test/TestCaseAll.yml index a98b89b40..3b2242787 100644 --- a/components/idf_test/integration_test/TestCaseAll.yml +++ b/components/idf_test/integration_test/TestCaseAll.yml @@ -880,7 +880,7 @@ test cases: - ['R SSC1 C +DHCP:AP,OK'] - - SSC SSC2 sta -C -s -p - ['R SSC2 C +JAP:CONNECTED'] - - - DELAY 10 + - - DELAY 30 - [''] - - SSC SSC1 ap -L - [R SSC1 C 192.168.4.2 C 192.168.4.3 P P ] @@ -8886,7 +8886,7 @@ test cases: - ['R SSC1 C +DHCP:AP,OK'] - - SSC SSC2 sta -C -s -p - ['R SSC2 C +JAP:CONNECTED'] - - - DELAY 10 + - - DELAY 30 - [''] - - SSC SSC1 ap -L - [R SSC1 C 192.168.4.2 C 192.168.4.3 P P ] From 39a06319e245b7b4bd9ec5f8dbfe12d00a1c7940 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Thu, 20 Oct 2016 16:10:51 +0800 Subject: [PATCH 037/149] build system: use -Og instead of -O0 for debug builds, expand help text in menuconfig --- Kconfig | 5 ++++- make/project.mk | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Kconfig b/Kconfig index 936181a9c..97da1f01f 100644 --- a/Kconfig +++ b/Kconfig @@ -27,7 +27,10 @@ choice OPTIMIZATION_LEVEL prompt "Optimization level" default OPTIMIZATION_LEVEL_DEBUG help - This option sets compiler optimization level. + This option sets optimization level. + For "Release" setting, -Os flag is added to CFLAGS, + and -DNDEBUG flag is added to CPPFLAGS. + For "Debug" setting, -Og flag is added to CFLAGS. config OPTIMIZATION_LEVEL_DEBUG bool "Debug" config OPTIMIZATION_LEVEL_RELEASE diff --git a/make/project.mk b/make/project.mk index 9088eda85..b646dfc41 100644 --- a/make/project.mk +++ b/make/project.mk @@ -179,7 +179,7 @@ ifneq ("$(CONFIG_OPTIMIZATION_LEVEL_RELEASE)","") OPTIMIZATION_FLAGS = -Os CPPFLAGS += -DNDEBUG else -OPTIMIZATION_FLAGS = -O0 +OPTIMIZATION_FLAGS = -Og endif # Enable generation of debugging symbols From d3d8c0453516e9a2c6b8a340086eef63e7ee0cbe Mon Sep 17 00:00:00 2001 From: Yinling Date: Thu, 20 Oct 2016 16:43:04 +0800 Subject: [PATCH 038/149] add known issue exception when setting mac address: WIFI_ADDR_0101,^WIFI_ADDR_0101 --- components/idf_test/integration_test/KnownIssues | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/idf_test/integration_test/KnownIssues b/components/idf_test/integration_test/KnownIssues index 5e8143040..85b5b2f21 100644 --- a/components/idf_test/integration_test/KnownIssues +++ b/components/idf_test/integration_test/KnownIssues @@ -62,6 +62,10 @@ WIFI_SCAN_0303 ^WIFI_SCAN_0103 ^WIFI_SCAN_0105 +# set mac address may lead to exception +WIFI_ADDR_0101 +^WIFI_ADDR_0101 + # DHCP issues ^TCPIP_DHCP_0301 TCPIP_DHCP_0301 From dfe0dcaed477023443bda6fa2319132b1f5bf20f Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Thu, 20 Oct 2016 17:17:54 +0800 Subject: [PATCH 039/149] build system: fix setting C**FLAGS from project makefile --- Kconfig | 10 ++++++++-- make/project.mk | 28 +++++++++++++++++++++------- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/Kconfig b/Kconfig index 97da1f01f..deb0cea83 100644 --- a/Kconfig +++ b/Kconfig @@ -28,9 +28,15 @@ choice OPTIMIZATION_LEVEL default OPTIMIZATION_LEVEL_DEBUG help This option sets optimization level. - For "Release" setting, -Os flag is added to CFLAGS, + + - for "Release" setting, -Os flag is added to CFLAGS, and -DNDEBUG flag is added to CPPFLAGS. - For "Debug" setting, -Og flag is added to CFLAGS. + + - for "Debug" setting, -Og flag is added to CFLAGS. + + To override any of these settings, set CFLAGS and/or CPPFLAGS + in project makefile, before including $(IDF_PATH)/make/project.mk. + config OPTIMIZATION_LEVEL_DEBUG bool "Debug" config OPTIMIZATION_LEVEL_RELEASE diff --git a/make/project.mk b/make/project.mk index b646dfc41..887086fd4 100644 --- a/make/project.mk +++ b/make/project.mk @@ -149,16 +149,16 @@ LDFLAGS ?= -nostdlib \ -Wl,-EL # Set default CPPFLAGS, CFLAGS, CXXFLAGS -# # These are exported so that components can use them when compiling. -# # If you need your component to add CFLAGS/etc for it's own source compilation only, set CFLAGS += in your component's Makefile. -# # If you need your component to add CFLAGS/etc globally for all source -# files, set CFLAGS += in your component's Makefile.projbuild +# files, set CFLAGS += in your component's Makefile.projbuild +# If you need to set CFLAGS/CPPFLAGS/CXXFLAGS at project level, set them in application Makefile +# before including project.mk. Default flags will be added before the ones provided in application Makefile. # CPPFLAGS used by C preprocessor -CPPFLAGS = -DESP_PLATFORM +# If any flags are defined in application Makefile, add them at the end. +CPPFLAGS := -DESP_PLATFORM $(CPPFLAGS) # Warnings-related flags relevant both for C and C++ COMMON_WARNING_FLAGS = -Wall -Werror \ @@ -186,10 +186,24 @@ endif OPTIMIZATION_FLAGS += -ggdb # List of flags to pass to C compiler -CFLAGS = -std=gnu99 $(strip $(OPTIMIZATION_FLAGS) $(COMMON_FLAGS) $(COMMON_WARNING_FLAGS)) +# If any flags are defined in application Makefile, add them at the end. +CFLAGS := $(strip \ + -std=gnu99 \ + $(OPTIMIZATION_FLAGS) \ + $(COMMON_FLAGS) \ + $(COMMON_WARNING_FLAGS) \ + $(CFLAGS)) # List of flags to pass to C++ compiler -CXXFLAGS = -std=gnu++11 -fno-exceptions -fno-rtti $(strip $(OPTIMIZATION_FLAGS) $(COMMON_FLAGS) $(COMMON_WARNING_FLAGS)) +# If any flags are defined in application Makefile, add them at the end. +CXXFLAGS := $(strip \ + -std=gnu++11 \ + -fno-exceptions \ + -fno-rtti \ + $(OPTIMIZATION_FLAGS) \ + $(COMMON_FLAGS) \ + $(COMMON_WARNING_FLAGS) \ + $(CXXFLAGS)) export CFLAGS CPPFLAGS CXXFLAGS From 6b8504005920535764a51d8ec4e9a392e3fcb178 Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Thu, 20 Oct 2016 20:11:13 +0800 Subject: [PATCH 040/149] Also export HOSTCC etc for components --- make/project.mk | 1 + 1 file changed, 1 insertion(+) diff --git a/make/project.mk b/make/project.mk index 2b2eff3f5..d822d6a21 100644 --- a/make/project.mk +++ b/make/project.mk @@ -176,6 +176,7 @@ HOSTCC := $(CC) HOSTLD := $(LD) HOSTAR := $(AR) HOSTOBJCOPY := $(OBJCOPY) +export HOSTCC HOSTLD HOSTAR HOSTOBJCOPY #Set target compiler. Defaults to whatever the user has #configured as prefix + yer olde gcc commands From feca308f1f13f5191a510064fc1a6c00a6235da2 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 21 Oct 2016 10:44:05 +1100 Subject: [PATCH 041/149] rom/spi_flash.h: Remove first parameter of SPI_read_status_high() Corrects the prototype to match the one compiled into ROM. --- components/esp32/include/rom/spi_flash.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/esp32/include/rom/spi_flash.h b/components/esp32/include/rom/spi_flash.h index ba8eebc2c..d182b51ca 100644 --- a/components/esp32/include/rom/spi_flash.h +++ b/components/esp32/include/rom/spi_flash.h @@ -218,7 +218,7 @@ void SelectSpiFunction(uint32_t ishspi); void spi_flash_attach(uint32_t ishspi, bool legacy); /** - * @brief SPI Read Flash status register. We use CMD 0x05. + * @brief SPI Read Flash status register. We use CMD 0x05 (RDSR). * Please do not call this function in SDK. * * @param SpiFlashChip *spi : The information for Flash, which is exported from ld file. @@ -232,7 +232,7 @@ void spi_flash_attach(uint32_t ishspi, bool legacy); SpiFlashOpResult SPI_read_status(SpiFlashChip *spi, uint32_t *status); /** - * @brief SPI Read Flash status register high 16 bit. We use CMD 0x35. + * @brief SPI Read Flash status register bits 8-15. We use CMD 0x35 (RDSR2). * Please do not call this function in SDK. * * @param SpiFlashChip *spi : The information for Flash, which is exported from ld file. @@ -243,7 +243,7 @@ SpiFlashOpResult SPI_read_status(SpiFlashChip *spi, uint32_t *status); * SPI_FLASH_RESULT_ERR : read error. * SPI_FLASH_RESULT_TIMEOUT : read timeout. */ -SpiFlashOpResult SPI_read_status_high(SpiFlashChip *spi, uint32_t *status); +SpiFlashOpResult SPI_read_status_high(uint32_t *status); /** * @brief Write status to Falsh status register. From 7104284e31aff45949bd087a7a278ca58751eef6 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 21 Oct 2016 15:26:11 +1100 Subject: [PATCH 042/149] Bump esptool version Incorporates fix for locked flash #50 --- components/esptool_py/esptool | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esptool_py/esptool b/components/esptool_py/esptool index 197ba605f..5c6962e89 160000 --- a/components/esptool_py/esptool +++ b/components/esptool_py/esptool @@ -1 +1 @@ -Subproject commit 197ba605fe0c05e16bf4c5ec07b726adc8d86abc +Subproject commit 5c6962e894e0a118c9a4b5760876433493449260 From 42827ff8693e229bd541c295651f3ef96aa6012d Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Tue, 18 Oct 2016 19:03:59 +0800 Subject: [PATCH 043/149] bootloader, menuconfig: add flash size setting support --- .../bootloader/src/main/bootloader_config.h | 2 +- .../bootloader/src/main/bootloader_start.c | 37 ++++++++++++++++++- components/esp32/include/rom/spi_flash.h | 6 +++ components/esp32/ld/esp32.rom.ld | 1 + components/esptool_py/Kconfig.projbuild | 27 ++++++++++++++ components/esptool_py/Makefile.projbuild | 7 +++- 6 files changed, 75 insertions(+), 5 deletions(-) diff --git a/components/bootloader/src/main/bootloader_config.h b/components/bootloader/src/main/bootloader_config.h index 709ff41b1..f99a1c94e 100644 --- a/components/bootloader/src/main/bootloader_config.h +++ b/components/bootloader/src/main/bootloader_config.h @@ -51,7 +51,7 @@ enum { SPI_SPEED_20M, SPI_SPEED_80M = 0xF }; -/*suppport flash size in esp32 */ +/*supported flash sizes*/ enum { SPI_SIZE_1MB = 0, SPI_SIZE_2MB, diff --git a/components/bootloader/src/main/bootloader_start.c b/components/bootloader/src/main/bootloader_start.c index a61ea77d5..e87f579f4 100644 --- a/components/bootloader/src/main/bootloader_start.c +++ b/components/bootloader/src/main/bootloader_start.c @@ -58,6 +58,7 @@ void IRAM_ATTR set_cache_and_start_app(uint32_t drom_addr, uint32_t irom_load_addr, uint32_t irom_size, uint32_t entry_addr); +static void update_flash_config(struct flash_hdr* pfhdr); void IRAM_ATTR call_start_cpu0() @@ -258,7 +259,7 @@ void bootloader_main() memset(&bs, 0, sizeof(bs)); ESP_LOGI(TAG, "compile time " __TIME__ ); - /* close watch dog here */ + /* disable watch dog here */ REG_CLR_BIT( RTC_CNTL_WDTCONFIG0_REG, RTC_CNTL_WDT_FLASHBOOT_MOD_EN ); REG_CLR_BIT( TIMG_WDTCONFIG0_REG(0), TIMG_WDT_FLASHBOOT_MOD_EN ); SPIUnlock(); @@ -269,6 +270,8 @@ void bootloader_main() print_flash_info(&fhdr); + update_flash_config(&fhdr); + if (!load_partition_table(&bs, PARTITION_ADD)) { ESP_LOGE(TAG, "load partition table error!"); return; @@ -364,7 +367,7 @@ void unpack_load_app(const partition_pos_t* partition) uint32_t irom_size = 0; /* Reload the RTC memory sections whenever a non-deepsleep reset - is occuring */ + is occurring */ bool load_rtc_memory = rtc_get_reset_reason(0) != DEEPSLEEP_RESET; ESP_LOGD(TAG, "bin_header: %u %u %u %u %08x", image_header.magic, @@ -482,6 +485,36 @@ void IRAM_ATTR set_cache_and_start_app( (*entry)(); } +static void update_flash_config(struct flash_hdr* pfhdr) +{ + uint32_t size; + switch(pfhdr->spi_size) { + case SPI_SIZE_1MB: + size = 1; + break; + case SPI_SIZE_2MB: + size = 2; + break; + case SPI_SIZE_4MB: + size = 4; + break; + case SPI_SIZE_8MB: + size = 8; + break; + case SPI_SIZE_16MB: + size = 16; + break; + default: + size = 2; + } + Cache_Read_Disable( 0 ); + // Set flash chip size + SPIParamCfg(g_rom_flashchip.deviceId, size * 0x100000, 0x10000, 0x1000, 0x100, 0xffff); + // TODO: set mode + // TODO: set frequency + Cache_Flush(0); + Cache_Read_Enable( 0 ); +} void print_flash_info(struct flash_hdr* pfhdr) { diff --git a/components/esp32/include/rom/spi_flash.h b/components/esp32/include/rom/spi_flash.h index d182b51ca..1f14c6617 100644 --- a/components/esp32/include/rom/spi_flash.h +++ b/components/esp32/include/rom/spi_flash.h @@ -503,6 +503,12 @@ void SPI_Write_Encrypt_Disable(void); */ SpiFlashOpResult SPI_Encrypt_Write(uint32_t flash_addr, uint32_t *data, uint32_t len); + +/** @brief Global SpiFlashChip structure used by ROM functions + * + */ +extern SpiFlashChip g_rom_flashchip; + /** * @} */ diff --git a/components/esp32/ld/esp32.rom.ld b/components/esp32/ld/esp32.rom.ld index cc14d3258..0fa28397e 100644 --- a/components/esp32/ld/esp32.rom.ld +++ b/components/esp32/ld/esp32.rom.ld @@ -286,6 +286,7 @@ PROVIDE ( _global_impure_ptr = 0x3ffae0b0 ); PROVIDE ( gmtime = 0x40059848 ); PROVIDE ( gmtime_r = 0x40059868 ); PROVIDE ( g_phyFuns_instance = 0x3ffae0c4 ); +PROVIDE ( g_rom_flashchip = 0x3ffae270 ); PROVIDE ( gpio_init = 0x40009c20 ); PROVIDE ( gpio_input_get = 0x40009b88 ); PROVIDE ( gpio_input_get_high = 0x40009b9c ); diff --git a/components/esptool_py/Kconfig.projbuild b/components/esptool_py/Kconfig.projbuild index 3da802296..8bab51225 100644 --- a/components/esptool_py/Kconfig.projbuild +++ b/components/esptool_py/Kconfig.projbuild @@ -94,4 +94,31 @@ config ESPTOOLPY_FLASHFREQ default "26m" if ESPTOOLPY_FLASHFREQ_26M default "20m" if ESPTOOLPY_FLASHFREQ_20M + +choice ESPTOOLPY_FLASHSIZE + prompt "Flash size" + default ESPTOOLPY_FLASHSIZE_2MB + help + SPI flash size, in megabytes + +config ESPTOOLPY_FLASHSIZE_1MB + bool "1 MB" +config ESPTOOLPY_FLASHSIZE_2MB + bool "2 MB" +config ESPTOOLPY_FLASHSIZE_4MB + bool "4 MB" +config ESPTOOLPY_FLASHSIZE_8MB + bool "8 MB" +config ESPTOOLPY_FLASHSIZE_16MB + bool "16 MB" +endchoice + +config ESPTOOLPY_FLASHSIZE + string + default "1MB" if ESPTOOLPY_FLASHSIZE_1MB + default "2MB" if ESPTOOLPY_FLASHSIZE_2MB + default "4MB" if ESPTOOLPY_FLASHSIZE_4MB + default "8MB" if ESPTOOLPY_FLASHSIZE_8MB + default "16MB" if ESPTOOLPY_FLASHSIZE_16MB + endmenu diff --git a/components/esptool_py/Makefile.projbuild b/components/esptool_py/Makefile.projbuild index 4d0dd1b3e..69c01e1e7 100644 --- a/components/esptool_py/Makefile.projbuild +++ b/components/esptool_py/Makefile.projbuild @@ -4,6 +4,7 @@ ESPPORT ?= $(CONFIG_ESPTOOLPY_PORT) ESPBAUD ?= $(CONFIG_ESPTOOLPY_BAUD) ESPFLASHMODE ?= $(CONFIG_ESPTOOLPY_FLASHMODE) ESPFLASHFREQ ?= $(CONFIG_ESPTOOLPY_FLASHFREQ) +ESPFLASHSIZE ?= $(CONFIG_ESPTOOLPY_FLASHSIZE) PYTHON ?= $(call dequote,$(CONFIG_PYTHON)) @@ -15,13 +16,15 @@ ESPTOOLPY_SRC := $(COMPONENT_PATH)/esptool/esptool.py ESPTOOLPY := $(PYTHON) $(ESPTOOLPY_SRC) --chip esp32 ESPTOOLPY_SERIAL := $(ESPTOOLPY) --port $(ESPPORT) --baud $(ESPBAUD) +ESPTOOL_FLASH_OPTIONS := --flash_mode $(ESPFLASHMODE) --flash_freq $(ESPFLASHFREQ) --flash_size $(ESPFLASHSIZE) + # the no-stub argument is temporary until esptool.py fully supports compressed uploads -ESPTOOLPY_WRITE_FLASH=$(ESPTOOLPY_SERIAL) write_flash $(if $(CONFIG_ESPTOOLPY_COMPRESSED),-z) --flash_mode $(ESPFLASHMODE) --flash_freq $(ESPFLASHFREQ) +ESPTOOLPY_WRITE_FLASH=$(ESPTOOLPY_SERIAL) write_flash $(if $(CONFIG_ESPTOOLPY_COMPRESSED),-z) $(ESPTOOL_FLASH_OPTIONS) ESPTOOL_ALL_FLASH_ARGS += $(CONFIG_APP_OFFSET) $(APP_BIN) $(APP_BIN): $(APP_ELF) $(ESPTOOLPY_SRC) - $(Q) $(ESPTOOLPY) elf2image --flash_mode $(ESPFLASHMODE) --flash_freq $(ESPFLASHFREQ) -o $@ $< + $(Q) $(ESPTOOLPY) elf2image $(ESPTOOL_FLASH_OPTIONS) -o $@ $< flash: all_binaries $(ESPTOOLPY_SRC) @echo "Flashing binaries to serial port $(ESPPORT) (app at offset $(CONFIG_APP_OFFSET))..." From 8e8caca2e2dc0bdad5966e59026e2f58975c8621 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 21 Oct 2016 16:02:06 +1100 Subject: [PATCH 044/149] Replace ROM SPIUnlock function with a version that can't lock flash Avoid bug where a bad status read is copied back to flash and can set lock bits. --- components/esp32/ld/esp32.rom.ld | 2 +- components/spi_flash/spi_flash_rom_patch.c | 72 ++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 components/spi_flash/spi_flash_rom_patch.c diff --git a/components/esp32/ld/esp32.rom.ld b/components/esp32/ld/esp32.rom.ld index 0fa28397e..4b0c1c17e 100644 --- a/components/esp32/ld/esp32.rom.ld +++ b/components/esp32/ld/esp32.rom.ld @@ -1598,7 +1598,7 @@ PROVIDE ( SPI_read_status = 0x4006226c ); PROVIDE ( SPI_read_status_high = 0x40062448 ); PROVIDE ( SPIUnlock = 0x400628b0 ); PROVIDE ( SPI_user_command_read = 0x400621b0 ); -PROVIDE ( spi_w25q16 = 0x3ffae270 ); +PROVIDE ( SPI_flashchip_data = 0x3ffae270 ); PROVIDE ( SPIWrite = 0x40062d50 ); /* This is static function, but can be used, not generated by script*/ PROVIDE ( SPI_write_enable = 0x40062320 ); diff --git a/components/spi_flash/spi_flash_rom_patch.c b/components/spi_flash/spi_flash_rom_patch.c new file mode 100644 index 000000000..7e23beaea --- /dev/null +++ b/components/spi_flash/spi_flash_rom_patch.c @@ -0,0 +1,72 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "rom/spi_flash.h" +#include "soc/spi_reg.h" + +static const uint32_t STATUS_QIE_BIT = (1 << 9); /* Quad Enable */ + +#define SPI_IDX 1 +#define OTH_IDX 0 + +extern SpiFlashChip SPI_flashchip_data; + +static void IRAM_ATTR Wait_SPI_Idle(void) +{ + /* Wait for SPI state machine to be idle */ + while((REG_READ(SPI_EXT2_REG(SPI_IDX)) & SPI_ST)) { + } + while(REG_READ(SPI_EXT2_REG(OTH_IDX)) & SPI_ST) { + } +} + +/* Modified version of SPIUnlock() that replaces version in ROM. + + This works around a bug where SPIUnlock sometimes reads the wrong + high status byte (RDSR2 result) and then copies it back to the + flash status, which can cause the CMP bit or Status Register + Protect bit to become set. + + Like other ROM SPI functions, this function is not designed to be + called directly from an RTOS environment without taking precautions + about interrupts, CPU coordination, flash mapping. However some of + the functions in esp_spi_flash.c call it. + */ +SpiFlashOpResult IRAM_ATTR SPIUnlock(void) +{ + uint32_t status; + + Wait_SPI_Idle(); + + if (SPI_read_status_high(&status) != SPI_FLASH_RESULT_OK) { + return SPI_FLASH_RESULT_ERR; + } + + /* Clear all bits except QIE, if it is set. + (This is different from ROM SPIUnlock, which keeps all bits as-is.) + */ + status &= STATUS_QIE_BIT; + + Wait_SPI_Idle(); + REG_WRITE(SPI_CMD_REG(SPI_IDX), SPI_FLASH_WREN); + while(REG_READ(SPI_CMD_REG(SPI_IDX)) != 0) { + } + Wait_SPI_Idle(); + + SET_PERI_REG_MASK(SPI_CTRL_REG(SPI_IDX), SPI_WRSR_2B); + if (SPI_write_status(&SPI_flashchip_data, status) != SPI_FLASH_RESULT_OK) { + return SPI_FLASH_RESULT_ERR; + } + + return SPI_FLASH_RESULT_OK; +} From f37e70ebd60a3b807a09299006ba94c3f32f65dc Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 21 Oct 2016 17:44:34 +1100 Subject: [PATCH 045/149] Bootloader: Export IS_BOOTLOADER_BUILD during make process --- components/bootloader/Makefile.projbuild | 2 +- components/bootloader/src/Makefile | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/components/bootloader/Makefile.projbuild b/components/bootloader/Makefile.projbuild index 91be3a6d6..50c95f9fe 100644 --- a/components/bootloader/Makefile.projbuild +++ b/components/bootloader/Makefile.projbuild @@ -8,7 +8,7 @@ # basically runs Make in the src/ directory but it needs to zero some variables # the ESP-IDF project.mk makefile exports first, to not let them interfere. # -ifeq ("$(IS_BOOTLOADER_BUILD)","") +ifndef IS_BOOTLOADER_BUILD BOOTLOADER_COMPONENT_PATH := $(COMPONENT_PATH) BOOTLOADER_BUILD_DIR=$(abspath $(BUILD_DIR_BASE)/bootloader) diff --git a/components/bootloader/src/Makefile b/components/bootloader/src/Makefile index f30e314a5..ddf664d44 100644 --- a/components/bootloader/src/Makefile +++ b/components/bootloader/src/Makefile @@ -10,6 +10,7 @@ COMPONENTS := esptool_py bootloader log # # IS_BOOTLOADER_BUILD tells the component Makefile.projbuild to be a no-op IS_BOOTLOADER_BUILD := 1 +export IS_BOOTLOADER_BUILD #We cannot include the esp32 component directly but we need its includes. #This is fixed by adding CFLAGS from Makefile.projbuild From 1413ec3ff0ba9409bbb9f839be644be25543f20f Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 21 Oct 2016 17:08:05 +1100 Subject: [PATCH 046/149] Remove SPIUnlock from linker script symbols Add a comment about why it was removed and where it went. --- components/bootloader/src/Makefile | 2 +- components/esp32/ld/esp32.rom.ld | 3 ++- components/spi_flash/component.mk | 5 +++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/components/bootloader/src/Makefile b/components/bootloader/src/Makefile index ddf664d44..add9c15d6 100644 --- a/components/bootloader/src/Makefile +++ b/components/bootloader/src/Makefile @@ -4,7 +4,7 @@ # PROJECT_NAME := bootloader -COMPONENTS := esptool_py bootloader log +COMPONENTS := esptool_py bootloader log spi_flash # The bootloader pseudo-component is also included in this build, for its Kconfig.projbuild to be included. # diff --git a/components/esp32/ld/esp32.rom.ld b/components/esp32/ld/esp32.rom.ld index 4b0c1c17e..5266a679b 100644 --- a/components/esp32/ld/esp32.rom.ld +++ b/components/esp32/ld/esp32.rom.ld @@ -1585,6 +1585,8 @@ PROVIDE ( SPIEraseBlock = 0x40062c4c ); PROVIDE ( SPIEraseChip = 0x40062c14 ); PROVIDE ( SPIEraseSector = 0x40062ccc ); PROVIDE ( spi_flash_attach = 0x40062a6c ); +/* NB: SPIUnlock @ 0x400628b0 has been replaced with an updated + version in the "spi_flash" component */ PROVIDE ( SPILock = 0x400628f0 ); PROVIDE ( SPIMasterReadModeCnfig = 0x40062b64 ); PROVIDE ( spi_modes = 0x3ff99270 ); @@ -1596,7 +1598,6 @@ PROVIDE ( SPIReadModeCnfig = 0x40062944 ); PROVIDE ( SPI_read_status = 0x4006226c ); /* This is static function, but can be used, not generated by script*/ PROVIDE ( SPI_read_status_high = 0x40062448 ); -PROVIDE ( SPIUnlock = 0x400628b0 ); PROVIDE ( SPI_user_command_read = 0x400621b0 ); PROVIDE ( SPI_flashchip_data = 0x3ffae270 ); PROVIDE ( SPIWrite = 0x40062d50 ); diff --git a/components/spi_flash/component.mk b/components/spi_flash/component.mk index ef497a7ec..459da0641 100755 --- a/components/spi_flash/component.mk +++ b/components/spi_flash/component.mk @@ -1,3 +1,8 @@ COMPONENT_ADD_INCLUDEDIRS := include +ifdef IS_BOOTLOADER_BUILD +# Bootloader needs updated SPIUnlock from this file +COMPONENT_OBJS := spi_flash_rom_patch.o +endif + include $(IDF_PATH)/make/component_common.mk From 53146799a003439e3594c3c1957306889af76aad Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Fri, 21 Oct 2016 17:59:57 +0800 Subject: [PATCH 047/149] Initial addition of wdt and brownout code --- components/esp32/Kconfig | 113 +++++++++++++ components/esp32/brownout.c | 37 ++++ components/esp32/cpu_start.c | 12 ++ components/esp32/include/esp_brownout.h | 6 + components/esp32/include/esp_int_wdt.h | 6 + components/esp32/include/esp_task_wdt.h | 8 + components/esp32/int_wdt.c | 82 +++++++++ components/esp32/task_wdt.c | 158 ++++++++++++++++++ .../include/freertos/FreeRTOSConfig.h | 4 +- 9 files changed, 424 insertions(+), 2 deletions(-) create mode 100644 components/esp32/brownout.c create mode 100644 components/esp32/include/esp_brownout.h create mode 100644 components/esp32/include/esp_int_wdt.h create mode 100644 components/esp32/include/esp_task_wdt.h create mode 100644 components/esp32/int_wdt.c create mode 100644 components/esp32/task_wdt.c diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index 535df23eb..c40d7930d 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -140,4 +140,117 @@ config ULP_COPROC_RESERVE_MEM default 0 depends on !ULP_COPROC_ENABLED +menu "Watchdogs & brown-out detection" + +config INT_WDT + bool "Interrupt watchdog" + default y + help + This watchdog timer can detect if the FreeRTOS tick interrupt has not been called for a certain time, + either because a task turned off interrupts and did not turn them on for a long time, or because an + interrupt handler did not return. It will try to invoke the panic handler first and failing that + reset the SoC. + +config INT_WDT_TIMEOUT_MS_MIN + default (2000/CONFIG_FREERTOS_HZ) + +config INT_WDT_TIMEOUT_MS + int "Interrupt watchdog timeout (ms)" + depends on INT_WDT + default 100 + range INT_WDT_TIMEOUT_MS_MIN 10000 + help + The timeout of the watchdog, in miliseconds. + +config TASK_WDT + bool "Task watchdog" + default y + help + This watchdog timer can be used to make sure individual tasks are still running. + +config TASK_WDT_PANIC + bool "Invoke panic handler when Task Watchdog is triggered" + depends on TASK_WDT + default n + help + Normally, the Task Watchdog will only print out a warning if it detects it has not + been fed. If this is enabled, it will invoke the panic handler instead, which + can then halt or reboot the chip. + +config TASK_WDT_TIMEOUT_S + int "Task watchdog timeout (seconds)" + depends on TASK_WDT + range 1 60 + default 5 + help + Timeout for the task WDT, in seconds. + +config TASK_WDT_CHECK_IDLE_TASK + bool "Task watchdog watches idle tasks" + depends on TASK_WDT + default y + help + With this turned on, the task WDT can detect if the idle task is not called within the task + watchdog timeout period. The idle task not being called usually is a symptom of another + task hoarding the CPU. It is also a bad thing because FreeRTOS household tasks depend on the + idle task getting some runtime every now and then. + +config BROWNOUT_DET + bool "Hardware brownout detect & reset" + default y + help + The ESP32 has a built-in brownout detector which can detect if the voltage is lower than + a specific value. If this happens, it will reset the chip in order to prevent unintended + behaviour. + +choice BROWNOUT_DET_LVL_SEL + prompt "Brownout voltage level" + depends on BROWNOUT_DET + default BROWNOUT_DET_LVL_SEL_25 + help + The brownout detector will reset the chip when the supply voltage is below this level. + +config BROWNOUT_DET_LVL_SEL_0 + bool "2.1V" +config BROWNOUT_DET_LVL_SEL_1 + bool "2.2V" +config BROWNOUT_DET_LVL_SEL_2 + bool "2.3V" +config BROWNOUT_DET_LVL_SEL_3 + bool "2.4V" +config BROWNOUT_DET_LVL_SEL_4 + bool "2.5V" +config BROWNOUT_DET_LVL_SEL_5 + bool "2.6V" +config BROWNOUT_DET_LVL_SEL_6 + bool "2.7V" +config BROWNOUT_DET_LVL_SEL_7 + bool "2.8V" +endchoice + +config BROWNOUT_DET_LVL + int + default 0 if BROWNOUT_DET_LVL_SEL_0 + default 1 if BROWNOUT_DET_LVL_SEL_1 + default 2 if BROWNOUT_DET_LVL_SEL_2 + default 3 if BROWNOUT_DET_LVL_SEL_3 + default 4 if BROWNOUT_DET_LVL_SEL_4 + default 5 if BROWNOUT_DET_LVL_SEL_5 + default 6 if BROWNOUT_DET_LVL_SEL_6 + default 7 if BROWNOUT_DET_LVL_SEL_7 + + +config BROWNOUT_DET_RESETDELAY + int "Brownout reset delay (in uS)" + depends on BROWNOUT_DET + range 0 6820 + default 1000 + help + The brownout detector can reset the chip after a certain delay, in order to make sure e.g. a voltage dip has entirely passed + before trying to restart the chip. You can set the delay here. + +endmenu + + + endmenu diff --git a/components/esp32/brownout.c b/components/esp32/brownout.c new file mode 100644 index 000000000..173d63a0c --- /dev/null +++ b/components/esp32/brownout.c @@ -0,0 +1,37 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +#include +#include +#include +#include +#include "sdkconfig.h" +#include "soc/soc.h" +#include "soc/rtc_cntl_reg.h" + + +void esp_brownout_init() { +// WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, +// RTC_CNTL_BROWN_OUT_ENA | (CONFIG_BROWNOUT_DET_LVL << RTC_CNTL_DBROWN_OUT_THRES_S) | +// RTC_CNTL_BROWN_OUT_RST_ENA | (((CONFIG_BROWNOUT_DET_RESETDELAY*150)/1000) << RTC_CNTL_BROWN_OUT_RST_WAIT_S) | +// RTC_CNTL_BROWN_OUT_PD_RF_ENA|RTC_CNTL_BROWN_OUT_CLOSE_FLASH_ENA); + + + WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, + RTC_CNTL_BROWN_OUT_ENA | (4 << RTC_CNTL_DBROWN_OUT_THRES_S) | + RTC_CNTL_BROWN_OUT_RST_ENA | (0x3FF << RTC_CNTL_BROWN_OUT_RST_WAIT_S) | + RTC_CNTL_BROWN_OUT_PD_RF_ENA | RTC_CNTL_BROWN_OUT_CLOSE_FLASH_ENA); + +} \ No newline at end of file diff --git a/components/esp32/cpu_start.c b/components/esp32/cpu_start.c index 7b2ccdc60..6a482c539 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -43,6 +43,8 @@ #include "esp_ipc.h" #include "esp_log.h" +#include "esp_brownout.h" + void start_cpu0(void) __attribute__((weak, alias("start_cpu0_default"))); void start_cpu0_default(void) IRAM_ATTR; #if !CONFIG_FREERTOS_UNICORE @@ -137,6 +139,16 @@ void start_cpu0_default(void) do_global_ctors(); esp_ipc_init(); spi_flash_init(); +#if CONFIG_BROWNOUT_DET + esp_brownout_init(); +#endif +#if CONFIG_INT_WDT + int_wdt_init() +#endif +#if CONFIG_TASK_WDT + task_wdt_init() +#endif + xTaskCreatePinnedToCore(&main_task, "main", ESP_TASK_MAIN_STACK, NULL, ESP_TASK_MAIN_PRIO, NULL, 0); diff --git a/components/esp32/include/esp_brownout.h b/components/esp32/include/esp_brownout.h new file mode 100644 index 000000000..acce05e0d --- /dev/null +++ b/components/esp32/include/esp_brownout.h @@ -0,0 +1,6 @@ +#ifndef BROWNOUT_H +#define BROWNOUT_H + +void esp_brownout_init(); + +#endif \ No newline at end of file diff --git a/components/esp32/include/esp_int_wdt.h b/components/esp32/include/esp_int_wdt.h new file mode 100644 index 000000000..81404e305 --- /dev/null +++ b/components/esp32/include/esp_int_wdt.h @@ -0,0 +1,6 @@ +#ifndef INT_WDT_H +#define INT_WDT_H + +void int_wdt_init(); + +#endif \ No newline at end of file diff --git a/components/esp32/include/esp_task_wdt.h b/components/esp32/include/esp_task_wdt.h new file mode 100644 index 000000000..9163e6907 --- /dev/null +++ b/components/esp32/include/esp_task_wdt.h @@ -0,0 +1,8 @@ +#ifndef TASK_WDT_H +#define TASK_WDT_H + +void task_wdt_feed(); +void task_wdt_delete(); +void task_wdt_init(); + +#endif \ No newline at end of file diff --git a/components/esp32/int_wdt.c b/components/esp32/int_wdt.c new file mode 100644 index 000000000..392ed6b6a --- /dev/null +++ b/components/esp32/int_wdt.c @@ -0,0 +1,82 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +/* +This routine enables a watchdog to catch instances of processes disabling +interrupts for too long, or code within interrupt handlers taking too long. +It does this by setting up a watchdog which gets fed from the FreeRTOS +task switch interrupt. When this watchdog times out, initially it will call +a high-level interrupt routine that will panic FreeRTOS in order to allow +for forensic examination of the state of the CPU. When this interrupt +handler is not called and the watchdog times out a second time, it will +reset the SoC. + +This uses the TIMERG1 WDT. +*/ + +#include "sdkconfig.h" +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include +#include "esp_err.h" +#include "esp_intr.h" +#include "soc/timer_group_struct.h" + +#include "esp_int_wdt.h" + +#if CONFIG_INT_WDT + + +#define WDT_INT_NUM 24 + + +#define WDT_WRITE_KEY 0x50D83AA1 + +static void esp_int_wdt_isr(void *arg) { + abort(); +} + + +void int_wdt_init() { + TIMERG1.wdt_wprotect=WDT_WRITE_KEY; + TIMERG1.wdt_config0.sys_reset_length=7; //3.2uS + TIMERG1.wdt_config0.cpu_reset_length=7; //3.2uS + TIMERG1.wdt_config0.level_int_en=1; + TIMERG1.wdt_config0.stg0=1; //1st stage timeout: interrupt + TIMERG1.wdt_config0.stg1=3; //2nd stage timeout: reset system + TIMERG1.wdt_config1.clk_prescale=80*500; //Prescaler: wdt counts in ticks of 0.5mS + TIMERG1.wdt_config2=CONFIG_INT_WDT_TIMEOUT_MS*2; //Set timeout before interrupt + TIMERG1.wdt_config3=CONFIG_INT_WDT_TIMEOUT_MS*4; //Set timeout before reset + TIMERG1.wdt_config0.en=1; + TIMERG1.wdt_feed=1; + TIMERG1.wdt_wprotect=0; + ESP_INTR_DISABLE(WDT_INT_NUM); + intr_matrix_set(xPortGetCoreID(), ETS_TG1_WDT_LEVEL_INTR_SOURCE, WDT_INT_NUM); + xt_set_interrupt_handler(WDT_INT_NUM, int_wdt_isr, NULL); + ESP_INTR_ENABLE(WDT_INT_NUM); +} + + + +void vApplicationTickHook(void) { + TIMERG1.wdt_wprotect=WDT_WRITE_KEY; + TIMERG1.wdt_feed=1; + TIMERG1.wdt_wprotect=0; +} + +#endif \ No newline at end of file diff --git a/components/esp32/task_wdt.c b/components/esp32/task_wdt.c new file mode 100644 index 000000000..7ae3dfb2e --- /dev/null +++ b/components/esp32/task_wdt.c @@ -0,0 +1,158 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +/* +This routine enables a more general-purpose task watchdog: tasks can individually +feed the watchdog and the watchdog will bark if one or more tasks haven't fed the +watchdog within the specified time. Optionally, the idle tasks can also configured +to feed the watchdog in a similar fashion, to detect CPU starvation. + +This uses the TIMERG0 WDT. +*/ + +#include +#include +#include +#include +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include +#include "esp_err.h" +#include "esp_intr.h" +#include "soc/timer_group_struct.h" +#include "esp_log.h" + +#include "esp_task_wdt.h" + +#if CONFIG_TASK_WDT + +static const char* TAG = "task_wdt"; + + +typedef struct wdt_task_t wdt_task_t; +struct wdt_task_t { + TaskHandle_t task_handle; + bool fed_watchdog; + wdt_task_t *next; +}; + + +static wdt_task_t *wdt_task_list=NULL; + +#define WDT_INT_NUM 24 + + +#define WDT_WRITE_KEY 0x50D83AA1 + +static void task_wdt_isr(void *arg) { + abort(); +} + + +void task_wdt_feed() { + wdt_task_t *wdttask=wdt_task_list; + bool found_task=false, do_feed_wdt=true; + TaskHandle_t handle=xTaskGetCurrentTaskHandle(); + //Walk the linked list of wdt tasks to find this one, as well as see if we need to feed + //the real watchdog timer. + while (wdttask!=NULL) { + //See if we are at the current task. + if (wdttask->task_handle == handle) { + wdttask->fed_watchdog=true; + found_task=true; + } + //If even one task in the list doesn't have the do_feed_wdt var set, we do not feed the watchdog. + if (!wdttask->fed_watchdog) do_feed_wdt=false; + //Next entry. + wdttask=wdttask->next; + } + + if (!found_task) { + //This is the first time the task calls the task_wdt_feed function. Create a new entry for it in + //the linked list. + wdt_task_t *newtask=malloc(sizeof(wdt_task_t)); + memset(newtask, 0, sizeof(wdt_task_t)); + newtask->task_handle=handle; + newtask->fed_watchdog=true; + if (wdt_task_list == NULL) { + wdt_task_list=newtask; + } else { + wdttask=wdt_task_list; + while (!(wdttask->next == NULL)) wdttask=wdttask->next; + wdttask->next=wdttask; + } + } + if (do_feed_wdt) { + //All tasks have checked in; time to feed the hw watchdog. + TIMERG0.wdt_wprotect=WDT_WRITE_KEY; + TIMERG0.wdt_feed=1; + TIMERG0.wdt_wprotect=0; + } +} + +void task_wdt_delete() { + TaskHandle_t handle=xTaskGetCurrentTaskHandle(); + wdt_task_t *wdttask=wdt_task_list; + //Wdt task list can't be empty + if (!wdt_task_list) { + ESP_LOGE(TAG, "task_wdt_delete: No tasks in list?"); + return; + } + if (handle==wdt_task_list) { + //Current task is first on list. + wdt_task_list=wdt_task_list->next; + free(wdttask); + } else { + //Find current task in list + while (wdttask->next!=NULL && wdttask->next->task_handle!=handle) wdttask=wdttask->next; + if (!wdttask->next) { + ESP_LOGE(TAG, "task_wdt_delete: Task never called task_wdt_feed!"); + return; + } + wdt_task_t *freeme=wdttask->next; + wdttask->next=wdttask->next->next; + free(freeme); + } +} + +void task_wdt_init() { + TIMERG0.wdt_wprotect=WDT_WRITE_KEY; + TIMERG0.wdt_config0.sys_reset_length=7; //3.2uS + TIMERG0.wdt_config0.cpu_reset_length=7; //3.2uS + TIMERG0.wdt_config0.level_int_en=1; + TIMERG0.wdt_config0.stg0=1; //1st stage timeout: interrupt + TIMERG0.wdt_config0.stg1=3; //2nd stage timeout: reset system + TIMERG0.wdt_config1.clk_prescale=80*500; //Prescaler: wdt counts in ticks of 0.5mS + TIMERG0.wdt_config2=CONFIG_TASK_WDT_TIMEOUT_S*2000; //Set timeout before interrupt + TIMERG0.wdt_config3=CONFIG_TASK_WDT_TIMEOUT_S*4000; //Set timeout before reset + TIMERG0.wdt_config0.en=1; + TIMERG0.wdt_feed=1; + TIMERG0.wdt_wprotect=0; + ESP_INTR_DISABLE(ETS_T0_WDT_INUM); + intr_matrix_set(xPortGetCoreID(), ETS_TG1_WDT_LEVEL_INTR_SOURCE, ETS_T0_WDT_INUM); + xt_set_interrupt_handler(ETS_T0_WDT_INUM, task_wdt_isr, NULL); + ESP_INTR_ENABLE(ETS_T0_WDT_INUM); +} + + +#if CONFIG_TASK_WDT_CHECK_IDLE_TASK +void vApplicationIdleHook(void) { + task_wdt_feed(); +} +#endif + +#endif \ No newline at end of file diff --git a/components/freertos/include/freertos/FreeRTOSConfig.h b/components/freertos/include/freertos/FreeRTOSConfig.h index d1958e770..a5483d6bb 100644 --- a/components/freertos/include/freertos/FreeRTOSConfig.h +++ b/components/freertos/include/freertos/FreeRTOSConfig.h @@ -152,9 +152,9 @@ *----------------------------------------------------------*/ #define configUSE_PREEMPTION 1 -#define configUSE_IDLE_HOOK 0 +#define configUSE_IDLE_HOOK ( CONFIG_TASK_WDT_CHECK_IDLE_TASK ) -#define configUSE_TICK_HOOK 0 +#define configUSE_TICK_HOOK ( CONFIG_INT_WDT ) #define configTICK_RATE_HZ ( CONFIG_FREERTOS_HZ ) From 2b8a49365954b1ecd74ccaef4523b0543ee73ff6 Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Fri, 21 Oct 2016 18:01:08 +0800 Subject: [PATCH 048/149] Add licenses to Trax files --- components/xtensa-debug-module/eri.c | 13 +++++++++++++ components/xtensa-debug-module/trax.c | 14 ++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/components/xtensa-debug-module/eri.c b/components/xtensa-debug-module/eri.c index e2c7e41eb..fc96b531f 100644 --- a/components/xtensa-debug-module/eri.c +++ b/components/xtensa-debug-module/eri.c @@ -1,3 +1,16 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. #include #include "eri.h" diff --git a/components/xtensa-debug-module/trax.c b/components/xtensa-debug-module/trax.c index d216a3df9..5174e4477 100644 --- a/components/xtensa-debug-module/trax.c +++ b/components/xtensa-debug-module/trax.c @@ -1,3 +1,17 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include #include "soc/dport_reg.h" #include "sdkconfig.h" From ae5c5630803c67964540d3943b661ea1a2c822cd Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Fri, 21 Oct 2016 19:30:29 +0800 Subject: [PATCH 049/149] Brownout works (in as far brownout can work...), int wdt works. --- components/esp32/Kconfig | 9 +++------ components/esp32/brownout.c | 12 +++--------- components/esp32/cpu_start.c | 6 ++++-- components/esp32/int_wdt.c | 9 +++------ components/freertos/xtensa_vectors.S | 13 ++++++++++--- 5 files changed, 23 insertions(+), 26 deletions(-) diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index c40d7930d..a2faa926a 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -151,16 +151,13 @@ config INT_WDT interrupt handler did not return. It will try to invoke the panic handler first and failing that reset the SoC. -config INT_WDT_TIMEOUT_MS_MIN - default (2000/CONFIG_FREERTOS_HZ) - config INT_WDT_TIMEOUT_MS int "Interrupt watchdog timeout (ms)" depends on INT_WDT - default 100 - range INT_WDT_TIMEOUT_MS_MIN 10000 + default 10 + range INT_WDT_TIMEOUT_MIN 10000 help - The timeout of the watchdog, in miliseconds. + The timeout of the watchdog, in miliseconds. Make this higher than the FreeRTOS tick rate. config TASK_WDT bool "Task watchdog" diff --git a/components/esp32/brownout.c b/components/esp32/brownout.c index 173d63a0c..8e8805b8e 100644 --- a/components/esp32/brownout.c +++ b/components/esp32/brownout.c @@ -23,15 +23,9 @@ void esp_brownout_init() { -// WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, -// RTC_CNTL_BROWN_OUT_ENA | (CONFIG_BROWNOUT_DET_LVL << RTC_CNTL_DBROWN_OUT_THRES_S) | -// RTC_CNTL_BROWN_OUT_RST_ENA | (((CONFIG_BROWNOUT_DET_RESETDELAY*150)/1000) << RTC_CNTL_BROWN_OUT_RST_WAIT_S) | -// RTC_CNTL_BROWN_OUT_PD_RF_ENA|RTC_CNTL_BROWN_OUT_CLOSE_FLASH_ENA); - - WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, - RTC_CNTL_BROWN_OUT_ENA | (4 << RTC_CNTL_DBROWN_OUT_THRES_S) | - RTC_CNTL_BROWN_OUT_RST_ENA | (0x3FF << RTC_CNTL_BROWN_OUT_RST_WAIT_S) | - RTC_CNTL_BROWN_OUT_PD_RF_ENA | RTC_CNTL_BROWN_OUT_CLOSE_FLASH_ENA); + RTC_CNTL_BROWN_OUT_ENA | (CONFIG_BROWNOUT_DET_LVL << RTC_CNTL_DBROWN_OUT_THRES_S) | + RTC_CNTL_BROWN_OUT_RST_ENA | (((CONFIG_BROWNOUT_DET_RESETDELAY*150)/1000) << RTC_CNTL_BROWN_OUT_RST_WAIT_S) | + RTC_CNTL_BROWN_OUT_PD_RF_ENA|RTC_CNTL_BROWN_OUT_CLOSE_FLASH_ENA); } \ No newline at end of file diff --git a/components/esp32/cpu_start.c b/components/esp32/cpu_start.c index 6a482c539..51f6cebb2 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -44,6 +44,8 @@ #include "esp_log.h" #include "esp_brownout.h" +#include "esp_int_wdt.h" +#include "esp_task_wdt.h" void start_cpu0(void) __attribute__((weak, alias("start_cpu0_default"))); void start_cpu0_default(void) IRAM_ATTR; @@ -143,10 +145,10 @@ void start_cpu0_default(void) esp_brownout_init(); #endif #if CONFIG_INT_WDT - int_wdt_init() + int_wdt_init(); #endif #if CONFIG_TASK_WDT - task_wdt_init() + task_wdt_init(); #endif xTaskCreatePinnedToCore(&main_task, "main", diff --git a/components/esp32/int_wdt.c b/components/esp32/int_wdt.c index 392ed6b6a..2b4d9e084 100644 --- a/components/esp32/int_wdt.c +++ b/components/esp32/int_wdt.c @@ -47,11 +47,6 @@ This uses the TIMERG1 WDT. #define WDT_WRITE_KEY 0x50D83AA1 -static void esp_int_wdt_isr(void *arg) { - abort(); -} - - void int_wdt_init() { TIMERG1.wdt_wprotect=WDT_WRITE_KEY; TIMERG1.wdt_config0.sys_reset_length=7; //3.2uS @@ -67,7 +62,9 @@ void int_wdt_init() { TIMERG1.wdt_wprotect=0; ESP_INTR_DISABLE(WDT_INT_NUM); intr_matrix_set(xPortGetCoreID(), ETS_TG1_WDT_LEVEL_INTR_SOURCE, WDT_INT_NUM); - xt_set_interrupt_handler(WDT_INT_NUM, int_wdt_isr, NULL); + //We do not register a handler for the interrupt because it is interrupt level 4 which + //is not servicable from C. Instead, xtensa_vectors.S has a call to the panic handler for + //this interrupt. ESP_INTR_ENABLE(WDT_INT_NUM); } diff --git a/components/freertos/xtensa_vectors.S b/components/freertos/xtensa_vectors.S index 7c2fc2960..89a47f741 100644 --- a/components/freertos/xtensa_vectors.S +++ b/components/freertos/xtensa_vectors.S @@ -339,12 +339,12 @@ _xt_panic: rsr a0, EXCSAVE_1 /* save interruptee's a0 */ s32i a0, sp, XT_STK_A0 - /* Set up PS for C, reenable hi-pri interrupts, and clear EXCM. */ - movi a0, PS_INTLEVEL(XCHAL_EXCM_LEVEL) | PS_UM | PS_WOE + /* Set up PS for C, disable all interrupts, and clear EXCM. */ + movi a0, PS_INTLEVEL(7) | PS_UM | PS_WOE wsr a0, PS //Call panic handler - mov a2,sp + mov a6,sp call4 panicHandler 1: j 1b /* loop infinitely */ @@ -1607,6 +1607,13 @@ _xt_highint4: ADD HIGH PRIORITY LEVEL 4 INTERRUPT HANDLER CODE HERE. */ + /* On the ESP32, this level is used for the INT_WDT handler. If that triggers, the program is stuck with interrupts + off and the CPU should panic. */ + rsr a0, EXCSAVE_4 + wsr a0, EXCSAVE_1 /* panic handler reads this register */ + call0 _xt_panic + + .align 4 .L_xt_highint4_exit: rsr a0, EXCSAVE_4 /* restore a0 */ From 60fb9a8c813778ab2b3b149d84eb888d046992e2 Mon Sep 17 00:00:00 2001 From: liuzhifu Date: Sun, 23 Oct 2016 00:49:41 +0800 Subject: [PATCH 050/149] components/lwip - add per socket tcp window Add code to support per socket tcp window and tcp send buffer size configuration. --- components/lwip/api/api_msg.c | 12 +++++----- components/lwip/api/lwip_debug.c | 3 +++ components/lwip/api/sockets.c | 20 ++++++++++++++++ components/lwip/core/init.c | 16 ++++++------- components/lwip/core/tcp.c | 23 +++++++++++-------- components/lwip/core/tcp_in.c | 6 ++--- components/lwip/core/tcp_out.c | 21 ++++++++++++----- components/lwip/include/lwip/lwip/opt.h | 12 +++++----- .../lwip/include/lwip/lwip/priv/tcp_priv.h | 2 +- components/lwip/include/lwip/lwip/sockets.h | 6 ++++- components/lwip/include/lwip/lwip/tcp.h | 13 +++++++++-- components/lwip/include/lwip/port/lwipopts.h | 21 +++++++++-------- 12 files changed, 103 insertions(+), 52 deletions(-) diff --git a/components/lwip/api/api_msg.c b/components/lwip/api/api_msg.c index e8e967ef4..9cac2e0e9 100755 --- a/components/lwip/api/api_msg.c +++ b/components/lwip/api/api_msg.c @@ -314,8 +314,8 @@ poll_tcp(void *arg, struct tcp_pcb *pcb) if (conn->flags & NETCONN_FLAG_CHECK_WRITESPACE) { /* If the queued byte- or pbuf-count drops below the configured low-water limit, let select mark this pcb as writable again. */ - if ((conn->pcb.tcp != NULL) && (tcp_sndbuf(conn->pcb.tcp) > TCP_SNDLOWAT) && - (tcp_sndqueuelen(conn->pcb.tcp) < TCP_SNDQUEUELOWAT)) { + if ((conn->pcb.tcp != NULL) && (tcp_sndbuf(conn->pcb.tcp) > TCP_SNDLOWAT(conn->pcb.tcp)) && + (tcp_sndqueuelen(conn->pcb.tcp) < TCP_SNDQUEUELOWAT(conn->pcb.tcp))) { conn->flags &= ~NETCONN_FLAG_CHECK_WRITESPACE; API_EVENT(conn, NETCONN_EVT_SENDPLUS, 0); } @@ -348,8 +348,8 @@ sent_tcp(void *arg, struct tcp_pcb *pcb, u16_t len) /* If the queued byte- or pbuf-count drops below the configured low-water limit, let select mark this pcb as writable again. */ - if ((conn->pcb.tcp != NULL) && (tcp_sndbuf(conn->pcb.tcp) > TCP_SNDLOWAT) && - (tcp_sndqueuelen(conn->pcb.tcp) < TCP_SNDQUEUELOWAT)) { + if ((conn->pcb.tcp != NULL) && (tcp_sndbuf(conn->pcb.tcp) > TCP_SNDLOWAT(conn->pcb.tcp) && + (tcp_sndqueuelen(conn->pcb.tcp) < TCP_SNDQUEUELOWAT(conn->pcb.tcp)))) { conn->flags &= ~NETCONN_FLAG_CHECK_WRITESPACE; API_EVENT(conn, NETCONN_EVT_SENDPLUS, len); } @@ -1540,8 +1540,8 @@ err_mem: and let poll_tcp check writable space to mark the pcb writable again */ API_EVENT(conn, NETCONN_EVT_SENDMINUS, len); conn->flags |= NETCONN_FLAG_CHECK_WRITESPACE; - } else if ((tcp_sndbuf(conn->pcb.tcp) <= TCP_SNDLOWAT) || - (tcp_sndqueuelen(conn->pcb.tcp) >= TCP_SNDQUEUELOWAT)) { + } else if ((tcp_sndbuf(conn->pcb.tcp) <= TCP_SNDLOWAT(conn->pcb.tcp)) || + (tcp_sndqueuelen(conn->pcb.tcp) >= TCP_SNDQUEUELOWAT(conn->pcb.tcp))) { /* The queued byte- or pbuf-count exceeds the configured low-water limit, let select mark this pcb as non-writable. */ API_EVENT(conn, NETCONN_EVT_SENDMINUS, len); diff --git a/components/lwip/api/lwip_debug.c b/components/lwip/api/lwip_debug.c index 1e5fed40d..d73a23e1a 100644 --- a/components/lwip/api/lwip_debug.c +++ b/components/lwip/api/lwip_debug.c @@ -48,6 +48,9 @@ static void dbg_lwip_tcp_pcb_one_show(struct tcp_pcb* pcb) printf("rttest=%d rtseq=%d sa=%d sv=%d\n", pcb->rttest, pcb->rtseq, pcb->sa, pcb->sv); printf("rto=%d nrtx=%d\n", pcb->rto, pcb->nrtx); printf("dupacks=%d lastack=%d\n", pcb->dupacks, pcb->lastack); +#if ESP_PER_SOC_TCP_WND + printf("per_soc_window=%d per_soc_snd_buf=%d\n", pcb->per_soc_tcp_wnd, pcb->per_soc_tcp_snd_buf); +#endif printf("cwnd=%d ssthreash=%d\n", pcb->cwnd, pcb->ssthresh); printf("snd_next=%d snd_wl1=%d snd_wl2=%d\n", pcb->snd_nxt, pcb->snd_wl1, pcb->snd_wl2); printf("snd_lbb=%d snd_wnd=%d snd_wnd_max=%d\n", pcb->snd_lbb, pcb->snd_wnd, pcb->snd_wnd_max); diff --git a/components/lwip/api/sockets.c b/components/lwip/api/sockets.c index 350847b57..15226be20 100755 --- a/components/lwip/api/sockets.c +++ b/components/lwip/api/sockets.c @@ -2395,6 +2395,16 @@ lwip_getsockopt_impl(int s, int level, int optname, void *optval, socklen_t *opt s, *(int *)optval)); break; #endif /* LWIP_TCP_KEEPALIVE */ + +#if ESP_PER_SOC_TCP_WND + case TCP_WINDOW: + *(int*)optval = (int)sock->conn->pcb.tcp->per_soc_tcp_wnd; + break; + case TCP_SNDBUF: + *(int*)optval = (int)sock->conn->pcb.tcp->per_soc_tcp_snd_buf; + break; +#endif + default: LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, IPPROTO_TCP, UNIMPL: optname=0x%x, ..)\n", s, optname)); @@ -2792,6 +2802,16 @@ lwip_setsockopt_impl(int s, int level, int optname, const void *optval, socklen_ s, sock->conn->pcb.tcp->keep_cnt)); break; #endif /* LWIP_TCP_KEEPALIVE */ + +#if ESP_PER_SOC_TCP_WND + case TCP_WINDOW: + sock->conn->pcb.tcp->per_soc_tcp_wnd = ((u32_t)(*(const int*)optval)) * TCP_MSS; + break; + case TCP_SNDBUF: + sock->conn->pcb.tcp->per_soc_tcp_snd_buf = ((u32_t)(*(const int*)optval)) * TCP_MSS; + break; +#endif + default: LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_setsockopt(%d, IPPROTO_TCP, UNIMPL: optname=0x%x, ..)\n", s, optname)); diff --git a/components/lwip/core/init.c b/components/lwip/core/init.c index 2a410d0e4..f974aedaf 100755 --- a/components/lwip/core/init.c +++ b/components/lwip/core/init.c @@ -135,13 +135,15 @@ //#endif #else /* LWIP_WND_SCALE */ -#ifndef LWIP_ESP8266 +#if (ESP_PER_SOC_TCP_WND == 0) #if (LWIP_TCP && (TCP_WND > 0xffff)) #error "If you want to use TCP, TCP_WND must fit in an u16_t, so, you have to reduce it in your lwipopts.h (or enable window scaling)" #endif #endif #endif /* LWIP_WND_SCALE */ + +#if (ESP_PER_SOC_TCP_WND == 0) #if (LWIP_TCP && (TCP_SND_QUEUELEN > 0xffff)) #error "If you want to use TCP, TCP_SND_QUEUELEN must fit in an u16_t, so, you have to reduce it in your lwipopts.h" #endif @@ -149,7 +151,6 @@ #error "TCP_SND_QUEUELEN must be at least 2 for no-copy TCP writes to work" #endif -#ifndef LWIP_ESP8266 #if (LWIP_TCP && ((TCP_MAXRTX > 12) || (TCP_SYNMAXRTX > 12))) #error "If you want to use TCP, TCP_MAXRTX and TCP_SYNMAXRTX must less or equal to 12 (due to tcp_backoff table), so, you have to reduce them in your lwipopts.h" #endif @@ -289,6 +290,8 @@ #if !MEMP_MEM_MALLOC && (MEMP_NUM_TCP_SEG < TCP_SND_QUEUELEN) #error "lwip_sanity_check: WARNING: MEMP_NUM_TCP_SEG should be at least as big as TCP_SND_QUEUELEN. If you know what you are doing, define LWIP_DISABLE_TCP_SANITY_CHECKS to 1 to disable this error." #endif + +#if (ESP_PER_SOC_TCP_WND == 0) #if TCP_SND_BUF < (2 * TCP_MSS) #error "lwip_sanity_check: WARNING: TCP_SND_BUF must be at least as much as (2 * TCP_MSS) for things to work smoothly. If you know what you are doing, define LWIP_DISABLE_TCP_SANITY_CHECKS to 1 to disable this error." #endif @@ -304,6 +307,8 @@ #if TCP_SNDQUEUELOWAT >= TCP_SND_QUEUELEN #error "lwip_sanity_check: WARNING: TCP_SNDQUEUELOWAT must be less than TCP_SND_QUEUELEN. If you know what you are doing, define LWIP_DISABLE_TCP_SANITY_CHECKS to 1 to disable this error." #endif +#endif + #if !MEMP_MEM_MALLOC && (PBUF_POOL_BUFSIZE <= (PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN)) #error "lwip_sanity_check: WARNING: PBUF_POOL_BUFSIZE does not provide enough space for protocol headers. If you know what you are doing, define LWIP_DISABLE_TCP_SANITY_CHECKS to 1 to disable this error." #endif @@ -328,13 +333,6 @@ void lwip_init(void) { -#ifdef LWIP_ESP8266 -// MEMP_NUM_TCP_PCB = 5; -// TCP_WND = (4 * TCP_MSS); -// TCP_MAXRTX = 12; -// TCP_SYNMAXRTX = 6; -#endif - /* Modules initialization */ stats_init(); #if !NO_SYS diff --git a/components/lwip/core/tcp.c b/components/lwip/core/tcp.c index e8fda52c8..e7ea56103 100755 --- a/components/lwip/core/tcp.c +++ b/components/lwip/core/tcp.c @@ -638,7 +638,7 @@ u32_t tcp_update_rcv_ann_wnd(struct tcp_pcb *pcb) { u32_t new_right_edge = pcb->rcv_nxt + pcb->rcv_wnd; - if (TCP_SEQ_GEQ(new_right_edge, pcb->rcv_ann_right_edge + LWIP_MIN((TCP_WND / 2), pcb->mss))) { + if (TCP_SEQ_GEQ(new_right_edge, pcb->rcv_ann_right_edge + LWIP_MIN((TCP_WND(pcb) / 2), pcb->mss))) { /* we can advertise more window */ pcb->rcv_ann_wnd = pcb->rcv_wnd; return new_right_edge - pcb->rcv_ann_right_edge; @@ -694,10 +694,10 @@ tcp_recved(struct tcp_pcb *pcb, u16_t len) wnd_inflation = tcp_update_rcv_ann_wnd(pcb); /* If the change in the right edge of window is significant (default - * watermark is TCP_WND/4), then send an explicit update now. + * watermark is TCP_WND(pcb)/4), then send an explicit update now. * Otherwise wait for a packet to be sent in the normal course of * events (or more window to be available later) */ - if (wnd_inflation >= TCP_WND_UPDATE_THRESHOLD) { + if (wnd_inflation >= TCP_WND_UPDATE_THRESHOLD(pcb)) { tcp_ack_now(pcb); tcp_output(pcb); } @@ -827,9 +827,9 @@ tcp_connect(struct tcp_pcb *pcb, const ip_addr_t *ipaddr, u16_t port, pcb->snd_lbb = iss - 1; /* Start with a window that does not need scaling. When window scaling is enabled and used, the window is enlarged when both sides agree on scaling. */ - pcb->rcv_wnd = pcb->rcv_ann_wnd = TCPWND_MIN16(TCP_WND); + pcb->rcv_wnd = pcb->rcv_ann_wnd = TCPWND_MIN16(TCP_WND(pcb)); pcb->rcv_ann_right_edge = pcb->rcv_nxt; - pcb->snd_wnd = TCP_WND; + pcb->snd_wnd = TCP_WND(pcb); /* As initial send MSS, we use TCP_MSS but limit it to 536. The send MSS is updated when an MSS option is received. */ pcb->mss = (TCP_MSS > 536) ? 536 : TCP_MSS; @@ -837,7 +837,7 @@ tcp_connect(struct tcp_pcb *pcb, const ip_addr_t *ipaddr, u16_t port, pcb->mss = tcp_eff_send_mss(pcb->mss, &pcb->local_ip, &pcb->remote_ip); #endif /* TCP_CALCULATE_EFF_SEND_MSS */ pcb->cwnd = 1; - pcb->ssthresh = TCP_WND; + pcb->ssthresh = TCP_WND(pcb); #if LWIP_CALLBACK_API pcb->connected = connected; #else /* LWIP_CALLBACK_API */ @@ -1581,11 +1581,11 @@ tcp_alloc(u8_t prio) if (pcb != NULL) { memset(pcb, 0, sizeof(struct tcp_pcb)); pcb->prio = prio; - pcb->snd_buf = TCP_SND_BUF; + pcb->snd_buf = TCP_SND_BUF_DEFAULT; pcb->snd_queuelen = 0; /* Start with a window that does not need scaling. When window scaling is enabled and used, the window is enlarged when both sides agree on scaling. */ - pcb->rcv_wnd = pcb->rcv_ann_wnd = TCPWND_MIN16(TCP_WND); + pcb->rcv_wnd = pcb->rcv_ann_wnd = TCPWND_MIN16(TCP_WND(pcb)); #if LWIP_WND_SCALE /* snd_scale and rcv_scale are zero unless both sides agree to use scaling */ pcb->snd_scale = 0; @@ -1608,7 +1608,6 @@ tcp_alloc(u8_t prio) pcb->snd_lbb = iss; pcb->tmr = tcp_ticks; pcb->last_timer = tcp_timer_ctr; - pcb->polltmr = 0; #if LWIP_CALLBACK_API @@ -1624,7 +1623,13 @@ tcp_alloc(u8_t prio) #endif /* LWIP_TCP_KEEPALIVE */ pcb->keep_cnt_sent = 0; + +#if ESP_PER_SOC_TCP_WND + pcb->per_soc_tcp_wnd = TCP_WND_DEFAULT; + pcb->per_soc_tcp_snd_buf = TCP_SND_BUF_DEFAULT; +#endif } + return pcb; } diff --git a/components/lwip/core/tcp_in.c b/components/lwip/core/tcp_in.c index 25d740385..90ebf1723 100755 --- a/components/lwip/core/tcp_in.c +++ b/components/lwip/core/tcp_in.c @@ -1745,9 +1745,9 @@ tcp_parseopt(struct tcp_pcb *pcb) pcb->rcv_scale = TCP_RCV_SCALE; pcb->flags |= TF_WND_SCALE; /* window scaling is enabled, we can use the full receive window */ - LWIP_ASSERT("window not at default value", pcb->rcv_wnd == TCPWND_MIN16(TCP_WND)); - LWIP_ASSERT("window not at default value", pcb->rcv_ann_wnd == TCPWND_MIN16(TCP_WND)); - pcb->rcv_wnd = pcb->rcv_ann_wnd = TCP_WND; + LWIP_ASSERT("window not at default value", pcb->rcv_wnd == TCPWND_MIN16(TCP_WND(pcb))); + LWIP_ASSERT("window not at default value", pcb->rcv_ann_wnd == TCPWND_MIN16(TCP_WND(pcb))); + pcb->rcv_wnd = pcb->rcv_ann_wnd = TCP_WND(pcb); } break; #endif diff --git a/components/lwip/core/tcp_out.c b/components/lwip/core/tcp_out.c index aac02e4eb..5f10befae 100755 --- a/components/lwip/core/tcp_out.c +++ b/components/lwip/core/tcp_out.c @@ -336,9 +336,9 @@ tcp_write_checks(struct tcp_pcb *pcb, u16_t len) /* If total number of pbufs on the unsent/unacked queues exceeds the * configured maximum, return an error */ /* check for configured max queuelen and possible overflow */ - if ((pcb->snd_queuelen >= TCP_SND_QUEUELEN) || (pcb->snd_queuelen > TCP_SNDQUEUELEN_OVERFLOW)) { + if ((pcb->snd_queuelen >= TCP_SND_QUEUELEN(pcb)) || (pcb->snd_queuelen > TCP_SNDQUEUELEN_OVERFLOW)) { LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SEVERE, ("tcp_write: too long queue %"U16_F" (max %"U16_F")\n", - pcb->snd_queuelen, TCP_SND_QUEUELEN)); + pcb->snd_queuelen, TCP_SND_QUEUELEN(pcb))); TCP_STATS_INC(tcp.memerr); pcb->flags |= TF_NAGLEMEMERR; return ERR_MEM; @@ -606,9 +606,9 @@ tcp_write(struct tcp_pcb *pcb, const void *arg, u16_t len, u8_t apiflags) /* Now that there are more segments queued, we check again if the * length of the queue exceeds the configured maximum or * overflows. */ - if ((queuelen > TCP_SND_QUEUELEN) || (queuelen > TCP_SNDQUEUELEN_OVERFLOW)) { + if ((queuelen > TCP_SND_QUEUELEN(pcb)) || (queuelen > TCP_SNDQUEUELEN_OVERFLOW)) { LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("tcp_write: queue too long %"U16_F" (%d)\n", - queuelen, (int)TCP_SND_QUEUELEN)); + queuelen, (int)TCP_SND_QUEUELEN(pcb))); pbuf_free(p); goto memerr; } @@ -766,10 +766,10 @@ tcp_enqueue_flags(struct tcp_pcb *pcb, u8_t flags) (flags & (TCP_SYN | TCP_FIN)) != 0); /* check for configured max queuelen and possible overflow (FIN flag should always come through!) */ - if (((pcb->snd_queuelen >= TCP_SND_QUEUELEN) || (pcb->snd_queuelen > TCP_SNDQUEUELEN_OVERFLOW)) && + if (((pcb->snd_queuelen >= TCP_SND_QUEUELEN(pcb)) || (pcb->snd_queuelen > TCP_SNDQUEUELEN_OVERFLOW)) && ((flags & TCP_FIN) == 0)) { LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SEVERE, ("tcp_enqueue_flags: too long queue %"U16_F" (max %"U16_F")\n", - pcb->snd_queuelen, TCP_SND_QUEUELEN)); + pcb->snd_queuelen, TCP_SND_QUEUELEN(pcb))); TCP_STATS_INC(tcp.memerr); pcb->flags |= TF_NAGLEMEMERR; return ERR_MEM; @@ -1301,6 +1301,7 @@ tcp_rst(u32_t seqno, u32_t ackno, struct pbuf *p; struct tcp_hdr *tcphdr; struct netif *netif; + p = pbuf_alloc(PBUF_IP, TCP_HLEN, PBUF_RAM); if (p == NULL) { LWIP_DEBUGF(TCP_DEBUG, ("tcp_rst: could not allocate memory for pbuf\n")); @@ -1315,10 +1316,18 @@ tcp_rst(u32_t seqno, u32_t ackno, tcphdr->seqno = htonl(seqno); tcphdr->ackno = htonl(ackno); TCPH_HDRLEN_FLAGS_SET(tcphdr, TCP_HLEN/4, TCP_RST | TCP_ACK); +#if ESP_PER_SOC_TCP_WND +#if LWIP_WND_SCALE + tcphdr->wnd = PP_HTONS(((TCP_WND_DEFAULT >> TCP_RCV_SCALE) & 0xFFFF)); +#else + tcphdr->wnd = PP_HTONS(TCP_WND_DEFAULT); +#endif +#else #if LWIP_WND_SCALE tcphdr->wnd = PP_HTONS(((TCP_WND >> TCP_RCV_SCALE) & 0xFFFF)); #else tcphdr->wnd = PP_HTONS(TCP_WND); +#endif #endif tcphdr->chksum = 0; tcphdr->urgp = 0; diff --git a/components/lwip/include/lwip/lwip/opt.h b/components/lwip/include/lwip/lwip/opt.h index 76fff8805..c286712e0 100755 --- a/components/lwip/include/lwip/lwip/opt.h +++ b/components/lwip/include/lwip/lwip/opt.h @@ -986,7 +986,7 @@ * (2 * TCP_MSS) for things to work well */ #ifndef TCP_WND -#define TCP_WND (4 * TCP_MSS) +#define TCP_WND(pcb) (4 * TCP_MSS) #endif /** @@ -1040,7 +1040,7 @@ * To achieve good performance, this should be at least 2 * TCP_MSS. */ #ifndef TCP_SND_BUF -#define TCP_SND_BUF (2 * TCP_MSS) +#define TCP_SND_BUF(pcb) (2 * TCP_MSS) #endif /** @@ -1048,7 +1048,7 @@ * as much as (2 * TCP_SND_BUF/TCP_MSS) for things to work. */ #ifndef TCP_SND_QUEUELEN -#define TCP_SND_QUEUELEN ((4 * (TCP_SND_BUF) + (TCP_MSS - 1))/(TCP_MSS)) +#define TCP_SND_QUEUELEN(pcb) ((4 * (TCP_SND_BUF((pcb))) + (TCP_MSS - 1))/(TCP_MSS)) #endif /** @@ -1057,7 +1057,7 @@ * TCP snd_buf for select to return writable (combined with TCP_SNDQUEUELOWAT). */ #ifndef TCP_SNDLOWAT -#define TCP_SNDLOWAT LWIP_MIN(LWIP_MAX(((TCP_SND_BUF)/2), (2 * TCP_MSS) + 1), (TCP_SND_BUF) - 1) +#define TCP_SNDLOWAT(pcb) LWIP_MIN(LWIP_MAX(((TCP_SND_BUF((pcb)))/2), (2 * TCP_MSS) + 1), (TCP_SND_BUF((pcb))) - 1) #endif /** @@ -1066,7 +1066,7 @@ * this number, select returns writable (combined with TCP_SNDLOWAT). */ #ifndef TCP_SNDQUEUELOWAT -#define TCP_SNDQUEUELOWAT LWIP_MAX(((TCP_SND_QUEUELEN)/2), 5) +#define TCP_SNDQUEUELOWAT(pcb) LWIP_MAX(((TCP_SND_QUEUELEN((pcb)))/2), 5) #endif /** @@ -1134,7 +1134,7 @@ * explicit window update */ #ifndef TCP_WND_UPDATE_THRESHOLD -#define TCP_WND_UPDATE_THRESHOLD LWIP_MIN((TCP_WND / 4), (TCP_MSS * 4)) +#define TCP_WND_UPDATE_THRESHOLD(pcb) LWIP_MIN((TCP_WND((pcb)) / 4), (TCP_MSS * 4)) #endif /** diff --git a/components/lwip/include/lwip/lwip/priv/tcp_priv.h b/components/lwip/include/lwip/lwip/priv/tcp_priv.h index b5261b445..0c498944b 100755 --- a/components/lwip/include/lwip/lwip/priv/tcp_priv.h +++ b/components/lwip/include/lwip/lwip/priv/tcp_priv.h @@ -92,7 +92,7 @@ err_t tcp_process_refused_data(struct tcp_pcb *pcb); ((tpcb)->flags & (TF_NODELAY | TF_INFR)) || \ (((tpcb)->unsent != NULL) && (((tpcb)->unsent->next != NULL) || \ ((tpcb)->unsent->len >= (tpcb)->mss))) || \ - ((tcp_sndbuf(tpcb) == 0) || (tcp_sndqueuelen(tpcb) >= TCP_SND_QUEUELEN)) \ + ((tcp_sndbuf(tpcb) == 0) || (tcp_sndqueuelen(tpcb) >= TCP_SND_QUEUELEN(tpcb))) \ ) ? 1 : 0) #define tcp_output_nagle(tpcb) (tcp_do_output_nagle(tpcb) ? tcp_output(tpcb) : ERR_OK) diff --git a/components/lwip/include/lwip/lwip/sockets.h b/components/lwip/include/lwip/lwip/sockets.h index aafb3d5cf..fc2b7e2d1 100755 --- a/components/lwip/include/lwip/lwip/sockets.h +++ b/components/lwip/include/lwip/lwip/sockets.h @@ -190,7 +190,6 @@ struct msghdr { #define SO_CONTIMEO 0x1009 /* Unimplemented: connect timeout */ #define SO_NO_CHECK 0x100a /* don't create UDP checksum */ - /* * Structure used for manipulating linger option. */ @@ -250,6 +249,11 @@ struct linger { #define TCP_KEEPIDLE 0x03 /* set pcb->keep_idle - Same as TCP_KEEPALIVE, but use seconds for get/setsockopt */ #define TCP_KEEPINTVL 0x04 /* set pcb->keep_intvl - Use seconds for get/setsockopt */ #define TCP_KEEPCNT 0x05 /* set pcb->keep_cnt - Use number of probes sent for get/setsockopt */ +#if ESP_PER_SOC_TCP_WND +#define TCP_WINDOW 0x06 /* set pcb->per_soc_tcp_wnd */ +#define TCP_SNDBUF 0x07 /* set pcb->per_soc_tcp_snd_buf */ +#endif + #endif /* LWIP_TCP */ #if LWIP_IPV6 diff --git a/components/lwip/include/lwip/lwip/tcp.h b/components/lwip/include/lwip/lwip/tcp.h index d52040f99..6b8c4b6c4 100755 --- a/components/lwip/include/lwip/lwip/tcp.h +++ b/components/lwip/include/lwip/lwip/tcp.h @@ -129,14 +129,14 @@ typedef err_t (*tcp_connected_fn)(void *arg, struct tcp_pcb *tpcb, err_t err); #define RCV_WND_SCALE(pcb, wnd) (((wnd) >> (pcb)->rcv_scale)) #define SND_WND_SCALE(pcb, wnd) (((wnd) << (pcb)->snd_scale)) #define TCPWND16(x) ((u16_t)LWIP_MIN((x), 0xFFFF)) -#define TCP_WND_MAX(pcb) ((tcpwnd_size_t)(((pcb)->flags & TF_WND_SCALE) ? TCP_WND : TCPWND16(TCP_WND))) +#define TCP_WND_MAX(pcb) ((tcpwnd_size_t)(((pcb)->flags & TF_WND_SCALE) ? TCP_WND(pcb) : TCPWND16(TCP_WND(pcb)))) typedef u32_t tcpwnd_size_t; typedef u16_t tcpflags_t; #else #define RCV_WND_SCALE(pcb, wnd) (wnd) #define SND_WND_SCALE(pcb, wnd) (wnd) #define TCPWND16(x) (x) -#define TCP_WND_MAX(pcb) TCP_WND +#define TCP_WND_MAX(pcb) TCP_WND(pcb) typedef u16_t tcpwnd_size_t; typedef u8_t tcpflags_t; #endif @@ -236,6 +236,11 @@ struct tcp_pcb { u8_t dupacks; u32_t lastack; /* Highest acknowledged seqno. */ +#if ESP_PER_SOC_TCP_WND + tcpwnd_size_t per_soc_tcp_wnd; /* per tcp socket tcp window size */ + tcpwnd_size_t per_soc_tcp_snd_buf; /* per tcp socket tcp send buffer size */ +#endif + /* congestion avoidance/control variables */ tcpwnd_size_t cwnd; tcpwnd_size_t ssthresh; @@ -402,6 +407,10 @@ const char* tcp_debug_state_str(enum tcp_state s); /* for compatibility with older implementation */ #define tcp_new_ip6() tcp_new_ip_type(IPADDR_TYPE_V6) +#if ESP_PER_SOC_TCP_WND +#define PER_SOC_WND(pcb) (pcb->per_soc_wnd) +#endif + #ifdef __cplusplus } #endif diff --git a/components/lwip/include/lwip/port/lwipopts.h b/components/lwip/include/lwip/port/lwipopts.h index 2c24b2be9..5667e2d20 100755 --- a/components/lwip/include/lwip/port/lwipopts.h +++ b/components/lwip/include/lwip/port/lwipopts.h @@ -225,18 +225,21 @@ extern unsigned long os_random(void); * TCP_WND: The size of a TCP window. This must be at least * (2 * TCP_MSS) for things to work well */ -#define PERF 1 + +#define ESP_PER_SOC_TCP_WND 1 +#if ESP_PER_SOC_TCP_WND +#define TCP_WND_DEFAULT (4*TCP_MSS) +#define TCP_SND_BUF_DEFAULT (2*TCP_MSS) + +#define TCP_WND(pcb) (pcb->per_soc_tcp_wnd) +#define TCP_SND_BUF(pcb) (pcb->per_soc_tcp_snd_buf) +#else #ifdef PERF extern unsigned char misc_prof_get_tcpw(void); extern unsigned char misc_prof_get_tcp_snd_buf(void); -#define TCP_WND (misc_prof_get_tcpw()*TCP_MSS) -#define TCP_SND_BUF (misc_prof_get_tcp_snd_buf()*TCP_MSS) - -#else - -#define TCP_WND (4 * TCP_MSS) -#define TCP_SND_BUF (2 * TCP_MSS) - +#define TCP_WND(pcb) (misc_prof_get_tcpw()*TCP_MSS) +#define TCP_SND_BUF(pcb) (misc_prof_get_tcp_snd_buf()*TCP_MSS) +#endif #endif From abd4dc7d438d51711913591fe6f4d88643015448 Mon Sep 17 00:00:00 2001 From: Yinling Date: Mon, 24 Oct 2016 18:42:40 +0800 Subject: [PATCH 051/149] add know issue ^WIFI_CONN_0601: in STA+AP mode, when STA reconencting to enternal AP, external STA can't connect to AP --- components/idf_test/integration_test/KnownIssues | 1 + 1 file changed, 1 insertion(+) diff --git a/components/idf_test/integration_test/KnownIssues b/components/idf_test/integration_test/KnownIssues index 85b5b2f21..e0991f39b 100644 --- a/components/idf_test/integration_test/KnownIssues +++ b/components/idf_test/integration_test/KnownIssues @@ -56,6 +56,7 @@ WIFI_CONN_0901 # Wifi connect issue WIFI_CONN_0104 ^WIFI_CONN_0104 +^WIFI_CONN_0601 # Wifi scan issue WIFI_SCAN_0303 From 3edcf5b096fa64ddca7772cf0cef8964d67a1bd9 Mon Sep 17 00:00:00 2001 From: Yinling Date: Mon, 24 Oct 2016 18:59:56 +0800 Subject: [PATCH 052/149] fix bug for case WIFI_CONN_0102: after set AP, STA will disconnected and do reconnect. scan could fail as reconnect also use scan --- components/idf_test/integration_test/TestCaseAll.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/idf_test/integration_test/TestCaseAll.yml b/components/idf_test/integration_test/TestCaseAll.yml index 3b2242787..9e4823a29 100644 --- a/components/idf_test/integration_test/TestCaseAll.yml +++ b/components/idf_test/integration_test/TestCaseAll.yml @@ -5868,6 +5868,8 @@ test cases: - ['R SSC2 RE "\+JAP:CONNECTED,%%s"%%()'] - - SSC SSC1 ap -S -s -n 15 - ['R SSC1 C +SAP:OK'] + - - SSC SSC2 sta -C -s -p + - ['R SSC2 RE "\+JAP:CONNECTED,%%s"%%()'] - - SSC SSC2 sta -S - ['R SSC2 RE "\+SCAN:%%s,.+,\d+,1"%%()'] comment: '' From 700ed6365123f30a15f5b80ffef9d3b489c160d4 Mon Sep 17 00:00:00 2001 From: liuzhifu Date: Tue, 25 Oct 2016 09:26:10 +0800 Subject: [PATCH 053/149] component/tcpip_adapter: not update dhcps status when it is stopped after mode switch When switch the mode from WIFI_MODE_STA/WIFI_MODE_NULL to WIFI_MODE_AP/WIFI_MODE_APSTA, if the dhcp server is STOPPED, then dhcp server will not start automatically. --- components/tcpip_adapter/tcpip_adapter_lwip.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/tcpip_adapter/tcpip_adapter_lwip.c b/components/tcpip_adapter/tcpip_adapter_lwip.c index 78fecf2cb..12cf05f95 100644 --- a/components/tcpip_adapter/tcpip_adapter_lwip.c +++ b/components/tcpip_adapter/tcpip_adapter_lwip.c @@ -103,7 +103,9 @@ esp_err_t tcpip_adapter_stop(tcpip_adapter_if_t tcpip_if) if (tcpip_if == TCPIP_ADAPTER_IF_AP) { dhcps_stop(esp_netif[tcpip_if]); // TODO: dhcps checks status by its self - dhcps_status = TCPIP_ADAPTER_DHCP_INIT; + if (TCPIP_ADAPTER_DHCP_STOPPED != dhcps_status){ + dhcps_status = TCPIP_ADAPTER_DHCP_INIT; + } } else if (tcpip_if == TCPIP_ADAPTER_IF_STA) { dhcp_release(esp_netif[tcpip_if]); dhcp_stop(esp_netif[tcpip_if]); From 75a11589a16a0f0a6e451ed2eecf9279ce2eb2bc Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Tue, 25 Oct 2016 17:05:13 +0800 Subject: [PATCH 054/149] Disable brown-out WDT, fix thread WDT, add panic reason indication to _xt_panic() --- components/esp32/Kconfig | 12 +++-- components/esp32/brownout.c | 10 +++- components/esp32/int_wdt.c | 12 +++-- components/esp32/task_wdt.c | 43 ++++++++++----- .../include/freertos/FreeRTOSConfig.h | 1 + components/freertos/include/freertos/panic.h | 13 +++++ components/freertos/panic.c | 54 ++++++++++++++++++- components/freertos/tasks.c | 1 - components/freertos/xtensa_vectors.S | 11 ++++ 9 files changed, 134 insertions(+), 23 deletions(-) diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index a2faa926a..dbd1cfb6f 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -140,7 +140,6 @@ config ULP_COPROC_RESERVE_MEM default 0 depends on !ULP_COPROC_ENABLED -menu "Watchdogs & brown-out detection" config INT_WDT bool "Interrupt watchdog" @@ -155,7 +154,7 @@ config INT_WDT_TIMEOUT_MS int "Interrupt watchdog timeout (ms)" depends on INT_WDT default 10 - range INT_WDT_TIMEOUT_MIN 10000 + range 10 10000 help The timeout of the watchdog, in miliseconds. Make this higher than the FreeRTOS tick rate. @@ -190,11 +189,15 @@ config TASK_WDT_CHECK_IDLE_TASK With this turned on, the task WDT can detect if the idle task is not called within the task watchdog timeout period. The idle task not being called usually is a symptom of another task hoarding the CPU. It is also a bad thing because FreeRTOS household tasks depend on the - idle task getting some runtime every now and then. + idle task getting some runtime every now and then. Take Care: With this disabled, this + watchdog will trigger if no tasks register themselves within the timeout value. +#The brownout detector code is disabled (by making it depend on a nonexisting symbol) because the current revision of ESP32 +#silicon has a bug in the brown-out detector, rendering it unusable for resetting the CPU. config BROWNOUT_DET bool "Hardware brownout detect & reset" default y + depends on NEEDS_ESP32_NEW_SILICON_REV help The ESP32 has a built-in brownout detector which can detect if the voltage is lower than a specific value. If this happens, it will reset the chip in order to prevent unintended @@ -207,6 +210,8 @@ choice BROWNOUT_DET_LVL_SEL help The brownout detector will reset the chip when the supply voltage is below this level. +#The voltage levels here are estimates, more work needs to be done to figure out the exact voltages +#of the brownout threshold levels. config BROWNOUT_DET_LVL_SEL_0 bool "2.1V" config BROWNOUT_DET_LVL_SEL_1 @@ -246,7 +251,6 @@ config BROWNOUT_DET_RESETDELAY The brownout detector can reset the chip after a certain delay, in order to make sure e.g. a voltage dip has entirely passed before trying to restart the chip. You can set the delay here. -endmenu diff --git a/components/esp32/brownout.c b/components/esp32/brownout.c index 8e8805b8e..97846ae8c 100644 --- a/components/esp32/brownout.c +++ b/components/esp32/brownout.c @@ -22,10 +22,18 @@ #include "soc/rtc_cntl_reg.h" +#if CONFIG_BROWNOUT_DET +/* +This file ins included in esp-idf, but the menuconfig option for this is disabled because a silicon bug +prohibits the brownout detector from functioning correctly on the ESP32. +*/ + void esp_brownout_init() { WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, RTC_CNTL_BROWN_OUT_ENA | (CONFIG_BROWNOUT_DET_LVL << RTC_CNTL_DBROWN_OUT_THRES_S) | RTC_CNTL_BROWN_OUT_RST_ENA | (((CONFIG_BROWNOUT_DET_RESETDELAY*150)/1000) << RTC_CNTL_BROWN_OUT_RST_WAIT_S) | RTC_CNTL_BROWN_OUT_PD_RF_ENA|RTC_CNTL_BROWN_OUT_CLOSE_FLASH_ENA); -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/components/esp32/int_wdt.c b/components/esp32/int_wdt.c index 2b4d9e084..5fb7a63ba 100644 --- a/components/esp32/int_wdt.c +++ b/components/esp32/int_wdt.c @@ -44,7 +44,6 @@ This uses the TIMERG1 WDT. #define WDT_INT_NUM 24 - #define WDT_WRITE_KEY 0x50D83AA1 void int_wdt_init() { @@ -55,11 +54,15 @@ void int_wdt_init() { TIMERG1.wdt_config0.stg0=1; //1st stage timeout: interrupt TIMERG1.wdt_config0.stg1=3; //2nd stage timeout: reset system TIMERG1.wdt_config1.clk_prescale=80*500; //Prescaler: wdt counts in ticks of 0.5mS - TIMERG1.wdt_config2=CONFIG_INT_WDT_TIMEOUT_MS*2; //Set timeout before interrupt - TIMERG1.wdt_config3=CONFIG_INT_WDT_TIMEOUT_MS*4; //Set timeout before reset + //The timer configs initially are set to 5 seconds, to make sure the CPU can start up. The tick hook sets + //it to their actual value. + TIMERG1.wdt_config2=10000; + TIMERG1.wdt_config3=10000; TIMERG1.wdt_config0.en=1; TIMERG1.wdt_feed=1; TIMERG1.wdt_wprotect=0; + TIMERG1.int_clr_timers.wdt=1; + TIMERG1.int_ena.wdt=1; ESP_INTR_DISABLE(WDT_INT_NUM); intr_matrix_set(xPortGetCoreID(), ETS_TG1_WDT_LEVEL_INTR_SOURCE, WDT_INT_NUM); //We do not register a handler for the interrupt because it is interrupt level 4 which @@ -69,9 +72,10 @@ void int_wdt_init() { } - void vApplicationTickHook(void) { TIMERG1.wdt_wprotect=WDT_WRITE_KEY; + TIMERG1.wdt_config2=CONFIG_INT_WDT_TIMEOUT_MS*2; //Set timeout before interrupt + TIMERG1.wdt_config3=CONFIG_INT_WDT_TIMEOUT_MS*4; //Set timeout before reset TIMERG1.wdt_feed=1; TIMERG1.wdt_wprotect=0; } diff --git a/components/esp32/task_wdt.c b/components/esp32/task_wdt.c index 7ae3dfb2e..3e4c43639 100644 --- a/components/esp32/task_wdt.c +++ b/components/esp32/task_wdt.c @@ -33,6 +33,7 @@ This uses the TIMERG0 WDT. #include #include "esp_err.h" #include "esp_intr.h" +#include "esp_attr.h" #include "soc/timer_group_struct.h" #include "esp_log.h" @@ -42,7 +43,6 @@ This uses the TIMERG0 WDT. static const char* TAG = "task_wdt"; - typedef struct wdt_task_t wdt_task_t; struct wdt_task_t { TaskHandle_t task_handle; @@ -50,16 +50,35 @@ struct wdt_task_t { wdt_task_t *next; }; - static wdt_task_t *wdt_task_list=NULL; +//We use this interrupt number on whatever task calls task_wdt_init. #define WDT_INT_NUM 24 - #define WDT_WRITE_KEY 0x50D83AA1 -static void task_wdt_isr(void *arg) { +static void IRAM_ATTR task_wdt_isr(void *arg) { + wdt_task_t *wdttask; + const char *cpu; + //Feed the watchdog so we do not reset + TIMERG0.wdt_wprotect=WDT_WRITE_KEY; + TIMERG0.wdt_feed=1; + TIMERG0.wdt_wprotect=0; + //Ack interrupt + TIMERG0.int_clr_timers.wdt=1; + //Watchdog got triggered because at least one task did not report in. + ets_printf("Task watchdog got triggered. The following tasks did not feed the watchdog in time:\n"); + for (wdttask=wdt_task_list; wdttask!=NULL; wdttask=wdttask->next) { + if (!wdttask->fed_watchdog) { + cpu=xTaskGetAffinity(wdttask->task_handle)==0?"CPU 0":"CPU 1"; + if (xTaskGetAffinity(wdttask->task_handle)==tskNO_AFFINITY) cpu="CPU 0/1"; + printf(" - %s (%s)\n", pcTaskGetTaskName(wdttask->task_handle), cpu); + } + } +#if CONFIG_TASK_WDT_PANIC + ets_printf("Aborting.\n"); abort(); +#endif } @@ -69,7 +88,7 @@ void task_wdt_feed() { TaskHandle_t handle=xTaskGetCurrentTaskHandle(); //Walk the linked list of wdt tasks to find this one, as well as see if we need to feed //the real watchdog timer. - while (wdttask!=NULL) { + for (wdttask=wdt_task_list; wdttask!=NULL; wdttask=wdttask->next) { //See if we are at the current task. if (wdttask->task_handle == handle) { wdttask->fed_watchdog=true; @@ -77,8 +96,6 @@ void task_wdt_feed() { } //If even one task in the list doesn't have the do_feed_wdt var set, we do not feed the watchdog. if (!wdttask->fed_watchdog) do_feed_wdt=false; - //Next entry. - wdttask=wdttask->next; } if (!found_task) { @@ -91,9 +108,8 @@ void task_wdt_feed() { if (wdt_task_list == NULL) { wdt_task_list=newtask; } else { - wdttask=wdt_task_list; - while (!(wdttask->next == NULL)) wdttask=wdttask->next; - wdttask->next=wdttask; + for (wdttask=wdt_task_list; wdttask->next!=NULL; wdttask=wdttask->next) ; + wdttask->next=newtask; } } if (do_feed_wdt) { @@ -101,6 +117,8 @@ void task_wdt_feed() { TIMERG0.wdt_wprotect=WDT_WRITE_KEY; TIMERG0.wdt_feed=1; TIMERG0.wdt_wprotect=0; + //Reset fed_watchdog status + for (wdttask=wdt_task_list; wdttask->next!=NULL; wdttask=wdttask->next) wdttask->fed_watchdog=false; } } @@ -143,12 +161,13 @@ void task_wdt_init() { TIMERG0.wdt_feed=1; TIMERG0.wdt_wprotect=0; ESP_INTR_DISABLE(ETS_T0_WDT_INUM); - intr_matrix_set(xPortGetCoreID(), ETS_TG1_WDT_LEVEL_INTR_SOURCE, ETS_T0_WDT_INUM); + intr_matrix_set(xPortGetCoreID(), ETS_TG0_WDT_LEVEL_INTR_SOURCE, ETS_T0_WDT_INUM); xt_set_interrupt_handler(ETS_T0_WDT_INUM, task_wdt_isr, NULL); + TIMERG0.int_clr_timers.wdt=1; + TIMERG0.int_ena.wdt=1; ESP_INTR_ENABLE(ETS_T0_WDT_INUM); } - #if CONFIG_TASK_WDT_CHECK_IDLE_TASK void vApplicationIdleHook(void) { task_wdt_feed(); diff --git a/components/freertos/include/freertos/FreeRTOSConfig.h b/components/freertos/include/freertos/FreeRTOSConfig.h index a5483d6bb..b732d1c07 100644 --- a/components/freertos/include/freertos/FreeRTOSConfig.h +++ b/components/freertos/include/freertos/FreeRTOSConfig.h @@ -231,6 +231,7 @@ #define INCLUDE_vTaskDelayUntil 1 #define INCLUDE_vTaskDelay 1 #define INCLUDE_uxTaskGetStackHighWaterMark 1 +#define INCLUDE_pcTaskGetTaskName 1 #if CONFIG_ENABLE_MEMORY_DEBUG #define configENABLE_MEMORY_DEBUG 1 diff --git a/components/freertos/include/freertos/panic.h b/components/freertos/include/freertos/panic.h index 9e902ed20..ba47e3d43 100644 --- a/components/freertos/include/freertos/panic.h +++ b/components/freertos/include/freertos/panic.h @@ -1,7 +1,20 @@ #ifndef PANIC_H #define PANIC_H + +#define PANIC_RSN_NONE 0 +#define PANIC_RSN_DEBUGEXCEPTION 1 +#define PANIC_RSN_DOUBLEEXCEPTION 2 +#define PANIC_RSN_KERNELEXCEPTION 3 +#define PANIC_RSN_COPROCEXCEPTION 4 +#define PANIC_RSN_INTWDT 5 +#define PANIC_RSN_MAX 5 + + +#ifndef __ASSEMBLER__ + void setBreakpointIfJtag(void *fn); +#endif #endif \ No newline at end of file diff --git a/components/freertos/panic.c b/components/freertos/panic.c index 6a87679d1..0c912e56f 100644 --- a/components/freertos/panic.c +++ b/components/freertos/panic.c @@ -25,8 +25,13 @@ #include "soc/io_mux_reg.h" #include "soc/dport_reg.h" #include "soc/rtc_cntl_reg.h" +#include "soc/timer_group_struct.h" #include "gdbstub.h" +#include "panic.h" + +#define WDT_WRITE_KEY 0x50D83AA1 + /* Panic handlers; these get called when an unhandled exception occurs or the assembly-level @@ -130,10 +135,25 @@ static int inOCDMode() { } void panicHandler(XtExcFrame *frame) { + int *regs=(int*)frame; + //Please keep in sync with PANIC_RSN_* defines + const char *reasons[]={ + "Unknown reason", + "Unhandled debug exception", + "Double exception", + "Unhandled kernel exception", + "Coprocessor exception", + "Interrupt wdt timeout" + }; + const char *reason=reasons[0]; + //The panic reason is stored in the EXCCAUSE register. + if (regs[20]<=PANIC_RSN_MAX) reason=reasons[regs[20]]; haltOtherCore(); panicPutStr("Guru Meditation Error: Core "); panicPutDec(xPortGetCoreID()); - panicPutStr(" panic'ed.\r\n"); + panicPutStr(" panic'ed ("); + panicPutStr(reason); + panicPutStr(")\r\n"); if (inOCDMode()) { asm("break.n 1"); @@ -175,6 +195,33 @@ void xt_unhandled_exception(XtExcFrame *frame) { } +//Disables all but one WDT, and allows enough time on that WDT to do what we need to do. +static void reconfigureAllWdts() { + TIMERG0.wdt_wprotect=WDT_WRITE_KEY; + TIMERG0.wdt_feed=1; + TIMERG0.wdt_config0.sys_reset_length=7; //3.2uS + TIMERG0.wdt_config0.cpu_reset_length=7; //3.2uS + TIMERG0.wdt_config0.stg0=3; //1st stage timeout: reset system + TIMERG0.wdt_config1.clk_prescale=80*500; //Prescaler: wdt counts in ticks of 0.5mS + TIMERG0.wdt_config2=2000; //1 second before reset + TIMERG0.wdt_config0.en=1; + TIMERG0.wdt_wprotect=0; + //Disable wdt 1 + TIMERG1.wdt_wprotect=WDT_WRITE_KEY; + TIMERG1.wdt_config0.en=0; + TIMERG1.wdt_wprotect=0; +} + +static void disableAllWdts() { + TIMERG0.wdt_wprotect=WDT_WRITE_KEY; + TIMERG0.wdt_config0.en=0; + TIMERG0.wdt_wprotect=0; + TIMERG1.wdt_wprotect=WDT_WRITE_KEY; + TIMERG1.wdt_config0.en=0; + TIMERG0.wdt_wprotect=0; +} + + /* We arrive here after a panic or unhandled exception, when no OCD is detected. Dump the registers to the serial port and either jump to the gdb stub, halt the CPU or reboot. @@ -187,6 +234,9 @@ void commonErrorHandler(XtExcFrame *frame) { "A6 ","A7 ","A8 ","A9 ","A10 ","A11 ","A12 ","A13 ", "A14 ","A15 ","SAR ","EXCCAUSE","EXCVADDR","LBEG ","LEND ","LCOUNT "}; + //Feed the watchdogs, so they will give us time to print out debug info + reconfigureAllWdts(); + panicPutStr("Register dump:\r\n"); for (x=0; x<24; x+=4) { @@ -201,6 +251,7 @@ void commonErrorHandler(XtExcFrame *frame) { panicPutStr("\r\n"); } #if CONFIG_FREERTOS_PANIC_GDBSTUB + disableAllWdts(); panicPutStr("Entering gdb stub now.\r\n"); gdbstubPanicHandler(frame); #elif CONFIG_FREERTOS_PANIC_PRINT_REBOOT || CONFIG_FREERTOS_PANIC_SILENT_REBOOT @@ -208,6 +259,7 @@ void commonErrorHandler(XtExcFrame *frame) { for (x=0; x<100; x++) ets_delay_us(1000); software_reset(); #else + disableAllWdts(); panicPutStr("CPU halted.\r\n"); while(1); #endif diff --git a/components/freertos/tasks.c b/components/freertos/tasks.c index 3cde3bf13..221bf476c 100644 --- a/components/freertos/tasks.c +++ b/components/freertos/tasks.c @@ -1832,7 +1832,6 @@ UBaseType_t uxTaskGetNumberOfTasks( void ) /*-----------------------------------------------------------*/ #if ( INCLUDE_pcTaskGetTaskName == 1 ) - char *pcTaskGetTaskName( TaskHandle_t xTaskToQuery ) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */ { TCB_t *pxTCB; diff --git a/components/freertos/xtensa_vectors.S b/components/freertos/xtensa_vectors.S index 89a47f741..5b31eb0a8 100644 --- a/components/freertos/xtensa_vectors.S +++ b/components/freertos/xtensa_vectors.S @@ -91,6 +91,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *******************************************************************************/ #include "xtensa_rtos.h" +#include "panic.h" /* Define for workaround: pin no-cpu-affinity tasks to a cpu when fpu is used. @@ -462,6 +463,8 @@ _DebugExceptionVector: jx a3 #else wsr a0, EXCSAVE+XCHAL_DEBUGLEVEL /* save original a0 somewhere */ + movi a0,PANIC_RSN_DEBUGEXCEPTION + wsr a0,EXCCAUSE call0 _xt_panic /* does not return */ rfi XCHAL_DEBUGLEVEL /* make a0 point here not later */ #endif @@ -489,6 +492,8 @@ _DoubleExceptionVector: #if XCHAL_HAVE_DEBUG break 1, 4 /* unhandled double exception */ #endif + movi a0,PANIC_RSN_DOUBLEEXCEPTION + wsr a0,EXCCAUSE call0 _xt_panic /* does not return */ rfde /* make a0 point here not later */ @@ -522,6 +527,8 @@ _xt_kernel_exc: #if XCHAL_HAVE_DEBUG break 1, 0 /* unhandled kernel exception */ #endif + movi a0,PANIC_RSN_KERNELEXCEPTION + wsr a0,EXCCAUSE call0 _xt_panic /* does not return */ rfe /* make a0 point here not there */ @@ -1024,6 +1031,8 @@ _xt_coproc_exc: #if XCHAL_HAVE_DEBUG break 1, 1 /* unhandled user exception */ #endif + movi a0,PANIC_RSN_COPROCEXCEPTION + wsr a0,EXCCAUSE call0 _xt_panic /* not in a thread (invalid) */ /* never returns */ @@ -1611,6 +1620,8 @@ _xt_highint4: off and the CPU should panic. */ rsr a0, EXCSAVE_4 wsr a0, EXCSAVE_1 /* panic handler reads this register */ + movi a0,PANIC_RSN_INTWDT + wsr a0,EXCCAUSE call0 _xt_panic From 89f7752cddc2e2a30885292e4d0c9002c5958705 Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Tue, 25 Oct 2016 18:08:55 +0800 Subject: [PATCH 055/149] Make CPU1 int wdt / idle task wdt configurable, panic now properly disables other cpu, tick handler now also is called on cpu1, task wdt prints currently running tasks. --- components/esp32/Kconfig | 16 ++++++++++- components/esp32/int_wdt.c | 23 +++++++++++++++ components/esp32/task_wdt.c | 8 ++++++ components/freertos/include/freertos/panic.h | 5 ++-- components/freertos/include/freertos/task.h | 11 +++++++ components/freertos/panic.c | 19 +++++++------ components/freertos/tasks.c | 30 ++++++++++++++++++++ components/freertos/xtensa_vectors.S | 17 +++++++++-- 8 files changed, 115 insertions(+), 14 deletions(-) diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index dbd1cfb6f..6fb47ebd0 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -158,6 +158,13 @@ config INT_WDT_TIMEOUT_MS help The timeout of the watchdog, in miliseconds. Make this higher than the FreeRTOS tick rate. +config INT_WDT_CHECK_CPU1 + bool "Also watch CPU1 tick interrupt" + depends on INT_WDT && !FREERTOS_UNICORE + default y + help + Also detect if interrupts on CPU 1 are disabled for too long. + config TASK_WDT bool "Task watchdog" default y @@ -182,7 +189,7 @@ config TASK_WDT_TIMEOUT_S Timeout for the task WDT, in seconds. config TASK_WDT_CHECK_IDLE_TASK - bool "Task watchdog watches idle tasks" + bool "Task watchdog watches CPU0 idle task" depends on TASK_WDT default y help @@ -192,6 +199,13 @@ config TASK_WDT_CHECK_IDLE_TASK idle task getting some runtime every now and then. Take Care: With this disabled, this watchdog will trigger if no tasks register themselves within the timeout value. +config TASK_WDT_CHECK_IDLE_TASK_CPU1 + bool "Task watchdog also watches CPU1 idle task" + depends on TASK_WDT_CHECK_IDLE_TASK && !FREERTOS_UNICORE + default y + help + Also check the idle task that runs on CPU1. + #The brownout detector code is disabled (by making it depend on a nonexisting symbol) because the current revision of ESP32 #silicon has a bug in the brown-out detector, rendering it unusable for resetting the CPU. config BROWNOUT_DET diff --git a/components/esp32/int_wdt.c b/components/esp32/int_wdt.c index 5fb7a63ba..8eadbeb8d 100644 --- a/components/esp32/int_wdt.c +++ b/components/esp32/int_wdt.c @@ -30,6 +30,7 @@ This uses the TIMERG1 WDT. #include #include #include +#include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include @@ -72,12 +73,34 @@ void int_wdt_init() { } +#if CONFIG_INT_WDT_CHECK_CPU1 +//Not static; the ISR assembly checks this. +bool int_wdt_app_cpu_ticked=false; + void vApplicationTickHook(void) { + if (xPortGetCoreID()!=0) { + int_wdt_app_cpu_ticked=true; + } else { + //Only feed wdt if app cpu also ticked. + if (int_wdt_app_cpu_ticked) { + TIMERG1.wdt_wprotect=WDT_WRITE_KEY; + TIMERG1.wdt_config2=CONFIG_INT_WDT_TIMEOUT_MS*2; //Set timeout before interrupt + TIMERG1.wdt_config3=CONFIG_INT_WDT_TIMEOUT_MS*4; //Set timeout before reset + TIMERG1.wdt_feed=1; + TIMERG1.wdt_wprotect=0; + int_wdt_app_cpu_ticked=false; + } + } +} +#else +void vApplicationTickHook(void) { + if (xPortGetCoreID()!=0) return; TIMERG1.wdt_wprotect=WDT_WRITE_KEY; TIMERG1.wdt_config2=CONFIG_INT_WDT_TIMEOUT_MS*2; //Set timeout before interrupt TIMERG1.wdt_config3=CONFIG_INT_WDT_TIMEOUT_MS*4; //Set timeout before reset TIMERG1.wdt_feed=1; TIMERG1.wdt_wprotect=0; } +#endif #endif \ No newline at end of file diff --git a/components/esp32/task_wdt.c b/components/esp32/task_wdt.c index 3e4c43639..6da0901fb 100644 --- a/components/esp32/task_wdt.c +++ b/components/esp32/task_wdt.c @@ -75,6 +75,11 @@ static void IRAM_ATTR task_wdt_isr(void *arg) { printf(" - %s (%s)\n", pcTaskGetTaskName(wdttask->task_handle), cpu); } } + ets_printf("Tasks currently running:\n"); + for (int x=0; x Date: Tue, 25 Oct 2016 18:18:11 +0800 Subject: [PATCH 056/149] Add licenses, docbook, general cleanup --- components/esp32/brownout.c | 2 +- components/esp32/include/esp_brownout.h | 19 ++- components/esp32/include/esp_int_wdt.h | 44 ++++- components/esp32/include/esp_task_wdt.h | 73 +++++++- components/esp32/int_wdt.c | 82 ++++----- components/esp32/task_wdt.c | 210 ++++++++++++------------ 6 files changed, 275 insertions(+), 155 deletions(-) diff --git a/components/esp32/brownout.c b/components/esp32/brownout.c index 97846ae8c..1dcde078e 100644 --- a/components/esp32/brownout.c +++ b/components/esp32/brownout.c @@ -24,7 +24,7 @@ #if CONFIG_BROWNOUT_DET /* -This file ins included in esp-idf, but the menuconfig option for this is disabled because a silicon bug +This file is included in esp-idf, but the menuconfig option for this is disabled because a silicon bug prohibits the brownout detector from functioning correctly on the ESP32. */ diff --git a/components/esp32/include/esp_brownout.h b/components/esp32/include/esp_brownout.h index acce05e0d..5a0b1aec0 100644 --- a/components/esp32/include/esp_brownout.h +++ b/components/esp32/include/esp_brownout.h @@ -1,5 +1,20 @@ -#ifndef BROWNOUT_H -#define BROWNOUT_H +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +#ifndef __ESP_BROWNOUT_H +#define __ESP_BROWNOUT_H void esp_brownout_init(); diff --git a/components/esp32/include/esp_int_wdt.h b/components/esp32/include/esp_int_wdt.h index 81404e305..dc5bd0dda 100644 --- a/components/esp32/include/esp_int_wdt.h +++ b/components/esp32/include/esp_int_wdt.h @@ -1,6 +1,46 @@ -#ifndef INT_WDT_H -#define INT_WDT_H +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef __ESP_INT_WDT_H +#define __ESP_INT_WDT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** @addtogroup Watchdog_APIs + * @{ + */ + +/** + * @brief Initialize the interrupt watchdog. This is called in the init code, no need to + * call it explicitly. + * + * @param null + * + * @return null + */ void int_wdt_init(); + +/** + * @} + */ + + +#ifdef __cplusplus +} +#endif + #endif \ No newline at end of file diff --git a/components/esp32/include/esp_task_wdt.h b/components/esp32/include/esp_task_wdt.h index 9163e6907..c050616af 100644 --- a/components/esp32/include/esp_task_wdt.h +++ b/components/esp32/include/esp_task_wdt.h @@ -1,8 +1,73 @@ -#ifndef TASK_WDT_H -#define TASK_WDT_H +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at -void task_wdt_feed(); -void task_wdt_delete(); +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef __ESP_TASK_WDT_H +#define __ESP_TASK_WDT_H + +#ifdef __cplusplus +extern "C" { +#endif + + +/** \defgroup Watchdog_APIs Watchdog APIs + * @brief Watchdog APIs + */ + +/** @addtogroup Watchdog_APIs + * @{ + */ + +/** + * @brief Initialize the task watchdog. This is called in the init code, no need to + * call it explicitly. + * + * @param null + * + * @return null + */ void task_wdt_init(); +/** + * @brief Feed the watchdog. After the first feeding session, the watchdog will expect the calling + * task to keep feeding the watchdog until task_wdt_delete() is called. + * + * @param null + * + * @return null + */ + +void task_wdt_feed(); + + +/** + * @brief Delete the watchdog for the current task. + * + * @param null + * + * @return null + */ +void task_wdt_delete(); + +/** + * @} + */ + + +#ifdef __cplusplus +} +#endif + + + #endif \ No newline at end of file diff --git a/components/esp32/int_wdt.c b/components/esp32/int_wdt.c index 8eadbeb8d..8c506bde1 100644 --- a/components/esp32/int_wdt.c +++ b/components/esp32/int_wdt.c @@ -48,28 +48,28 @@ This uses the TIMERG1 WDT. #define WDT_WRITE_KEY 0x50D83AA1 void int_wdt_init() { - TIMERG1.wdt_wprotect=WDT_WRITE_KEY; - TIMERG1.wdt_config0.sys_reset_length=7; //3.2uS - TIMERG1.wdt_config0.cpu_reset_length=7; //3.2uS - TIMERG1.wdt_config0.level_int_en=1; - TIMERG1.wdt_config0.stg0=1; //1st stage timeout: interrupt - TIMERG1.wdt_config0.stg1=3; //2nd stage timeout: reset system - TIMERG1.wdt_config1.clk_prescale=80*500; //Prescaler: wdt counts in ticks of 0.5mS - //The timer configs initially are set to 5 seconds, to make sure the CPU can start up. The tick hook sets - //it to their actual value. - TIMERG1.wdt_config2=10000; - TIMERG1.wdt_config3=10000; - TIMERG1.wdt_config0.en=1; - TIMERG1.wdt_feed=1; - TIMERG1.wdt_wprotect=0; - TIMERG1.int_clr_timers.wdt=1; - TIMERG1.int_ena.wdt=1; - ESP_INTR_DISABLE(WDT_INT_NUM); - intr_matrix_set(xPortGetCoreID(), ETS_TG1_WDT_LEVEL_INTR_SOURCE, WDT_INT_NUM); - //We do not register a handler for the interrupt because it is interrupt level 4 which - //is not servicable from C. Instead, xtensa_vectors.S has a call to the panic handler for - //this interrupt. - ESP_INTR_ENABLE(WDT_INT_NUM); + TIMERG1.wdt_wprotect=WDT_WRITE_KEY; + TIMERG1.wdt_config0.sys_reset_length=7; //3.2uS + TIMERG1.wdt_config0.cpu_reset_length=7; //3.2uS + TIMERG1.wdt_config0.level_int_en=1; + TIMERG1.wdt_config0.stg0=1; //1st stage timeout: interrupt + TIMERG1.wdt_config0.stg1=3; //2nd stage timeout: reset system + TIMERG1.wdt_config1.clk_prescale=80*500; //Prescaler: wdt counts in ticks of 0.5mS + //The timer configs initially are set to 5 seconds, to make sure the CPU can start up. The tick hook sets + //it to their actual value. + TIMERG1.wdt_config2=10000; + TIMERG1.wdt_config3=10000; + TIMERG1.wdt_config0.en=1; + TIMERG1.wdt_feed=1; + TIMERG1.wdt_wprotect=0; + TIMERG1.int_clr_timers.wdt=1; + TIMERG1.int_ena.wdt=1; + ESP_INTR_DISABLE(WDT_INT_NUM); + intr_matrix_set(xPortGetCoreID(), ETS_TG1_WDT_LEVEL_INTR_SOURCE, WDT_INT_NUM); + //We do not register a handler for the interrupt because it is interrupt level 4 which + //is not servicable from C. Instead, xtensa_vectors.S has a call to the panic handler for + //this interrupt. + ESP_INTR_ENABLE(WDT_INT_NUM); } @@ -78,28 +78,28 @@ void int_wdt_init() { bool int_wdt_app_cpu_ticked=false; void vApplicationTickHook(void) { - if (xPortGetCoreID()!=0) { - int_wdt_app_cpu_ticked=true; - } else { - //Only feed wdt if app cpu also ticked. - if (int_wdt_app_cpu_ticked) { - TIMERG1.wdt_wprotect=WDT_WRITE_KEY; - TIMERG1.wdt_config2=CONFIG_INT_WDT_TIMEOUT_MS*2; //Set timeout before interrupt - TIMERG1.wdt_config3=CONFIG_INT_WDT_TIMEOUT_MS*4; //Set timeout before reset - TIMERG1.wdt_feed=1; - TIMERG1.wdt_wprotect=0; - int_wdt_app_cpu_ticked=false; - } - } + if (xPortGetCoreID()!=0) { + int_wdt_app_cpu_ticked=true; + } else { + //Only feed wdt if app cpu also ticked. + if (int_wdt_app_cpu_ticked) { + TIMERG1.wdt_wprotect=WDT_WRITE_KEY; + TIMERG1.wdt_config2=CONFIG_INT_WDT_TIMEOUT_MS*2; //Set timeout before interrupt + TIMERG1.wdt_config3=CONFIG_INT_WDT_TIMEOUT_MS*4; //Set timeout before reset + TIMERG1.wdt_feed=1; + TIMERG1.wdt_wprotect=0; + int_wdt_app_cpu_ticked=false; + } + } } #else void vApplicationTickHook(void) { - if (xPortGetCoreID()!=0) return; - TIMERG1.wdt_wprotect=WDT_WRITE_KEY; - TIMERG1.wdt_config2=CONFIG_INT_WDT_TIMEOUT_MS*2; //Set timeout before interrupt - TIMERG1.wdt_config3=CONFIG_INT_WDT_TIMEOUT_MS*4; //Set timeout before reset - TIMERG1.wdt_feed=1; - TIMERG1.wdt_wprotect=0; + if (xPortGetCoreID()!=0) return; + TIMERG1.wdt_wprotect=WDT_WRITE_KEY; + TIMERG1.wdt_config2=CONFIG_INT_WDT_TIMEOUT_MS*2; //Set timeout before interrupt + TIMERG1.wdt_config3=CONFIG_INT_WDT_TIMEOUT_MS*4; //Set timeout before reset + TIMERG1.wdt_feed=1; + TIMERG1.wdt_wprotect=0; } #endif diff --git a/components/esp32/task_wdt.c b/components/esp32/task_wdt.c index 6da0901fb..e6a462088 100644 --- a/components/esp32/task_wdt.c +++ b/components/esp32/task_wdt.c @@ -45,9 +45,9 @@ static const char* TAG = "task_wdt"; typedef struct wdt_task_t wdt_task_t; struct wdt_task_t { - TaskHandle_t task_handle; - bool fed_watchdog; - wdt_task_t *next; + TaskHandle_t task_handle; + bool fed_watchdog; + wdt_task_t *next; }; static wdt_task_t *wdt_task_list=NULL; @@ -58,127 +58,127 @@ static wdt_task_t *wdt_task_list=NULL; #define WDT_WRITE_KEY 0x50D83AA1 static void IRAM_ATTR task_wdt_isr(void *arg) { - wdt_task_t *wdttask; - const char *cpu; - //Feed the watchdog so we do not reset - TIMERG0.wdt_wprotect=WDT_WRITE_KEY; - TIMERG0.wdt_feed=1; - TIMERG0.wdt_wprotect=0; - //Ack interrupt - TIMERG0.int_clr_timers.wdt=1; - //Watchdog got triggered because at least one task did not report in. - ets_printf("Task watchdog got triggered. The following tasks did not feed the watchdog in time:\n"); - for (wdttask=wdt_task_list; wdttask!=NULL; wdttask=wdttask->next) { - if (!wdttask->fed_watchdog) { - cpu=xTaskGetAffinity(wdttask->task_handle)==0?"CPU 0":"CPU 1"; - if (xTaskGetAffinity(wdttask->task_handle)==tskNO_AFFINITY) cpu="CPU 0/1"; - printf(" - %s (%s)\n", pcTaskGetTaskName(wdttask->task_handle), cpu); - } - } - ets_printf("Tasks currently running:\n"); - for (int x=0; xnext) { + if (!wdttask->fed_watchdog) { + cpu=xTaskGetAffinity(wdttask->task_handle)==0?"CPU 0":"CPU 1"; + if (xTaskGetAffinity(wdttask->task_handle)==tskNO_AFFINITY) cpu="CPU 0/1"; + printf(" - %s (%s)\n", pcTaskGetTaskName(wdttask->task_handle), cpu); + } + } + ets_printf("Tasks currently running:\n"); + for (int x=0; xnext) { - //See if we are at the current task. - if (wdttask->task_handle == handle) { - wdttask->fed_watchdog=true; - found_task=true; - } - //If even one task in the list doesn't have the do_feed_wdt var set, we do not feed the watchdog. - if (!wdttask->fed_watchdog) do_feed_wdt=false; - } - - if (!found_task) { - //This is the first time the task calls the task_wdt_feed function. Create a new entry for it in - //the linked list. - wdt_task_t *newtask=malloc(sizeof(wdt_task_t)); - memset(newtask, 0, sizeof(wdt_task_t)); - newtask->task_handle=handle; - newtask->fed_watchdog=true; - if (wdt_task_list == NULL) { - wdt_task_list=newtask; - } else { - for (wdttask=wdt_task_list; wdttask->next!=NULL; wdttask=wdttask->next) ; - wdttask->next=newtask; - } - } - if (do_feed_wdt) { - //All tasks have checked in; time to feed the hw watchdog. - TIMERG0.wdt_wprotect=WDT_WRITE_KEY; - TIMERG0.wdt_feed=1; - TIMERG0.wdt_wprotect=0; - //Reset fed_watchdog status - for (wdttask=wdt_task_list; wdttask->next!=NULL; wdttask=wdttask->next) wdttask->fed_watchdog=false; - } + wdt_task_t *wdttask=wdt_task_list; + bool found_task=false, do_feed_wdt=true; + TaskHandle_t handle=xTaskGetCurrentTaskHandle(); + //Walk the linked list of wdt tasks to find this one, as well as see if we need to feed + //the real watchdog timer. + for (wdttask=wdt_task_list; wdttask!=NULL; wdttask=wdttask->next) { + //See if we are at the current task. + if (wdttask->task_handle == handle) { + wdttask->fed_watchdog=true; + found_task=true; + } + //If even one task in the list doesn't have the do_feed_wdt var set, we do not feed the watchdog. + if (!wdttask->fed_watchdog) do_feed_wdt=false; + } + + if (!found_task) { + //This is the first time the task calls the task_wdt_feed function. Create a new entry for it in + //the linked list. + wdt_task_t *newtask=malloc(sizeof(wdt_task_t)); + memset(newtask, 0, sizeof(wdt_task_t)); + newtask->task_handle=handle; + newtask->fed_watchdog=true; + if (wdt_task_list == NULL) { + wdt_task_list=newtask; + } else { + for (wdttask=wdt_task_list; wdttask->next!=NULL; wdttask=wdttask->next) ; + wdttask->next=newtask; + } + } + if (do_feed_wdt) { + //All tasks have checked in; time to feed the hw watchdog. + TIMERG0.wdt_wprotect=WDT_WRITE_KEY; + TIMERG0.wdt_feed=1; + TIMERG0.wdt_wprotect=0; + //Reset fed_watchdog status + for (wdttask=wdt_task_list; wdttask->next!=NULL; wdttask=wdttask->next) wdttask->fed_watchdog=false; + } } void task_wdt_delete() { - TaskHandle_t handle=xTaskGetCurrentTaskHandle(); - wdt_task_t *wdttask=wdt_task_list; - //Wdt task list can't be empty - if (!wdt_task_list) { - ESP_LOGE(TAG, "task_wdt_delete: No tasks in list?"); - return; - } - if (handle==wdt_task_list) { - //Current task is first on list. - wdt_task_list=wdt_task_list->next; - free(wdttask); - } else { - //Find current task in list - while (wdttask->next!=NULL && wdttask->next->task_handle!=handle) wdttask=wdttask->next; - if (!wdttask->next) { - ESP_LOGE(TAG, "task_wdt_delete: Task never called task_wdt_feed!"); - return; - } - wdt_task_t *freeme=wdttask->next; - wdttask->next=wdttask->next->next; - free(freeme); - } + TaskHandle_t handle=xTaskGetCurrentTaskHandle(); + wdt_task_t *wdttask=wdt_task_list; + //Wdt task list can't be empty + if (!wdt_task_list) { + ESP_LOGE(TAG, "task_wdt_delete: No tasks in list?"); + return; + } + if (handle==wdt_task_list) { + //Current task is first on list. + wdt_task_list=wdt_task_list->next; + free(wdttask); + } else { + //Find current task in list + while (wdttask->next!=NULL && wdttask->next->task_handle!=handle) wdttask=wdttask->next; + if (!wdttask->next) { + ESP_LOGE(TAG, "task_wdt_delete: Task never called task_wdt_feed!"); + return; + } + wdt_task_t *freeme=wdttask->next; + wdttask->next=wdttask->next->next; + free(freeme); + } } void task_wdt_init() { - TIMERG0.wdt_wprotect=WDT_WRITE_KEY; - TIMERG0.wdt_config0.sys_reset_length=7; //3.2uS - TIMERG0.wdt_config0.cpu_reset_length=7; //3.2uS - TIMERG0.wdt_config0.level_int_en=1; - TIMERG0.wdt_config0.stg0=1; //1st stage timeout: interrupt - TIMERG0.wdt_config0.stg1=3; //2nd stage timeout: reset system - TIMERG0.wdt_config1.clk_prescale=80*500; //Prescaler: wdt counts in ticks of 0.5mS - TIMERG0.wdt_config2=CONFIG_TASK_WDT_TIMEOUT_S*2000; //Set timeout before interrupt - TIMERG0.wdt_config3=CONFIG_TASK_WDT_TIMEOUT_S*4000; //Set timeout before reset - TIMERG0.wdt_config0.en=1; - TIMERG0.wdt_feed=1; - TIMERG0.wdt_wprotect=0; - ESP_INTR_DISABLE(ETS_T0_WDT_INUM); - intr_matrix_set(xPortGetCoreID(), ETS_TG0_WDT_LEVEL_INTR_SOURCE, ETS_T0_WDT_INUM); - xt_set_interrupt_handler(ETS_T0_WDT_INUM, task_wdt_isr, NULL); - TIMERG0.int_clr_timers.wdt=1; - TIMERG0.int_ena.wdt=1; - ESP_INTR_ENABLE(ETS_T0_WDT_INUM); + TIMERG0.wdt_wprotect=WDT_WRITE_KEY; + TIMERG0.wdt_config0.sys_reset_length=7; //3.2uS + TIMERG0.wdt_config0.cpu_reset_length=7; //3.2uS + TIMERG0.wdt_config0.level_int_en=1; + TIMERG0.wdt_config0.stg0=1; //1st stage timeout: interrupt + TIMERG0.wdt_config0.stg1=3; //2nd stage timeout: reset system + TIMERG0.wdt_config1.clk_prescale=80*500; //Prescaler: wdt counts in ticks of 0.5mS + TIMERG0.wdt_config2=CONFIG_TASK_WDT_TIMEOUT_S*2000; //Set timeout before interrupt + TIMERG0.wdt_config3=CONFIG_TASK_WDT_TIMEOUT_S*4000; //Set timeout before reset + TIMERG0.wdt_config0.en=1; + TIMERG0.wdt_feed=1; + TIMERG0.wdt_wprotect=0; + ESP_INTR_DISABLE(ETS_T0_WDT_INUM); + intr_matrix_set(xPortGetCoreID(), ETS_TG0_WDT_LEVEL_INTR_SOURCE, ETS_T0_WDT_INUM); + xt_set_interrupt_handler(ETS_T0_WDT_INUM, task_wdt_isr, NULL); + TIMERG0.int_clr_timers.wdt=1; + TIMERG0.int_ena.wdt=1; + ESP_INTR_ENABLE(ETS_T0_WDT_INUM); } #if CONFIG_TASK_WDT_CHECK_IDLE_TASK void vApplicationIdleHook(void) { #if !CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1 - if (xPortGetCoreID()!=0) return; + if (xPortGetCoreID()!=0) return; #endif - task_wdt_feed(); + task_wdt_feed(); } #endif From 1ca97f5adbc426e2968f446685de4e365586bf2a Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Wed, 26 Oct 2016 11:06:53 +0800 Subject: [PATCH 057/149] Move panic handler code from FreeRTOS to esp32 component --- .../include/freertos/panic.h => esp32/include/esp_panic.h} | 0 components/{freertos => esp32}/panic.c | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename components/{freertos/include/freertos/panic.h => esp32/include/esp_panic.h} (100%) rename components/{freertos => esp32}/panic.c (100%) diff --git a/components/freertos/include/freertos/panic.h b/components/esp32/include/esp_panic.h similarity index 100% rename from components/freertos/include/freertos/panic.h rename to components/esp32/include/esp_panic.h diff --git a/components/freertos/panic.c b/components/esp32/panic.c similarity index 100% rename from components/freertos/panic.c rename to components/esp32/panic.c From 7d254eb3f0486b9c81d02f769080325336d996a0 Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Wed, 26 Oct 2016 12:23:01 +0800 Subject: [PATCH 058/149] Move panic handler and gdbstub into esp32 component, clean up wdt according to merge req suggestions --- components/esp32/Kconfig | 41 +++++++ components/esp32/cpu_start.c | 12 +-- components/{freertos => esp32}/gdbstub.c | 2 +- .../gdbstub.h => esp32/include/esp_gdbstub.h} | 2 +- components/esp32/include/esp_int_wdt.h | 20 +++- components/esp32/include/esp_panic.h | 2 +- components/esp32/include/esp_task_wdt.h | 20 +++- components/esp32/include/soc/rtc_cntl_reg.h | 2 + .../esp32/include/soc/timer_group_reg.h | 2 + components/esp32/int_wdt.c | 17 +-- components/esp32/panic.c | 22 ++-- components/esp32/task_wdt.c | 22 +--- components/freertos/Kconfig | 39 ------- components/freertos/port.c | 4 +- components/freertos/xtensa_vectors.S | 100 +++++++++--------- 15 files changed, 161 insertions(+), 146 deletions(-) rename components/{freertos => esp32}/gdbstub.c (99%) rename components/{freertos/gdbstub.h => esp32/include/esp_gdbstub.h} (93%) diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index 7fc5090f4..9e141529b 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -81,8 +81,10 @@ config TRACEMEM_RESERVE_DRAM default 0x4000 if MEMMAP_TRACEMEM && !MEMMAP_TRACEMEM_TWOBANKS default 0x0 +# Not implemented and/or needs new silicon rev to work config MEMMAP_SPISRAM bool "Use external SPI SRAM chip as main memory" + depends on ESP32_NEEDS_NEW_SILICON_REV default "n" help The ESP32 can control an external SPI SRAM chip, adding the memory it contains to the @@ -153,6 +155,45 @@ config ULP_COPROC_RESERVE_MEM depends on !ULP_COPROC_ENABLED +choice ESP32_PANIC + prompt "Panic handler behaviour" + default FREERTOS_PANIC_PRINT_REBOOT + help + If FreeRTOS detects unexpected behaviour or an unhandled exception, the panic handler is + invoked. Configure the panic handlers action here. + +config ESP32_PANIC_PRINT_HALT + bool "Print registers and halt" + help + Outputs the relevant registers over the serial port and halt the + processor. Needs a manual reset to restart. + +config ESP32_PANIC_PRINT_REBOOT + bool "Print registers and reboot" + help + Outputs the relevant registers over the serial port and immediately + reset the processor. + +config ESP32_PANIC_SILENT_REBOOT + bool "Silent reboot" + help + Just resets the processor without outputting anything + +config ESP32_PANIC_GDBSTUB + bool "Invoke GDBStub" + help + Invoke gdbstub on the serial port, allowing for gdb to attach to it to do a postmortem + of the crash. +endchoice + +config ESP32_DEBUG_OCDAWARE + bool "Make exception and panic handlers JTAG/OCD aware" + default y + help + The FreeRTOS panic and unhandled exception handers can detect a JTAG OCD debugger and + instead of panicking, have the debugger stop on the offending instruction. + + config INT_WDT bool "Interrupt watchdog" default y diff --git a/components/esp32/cpu_start.c b/components/esp32/cpu_start.c index aecea66ef..37205dcd9 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -147,19 +147,19 @@ void start_cpu0_default(void) #endif esp_set_cpu_freq(); // set CPU frequency configured in menuconfig uart_div_modify(0, (APB_CLK_FREQ << 4) / 115200); - ets_setup_syscalls(); - do_global_ctors(); - esp_ipc_init(); - spi_flash_init(); #if CONFIG_BROWNOUT_DET esp_brownout_init(); #endif #if CONFIG_INT_WDT - int_wdt_init(); + esp_int_wdt_init(); #endif #if CONFIG_TASK_WDT - task_wdt_init(); + esp_task_wdt_init(); #endif + ets_setup_syscalls(); + do_global_ctors(); + esp_ipc_init(); + spi_flash_init(); xTaskCreatePinnedToCore(&main_task, "main", ESP_TASK_MAIN_STACK, NULL, diff --git a/components/freertos/gdbstub.c b/components/esp32/gdbstub.c similarity index 99% rename from components/freertos/gdbstub.c rename to components/esp32/gdbstub.c index 1a92299bd..d75fced4c 100644 --- a/components/freertos/gdbstub.c +++ b/components/esp32/gdbstub.c @@ -24,7 +24,7 @@ #include "soc/uart_reg.h" #include "soc/io_mux_reg.h" -#include "gdbstub.h" +#include "esp_gdbstub.h" //Length of buffer used to reserve GDB commands. Has to be at least able to fit the G command, which //implies a minimum size of about 320 bytes. diff --git a/components/freertos/gdbstub.h b/components/esp32/include/esp_gdbstub.h similarity index 93% rename from components/freertos/gdbstub.h rename to components/esp32/include/esp_gdbstub.h index 5e97213c9..bc26f2941 100644 --- a/components/freertos/gdbstub.h +++ b/components/esp32/include/esp_gdbstub.h @@ -17,6 +17,6 @@ #include #include "freertos/xtensa_api.h" -void gdbstubPanicHandler(XtExcFrame *frame); +void esp_gdbstub_panic_handler(XtExcFrame *frame); #endif \ No newline at end of file diff --git a/components/esp32/include/esp_int_wdt.h b/components/esp32/include/esp_int_wdt.h index dc5bd0dda..438740039 100644 --- a/components/esp32/include/esp_int_wdt.h +++ b/components/esp32/include/esp_int_wdt.h @@ -23,15 +23,29 @@ extern "C" { * @{ */ +/* +This routine enables a watchdog to catch instances of processes disabling +interrupts for too long, or code within interrupt handlers taking too long. +It does this by setting up a watchdog which gets fed from the FreeRTOS +task switch interrupt. When this watchdog times out, initially it will call +a high-level interrupt routine that will panic FreeRTOS in order to allow +for forensic examination of the state of the CPU. When this interrupt +handler is not called and the watchdog times out a second time, it will +reset the SoC. + +This uses the TIMERG1 WDT. +*/ + + /** - * @brief Initialize the interrupt watchdog. This is called in the init code, no need to - * call it explicitly. + * @brief Initialize the interrupt watchdog. This is called in the init code if + * the interrupt watchdog is enabled in menuconfig. * * @param null * * @return null */ -void int_wdt_init(); +void esp_int_wdt_init(); /** diff --git a/components/esp32/include/esp_panic.h b/components/esp32/include/esp_panic.h index 89dee5b24..6aba6c5f4 100644 --- a/components/esp32/include/esp_panic.h +++ b/components/esp32/include/esp_panic.h @@ -14,7 +14,7 @@ #ifndef __ASSEMBLER__ -void setBreakpointIfJtag(void *fn); +void esp_set_breakpoint_if_jtag(void *fn); #endif diff --git a/components/esp32/include/esp_task_wdt.h b/components/esp32/include/esp_task_wdt.h index c050616af..bbc499567 100644 --- a/components/esp32/include/esp_task_wdt.h +++ b/components/esp32/include/esp_task_wdt.h @@ -28,15 +28,25 @@ extern "C" { * @{ */ +/* +This routine enables a more general-purpose task watchdog: tasks can individually +feed the watchdog and the watchdog will bark if one or more tasks haven't fed the +watchdog within the specified time. Optionally, the idle tasks can also configured +to feed the watchdog in a similar fashion, to detect CPU starvation. + +This uses the TIMERG0 WDT. +*/ + + /** - * @brief Initialize the task watchdog. This is called in the init code, no need to - * call it explicitly. + * @brief Initialize the task watchdog. This is called in the init code, if the + * task watchdog is enabled in menuconfig. * * @param null * * @return null */ -void task_wdt_init(); +void esp_task_wdt_init(); /** * @brief Feed the watchdog. After the first feeding session, the watchdog will expect the calling @@ -47,7 +57,7 @@ void task_wdt_init(); * @return null */ -void task_wdt_feed(); +void esp_task_wdt_feed(); /** @@ -57,7 +67,7 @@ void task_wdt_feed(); * * @return null */ -void task_wdt_delete(); +void esp_task_wdt_delete(); /** * @} diff --git a/components/esp32/include/soc/rtc_cntl_reg.h b/components/esp32/include/soc/rtc_cntl_reg.h index 47328611e..24e0f1403 100644 --- a/components/esp32/include/soc/rtc_cntl_reg.h +++ b/components/esp32/include/soc/rtc_cntl_reg.h @@ -14,6 +14,8 @@ #ifndef _SOC_RTC_CNTL_REG_H_ #define _SOC_RTC_CNTL_REG_H_ +#define WDT_WRITE_KEY 0x50D83AA1 + #include "soc.h" #define RTC_CNTL_OPTIONS0_REG (DR_REG_RTCCNTL_BASE + 0x0) diff --git a/components/esp32/include/soc/timer_group_reg.h b/components/esp32/include/soc/timer_group_reg.h index 96a5eb790..0d67cab51 100644 --- a/components/esp32/include/soc/timer_group_reg.h +++ b/components/esp32/include/soc/timer_group_reg.h @@ -15,6 +15,8 @@ #define __TIMG_REG_H__ #include "soc.h" +#define WDT_WRITE_KEY 0x50D83AA1 + #define REG_TIMG_BASE(i) (DR_REG_TIMERGROUP0_BASE + i*0x1000) #define TIMG_T0CONFIG_REG(i) (REG_TIMG_BASE(i) + 0x0000) /* TIMG_T0_EN : R/W ;bitpos:[31] ;default: 1'h0 ; */ diff --git a/components/esp32/int_wdt.c b/components/esp32/int_wdt.c index 8c506bde1..5f123ee36 100644 --- a/components/esp32/int_wdt.c +++ b/components/esp32/int_wdt.c @@ -13,18 +13,6 @@ // limitations under the License. -/* -This routine enables a watchdog to catch instances of processes disabling -interrupts for too long, or code within interrupt handlers taking too long. -It does this by setting up a watchdog which gets fed from the FreeRTOS -task switch interrupt. When this watchdog times out, initially it will call -a high-level interrupt routine that will panic FreeRTOS in order to allow -for forensic examination of the state of the CPU. When this interrupt -handler is not called and the watchdog times out a second time, it will -reset the SoC. - -This uses the TIMERG1 WDT. -*/ #include "sdkconfig.h" #include @@ -37,6 +25,7 @@ This uses the TIMERG1 WDT. #include "esp_err.h" #include "esp_intr.h" #include "soc/timer_group_struct.h" +#include "soc/timer_group_reg.h" #include "esp_int_wdt.h" @@ -45,9 +34,8 @@ This uses the TIMERG1 WDT. #define WDT_INT_NUM 24 -#define WDT_WRITE_KEY 0x50D83AA1 -void int_wdt_init() { +void esp_int_wdt_init() { TIMERG1.wdt_wprotect=WDT_WRITE_KEY; TIMERG1.wdt_config0.sys_reset_length=7; //3.2uS TIMERG1.wdt_config0.cpu_reset_length=7; //3.2uS @@ -73,6 +61,7 @@ void int_wdt_init() { } +//Take care: the tick hook can also be called before esp_int_wdt_init() is called. #if CONFIG_INT_WDT_CHECK_CPU1 //Not static; the ISR assembly checks this. bool int_wdt_app_cpu_ticked=false; diff --git a/components/esp32/panic.c b/components/esp32/panic.c index 0735e6b04..758d581d0 100644 --- a/components/esp32/panic.c +++ b/components/esp32/panic.c @@ -26,11 +26,10 @@ #include "soc/dport_reg.h" #include "soc/rtc_cntl_reg.h" #include "soc/timer_group_struct.h" +#include "soc/timer_group_reg.h" -#include "gdbstub.h" -#include "panic.h" - -#define WDT_WRITE_KEY 0x50D83AA1 +#include "esp_gdbstub.h" +#include "esp_panic.h" /* @@ -196,7 +195,13 @@ void xt_unhandled_exception(XtExcFrame *frame) { } -//Disables all but one WDT, and allows enough time on that WDT to do what we need to do. +/* +If watchdogs are enabled, the panic handler runs the risk of getting aborted pre-emptively because +an overzealous watchdog decides to reset it. On the other hand, if we disable all watchdogs, we run +the risk of somehow halting in the panic handler and not resetting. That is why this routine kills +all watchdogs except the timer group 0 watchdog, and it reconfigures that to reset the chip after +one second. +*/ static void reconfigureAllWdts() { TIMERG0.wdt_wprotect=WDT_WRITE_KEY; TIMERG0.wdt_feed=1; @@ -213,6 +218,9 @@ static void reconfigureAllWdts() { TIMERG1.wdt_wprotect=0; } +/* +This disables all the watchdogs for when we call the gdbstub. +*/ static void disableAllWdts() { TIMERG0.wdt_wprotect=WDT_WRITE_KEY; TIMERG0.wdt_config0.en=0; @@ -254,7 +262,7 @@ void commonErrorHandler(XtExcFrame *frame) { #if CONFIG_FREERTOS_PANIC_GDBSTUB disableAllWdts(); panicPutStr("Entering gdb stub now.\r\n"); - gdbstubPanicHandler(frame); + esp_gdbstub_panic_handler(frame); #elif CONFIG_FREERTOS_PANIC_PRINT_REBOOT || CONFIG_FREERTOS_PANIC_SILENT_REBOOT panicPutStr("Rebooting...\r\n"); for (x=0; x<100; x++) ets_delay_us(1000); @@ -267,7 +275,7 @@ void commonErrorHandler(XtExcFrame *frame) { } -void setBreakpointIfJtag(void *fn) { +void esp_set_breakpoint_if_jtag(void *fn) { if (!inOCDMode()) return; setFirstBreakpoint((uint32_t)fn); } diff --git a/components/esp32/task_wdt.c b/components/esp32/task_wdt.c index e6a462088..24d3977b1 100644 --- a/components/esp32/task_wdt.c +++ b/components/esp32/task_wdt.c @@ -13,14 +13,6 @@ // limitations under the License. -/* -This routine enables a more general-purpose task watchdog: tasks can individually -feed the watchdog and the watchdog will bark if one or more tasks haven't fed the -watchdog within the specified time. Optionally, the idle tasks can also configured -to feed the watchdog in a similar fashion, to detect CPU starvation. - -This uses the TIMERG0 WDT. -*/ #include #include @@ -35,6 +27,7 @@ This uses the TIMERG0 WDT. #include "esp_intr.h" #include "esp_attr.h" #include "soc/timer_group_struct.h" +#include "soc/timer_group_reg.h" #include "esp_log.h" #include "esp_task_wdt.h" @@ -52,11 +45,6 @@ struct wdt_task_t { static wdt_task_t *wdt_task_list=NULL; -//We use this interrupt number on whatever task calls task_wdt_init. -#define WDT_INT_NUM 24 - -#define WDT_WRITE_KEY 0x50D83AA1 - static void IRAM_ATTR task_wdt_isr(void *arg) { wdt_task_t *wdttask; const char *cpu; @@ -87,7 +75,7 @@ static void IRAM_ATTR task_wdt_isr(void *arg) { } -void task_wdt_feed() { +void esp_task_wdt_feed() { wdt_task_t *wdttask=wdt_task_list; bool found_task=false, do_feed_wdt=true; TaskHandle_t handle=xTaskGetCurrentTaskHandle(); @@ -127,7 +115,7 @@ void task_wdt_feed() { } } -void task_wdt_delete() { +void esp_task_wdt_delete() { TaskHandle_t handle=xTaskGetCurrentTaskHandle(); wdt_task_t *wdttask=wdt_task_list; //Wdt task list can't be empty @@ -152,7 +140,7 @@ void task_wdt_delete() { } } -void task_wdt_init() { +void esp_task_wdt_init() { TIMERG0.wdt_wprotect=WDT_WRITE_KEY; TIMERG0.wdt_config0.sys_reset_length=7; //3.2uS TIMERG0.wdt_config0.cpu_reset_length=7; //3.2uS @@ -178,7 +166,7 @@ void vApplicationIdleHook(void) { #if !CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1 if (xPortGetCoreID()!=0) return; #endif - task_wdt_feed(); + esp_task_wdt_feed(); } #endif diff --git a/components/freertos/Kconfig b/components/freertos/Kconfig index 1a65e1eeb..413c710d2 100644 --- a/components/freertos/Kconfig +++ b/components/freertos/Kconfig @@ -93,45 +93,6 @@ config FREERTOS_THREAD_LOCAL_STORAGE_POINTERS If using the WiFi stack, this value must be at least 1. -#This still needs to be implemented. -choice FREERTOS_PANIC - prompt "Panic handler behaviour" - default FREERTOS_PANIC_PRINT_REBOOT - help - If FreeRTOS detects unexpected behaviour or an unhandled exception, the panic handler is - invoked. Configure the panic handlers action here. - -config FREERTOS_PANIC_PRINT_HALT - bool "Print registers and halt" - help - Outputs the relevant registers over the serial port and halt the - processor. Needs a manual reset to restart. - -config FREERTOS_PANIC_PRINT_REBOOT - bool "Print registers and reboot" - help - Outputs the relevant registers over the serial port and immediately - reset the processor. - -config FREERTOS_PANIC_SILENT_REBOOT - bool "Silent reboot" - help - Just resets the processor without outputting anything - -config FREERTOS_PANIC_GDBSTUB - bool "Invoke GDBStub" - help - Invoke gdbstub on the serial port, allowing for gdb to attach to it to do a postmortem - of the crash. -endchoice - -config FREERTOS_DEBUG_OCDAWARE - bool "Make exception and panic handlers JTAG/OCD aware" - default y - help - The FreeRTOS panic and unhandled exception handers can detect a JTAG OCD debugger and - instead of panicking, have the debugger stop on the offending instruction. - choice FREERTOS_ASSERT prompt "FreeRTOS assertions" default FREERTOS_ASSERT_FAIL_ABORT diff --git a/components/freertos/port.c b/components/freertos/port.c index a982db7d4..834d787be 100644 --- a/components/freertos/port.c +++ b/components/freertos/port.c @@ -101,7 +101,7 @@ #include "FreeRTOS.h" #include "task.h" -#include "panic.h" +#include "esp_panic.h" /* Defined in portasm.h */ extern void _frxt_tick_timer_init(void); @@ -375,7 +375,7 @@ portBASE_TYPE vPortCPUReleaseMutex(portMUX_TYPE *mux) { #if CONFIG_FREERTOS_BREAK_ON_SCHEDULER_START_JTAG void vPortFirstTaskHook(TaskFunction_t function) { - setBreakpointIfJtag(function); + esp_set_breakpoint_if_jtag(function); } #endif diff --git a/components/freertos/xtensa_vectors.S b/components/freertos/xtensa_vectors.S index fcf9af1cd..94acbd896 100644 --- a/components/freertos/xtensa_vectors.S +++ b/components/freertos/xtensa_vectors.S @@ -91,7 +91,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *******************************************************************************/ #include "xtensa_rtos.h" -#include "panic.h" +#include "esp_panic.h" #include "sdkconfig.h" /* Define for workaround: pin no-cpu-affinity tasks to a cpu when fpu is used. @@ -303,12 +303,12 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ .section .iram1,"ax" - .global panicHandler + .global panicHandler .global _xt_panic .type _xt_panic,@function .align 4 - .literal_position + .literal_position .align 4 _xt_panic: @@ -344,41 +344,41 @@ _xt_panic: movi a0, PS_INTLEVEL(7) | PS_UM | PS_WOE wsr a0, PS - //Call panic handler - mov a6,sp - call4 panicHandler + //Call panic handler + mov a6,sp + call4 panicHandler 1: j 1b /* loop infinitely */ - retw + retw - .align 4 + .align 4 //Call using call0. Prints the hex char in a2. Kills a3, a4, a5 panic_print_hex: - movi a3,0x60000000 - movi a4,8 + movi a3,0x60000000 + movi a4,8 panic_print_hex_loop: - l32i a5, a3, 0x1c - extui a5, a5, 16, 8 - bgei a5,64,panic_print_hex_loop + l32i a5, a3, 0x1c + extui a5, a5, 16, 8 + bgei a5,64,panic_print_hex_loop - srli a5,a2,28 - bgei a5,10,panic_print_hex_a - addi a5,a5,'0' - j panic_print_hex_ok + srli a5,a2,28 + bgei a5,10,panic_print_hex_a + addi a5,a5,'0' + j panic_print_hex_ok panic_print_hex_a: - addi a5,a5,'A'-10 + addi a5,a5,'A'-10 panic_print_hex_ok: - s32i a5,a3,0 - slli a2,a2,4 - - addi a4,a4,-1 - bnei a4,0,panic_print_hex_loop - movi a5,' ' - s32i a5,a3,0 + s32i a5,a3,0 + slli a2,a2,4 + + addi a4,a4,-1 + bnei a4,0,panic_print_hex_loop + movi a5,' ' + s32i a5,a3,0 - ret + ret @@ -463,8 +463,8 @@ _DebugExceptionVector: jx a3 #else wsr a0, EXCSAVE+XCHAL_DEBUGLEVEL /* save original a0 somewhere */ - movi a0,PANIC_RSN_DEBUGEXCEPTION - wsr a0,EXCCAUSE + movi a0,PANIC_RSN_DEBUGEXCEPTION + wsr a0,EXCCAUSE call0 _xt_panic /* does not return */ rfi XCHAL_DEBUGLEVEL /* make a0 point here not later */ #endif @@ -492,8 +492,8 @@ _DoubleExceptionVector: #if XCHAL_HAVE_DEBUG break 1, 4 /* unhandled double exception */ #endif - movi a0,PANIC_RSN_DOUBLEEXCEPTION - wsr a0,EXCCAUSE + movi a0,PANIC_RSN_DOUBLEEXCEPTION + wsr a0,EXCCAUSE call0 _xt_panic /* does not return */ rfde /* make a0 point here not later */ @@ -527,8 +527,8 @@ _xt_kernel_exc: #if XCHAL_HAVE_DEBUG break 1, 0 /* unhandled kernel exception */ #endif - movi a0,PANIC_RSN_KERNELEXCEPTION - wsr a0,EXCCAUSE + movi a0,PANIC_RSN_KERNELEXCEPTION + wsr a0,EXCCAUSE call0 _xt_panic /* does not return */ rfe /* make a0 point here not there */ @@ -916,11 +916,11 @@ _xt_coproc_exc: addi a2, a2, TASKTCB_XCOREID_OFFSET /* offset to xCoreID in tcb struct */ s32i a3, a2, 0 /* store current cpuid */ - /* Grab correct xt_coproc_owner_sa for this core */ - movi a2, XCHAL_CP_MAX << 2 - mull a2, a2, a3 + /* Grab correct xt_coproc_owner_sa for this core */ + movi a2, XCHAL_CP_MAX << 2 + mull a2, a2, a3 movi a3, _xt_coproc_owner_sa /* a3 = base of owner array */ - add a3, a3, a2 + add a3, a3, a2 extui a2, a0, 0, 16 /* coprocessor bitmask portion */ or a4, a4, a2 /* a4 = CPENABLE | (1 << n) */ @@ -1031,8 +1031,8 @@ _xt_coproc_exc: #if XCHAL_HAVE_DEBUG break 1, 1 /* unhandled user exception */ #endif - movi a0,PANIC_RSN_COPROCEXCEPTION - wsr a0,EXCCAUSE + movi a0,PANIC_RSN_COPROCEXCEPTION + wsr a0,EXCCAUSE call0 _xt_panic /* not in a thread (invalid) */ /* never returns */ @@ -1620,19 +1620,19 @@ _xt_highint4: /* On the ESP32, this level is used for the INT_WDT handler. If that triggers, the program is stuck with interrupts off and the CPU should panic. */ - rsr a0, EXCSAVE_4 - wsr a0, EXCSAVE_1 /* panic handler reads this register */ - /* Set EXCCAUSE to reflect cause of the wdt int trigger */ - movi a0,PANIC_RSN_INTWDT_CPU0 - wsr a0,EXCCAUSE + rsr a0, EXCSAVE_4 + wsr a0, EXCSAVE_1 /* panic handler reads this register */ + /* Set EXCCAUSE to reflect cause of the wdt int trigger */ + movi a0,PANIC_RSN_INTWDT_CPU0 + wsr a0,EXCCAUSE #if CONFIG_INT_WDT_CHECK_CPU1 - /* Check if the cause is the app cpu failing to tick.*/ - movi a0, int_wdt_app_cpu_ticked - l32i a0, a0, 0 - bnez a0, 1f - /* It is. Modify cause. */ - movi a0,PANIC_RSN_INTWDT_CPU1 - wsr a0,EXCCAUSE + /* Check if the cause is the app cpu failing to tick.*/ + movi a0, int_wdt_app_cpu_ticked + l32i a0, a0, 0 + bnez a0, 1f + /* It is. Modify cause. */ + movi a0,PANIC_RSN_INTWDT_CPU1 + wsr a0,EXCCAUSE 1: #endif call0 _xt_panic From bb1efe50c3c86c6d466834cef8f5110c0163f404 Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Wed, 26 Oct 2016 12:23:35 +0800 Subject: [PATCH 059/149] Remove superfluous backup files --- components/freertos/xtensa_vectors.S-new | 1915 -------------------- components/freertos/xtensa_vectors.S-old | 2064 ---------------------- 2 files changed, 3979 deletions(-) delete mode 100644 components/freertos/xtensa_vectors.S-new delete mode 100644 components/freertos/xtensa_vectors.S-old diff --git a/components/freertos/xtensa_vectors.S-new b/components/freertos/xtensa_vectors.S-new deleted file mode 100644 index 88349eee9..000000000 --- a/components/freertos/xtensa_vectors.S-new +++ /dev/null @@ -1,1915 +0,0 @@ -/******************************************************************************* -Copyright (c) 2006-2015 Cadence Design Systems Inc. - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------------------------------- - - XTENSA VECTORS AND LOW LEVEL HANDLERS FOR AN RTOS - - Xtensa low level exception and interrupt vectors and handlers for an RTOS. - - Interrupt handlers and user exception handlers support interaction with - the RTOS by calling XT_RTOS_INT_ENTER and XT_RTOS_INT_EXIT before and - after user's specific interrupt handlers. These macros are defined in - xtensa_.h to call suitable functions in a specific RTOS. - - Users can install application-specific interrupt handlers for low and - medium level interrupts, by calling xt_set_interrupt_handler(). These - handlers can be written in C, and must obey C calling convention. The - handler table is indexed by the interrupt number. Each handler may be - provided with an argument. - - Note that the system timer interrupt is handled specially, and is - dispatched to the RTOS-specific handler. This timer cannot be hooked - by application code. - - Optional hooks are also provided to install a handler per level at - run-time, made available by compiling this source file with - '-DXT_INTEXC_HOOKS' (useful for automated testing). - -!! This file is a template that usually needs to be modified to handle !! -!! application specific interrupts. Search USER_EDIT for helpful comments !! -!! on where to insert handlers and how to write them. !! - - Users can also install application-specific exception handlers in the - same way, by calling xt_set_exception_handler(). One handler slot is - provided for each exception type. Note that some exceptions are handled - by the porting layer itself, and cannot be taken over by application - code in this manner. These are the alloca, syscall, and coprocessor - exceptions. - - The exception handlers can be written in C, and must follow C calling - convention. Each handler is passed a pointer to an exception frame as - its single argument. The exception frame is created on the stack, and - holds the saved context of the thread that took the exception. If the - handler returns, the context will be restored and the instruction that - caused the exception will be retried. If the handler makes any changes - to the saved state in the exception frame, the changes will be applied - when restoring the context. - - Because Xtensa is a configurable architecture, this port supports all user - generated configurations (except restrictions stated in the release notes). - This is accomplished by conditional compilation using macros and functions - defined in the Xtensa HAL (hardware adaptation layer) for your configuration. - Only the relevant parts of this file will be included in your RTOS build. - For example, this file provides interrupt vector templates for all types and - all priority levels, but only the ones in your configuration are built. - - NOTES on the use of 'call0' for long jumps instead of 'j': - 1. This file should be assembled with the -mlongcalls option to xt-xcc. - 2. The -mlongcalls compiler option causes 'call0 dest' to be expanded to - a sequence 'l32r a0, dest' 'callx0 a0' which works regardless of the - distance from the call to the destination. The linker then relaxes - it back to 'call0 dest' if it determines that dest is within range. - This allows more flexibility in locating code without the performance - overhead of the 'l32r' literal data load in cases where the destination - is in range of 'call0'. There is an additional benefit in that 'call0' - has a longer range than 'j' due to the target being word-aligned, so - the 'l32r' sequence is less likely needed. - 3. The use of 'call0' with -mlongcalls requires that register a0 not be - live at the time of the call, which is always the case for a function - call but needs to be ensured if 'call0' is used as a jump in lieu of 'j'. - 4. This use of 'call0' is independent of the C function call ABI. - -*******************************************************************************/ - -#include "xtensa_rtos.h" - - -/* Enable stack backtrace across exception/interrupt - see below */ -#define XT_DEBUG_BACKTRACE 1 - - -/* --------------------------------------------------------------------------------- - Defines used to access _xtos_interrupt_table. --------------------------------------------------------------------------------- -*/ -#define XIE_HANDLER 0 -#define XIE_ARG 4 -#define XIE_SIZE 8 - -/* --------------------------------------------------------------------------------- - Macro extract_msb - return the input with only the highest bit set. - - Input : "ain" - Input value, clobbered. - Output : "aout" - Output value, has only one bit set, MSB of "ain". - The two arguments must be different AR registers. --------------------------------------------------------------------------------- -*/ - - .macro extract_msb aout ain -1: - addi \aout, \ain, -1 /* aout = ain - 1 */ - and \ain, \ain, \aout /* ain = ain & aout */ - bnez \ain, 1b /* repeat until ain == 0 */ - addi \aout, \aout, 1 /* return aout + 1 */ - .endm - -/* --------------------------------------------------------------------------------- - Macro dispatch_c_isr - dispatch interrupts to user ISRs. - This will dispatch to user handlers (if any) that are registered in the - XTOS dispatch table (_xtos_interrupt_table). These handlers would have - been registered by calling _xtos_set_interrupt_handler(). There is one - exception - the timer interrupt used by the OS will not be dispatched - to a user handler - this must be handled by the caller of this macro. - - Level triggered and software interrupts are automatically deasserted by - this code. - - ASSUMPTIONS: - -- PS.INTLEVEL is set to "level" at entry - -- PS.EXCM = 0, C calling enabled - - NOTE: For CALL0 ABI, a12-a15 have not yet been saved. - - NOTE: This macro will use registers a0 and a2-a6. The arguments are: - level -- interrupt level - mask -- interrupt bitmask for this level --------------------------------------------------------------------------------- -*/ - - .macro dispatch_c_isr level mask - - /* Get mask of pending, enabled interrupts at this level into a2. */ - -.L_xt_user_int_&level&: - rsr a2, INTENABLE - rsr a3, INTERRUPT - movi a4, \mask - and a2, a2, a3 - and a2, a2, a4 - beqz a2, 9f /* nothing to do */ - - /* This bit of code provides a nice debug backtrace in the debugger. - It does take a few more instructions, so undef XT_DEBUG_BACKTRACE - if you want to save the cycles. - */ - #if XT_DEBUG_BACKTRACE - #ifndef __XTENSA_CALL0_ABI__ - rsr a0, EPC_1 + \level - 1 /* return address */ - movi a4, 0xC0000000 /* constant with top 2 bits set (call size) */ - or a0, a0, a4 /* set top 2 bits */ - addx2 a0, a4, a0 /* clear top bit -- simulating call4 size */ - #endif - #endif - - #ifdef XT_INTEXC_HOOKS - /* Call interrupt hook if present to (pre)handle interrupts. */ - movi a4, _xt_intexc_hooks - l32i a4, a4, \level << 2 - beqz a4, 2f - #ifdef __XTENSA_CALL0_ABI__ - callx0 a4 - beqz a2, 9f - #else - mov a6, a2 - callx4 a4 - beqz a6, 9f - mov a2, a6 - #endif -2: - #endif - - /* Now look up in the dispatch table and call user ISR if any. */ - /* If multiple bits are set then MSB has highest priority. */ - - extract_msb a4, a2 /* a4 = MSB of a2, a2 trashed */ - - #ifdef XT_USE_SWPRI - /* Enable all interrupts at this level that are numerically higher - than the one we just selected, since they are treated as higher - priority. - */ - movi a3, \mask /* a3 = all interrupts at this level */ - add a2, a4, a4 /* a2 = a4 << 1 */ - addi a2, a2, -1 /* a2 = mask of 1's <= a4 bit */ - and a2, a2, a3 /* a2 = mask of all bits <= a4 at this level */ - movi a3, _xt_intdata - l32i a6, a3, 4 /* a6 = _xt_vpri_mask */ - neg a2, a2 - addi a2, a2, -1 /* a2 = mask to apply */ - and a5, a6, a2 /* mask off all bits <= a4 bit */ - s32i a5, a3, 4 /* update _xt_vpri_mask */ - rsr a3, INTENABLE - and a3, a3, a2 /* mask off all bits <= a4 bit */ - wsr a3, INTENABLE - rsil a3, \level - 1 /* lower interrupt level by 1 */ - #endif - - movi a3, XT_TIMER_INTEN /* a3 = timer interrupt bit */ - wsr a4, INTCLEAR /* clear sw or edge-triggered interrupt */ - beq a3, a4, 7f /* if timer interrupt then skip table */ - - find_ms_setbit a3, a4, a3, 0 /* a3 = interrupt number */ - - movi a4, _xt_interrupt_table - addx8 a3, a3, a4 /* a3 = address of interrupt table entry */ - l32i a4, a3, XIE_HANDLER /* a4 = handler address */ - #ifdef __XTENSA_CALL0_ABI__ - mov a12, a6 /* save in callee-saved reg */ - l32i a2, a3, XIE_ARG /* a2 = handler arg */ - callx0 a4 /* call handler */ - mov a2, a12 - #else - mov a2, a6 /* save in windowed reg */ - l32i a6, a3, XIE_ARG /* a6 = handler arg */ - callx4 a4 /* call handler */ - #endif - - #ifdef XT_USE_SWPRI - j 8f - #else - j .L_xt_user_int_&level& /* check for more interrupts */ - #endif - -7: - - .ifeq XT_TIMER_INTPRI - \level -.L_xt_user_int_timer_&level&: - /* - Interrupt handler for the RTOS tick timer if at this level. - We'll be reading the interrupt state again after this call - so no need to preserve any registers except a6 (vpri_mask). - */ - - #ifdef __XTENSA_CALL0_ABI__ - mov a12, a6 - call0 XT_RTOS_TIMER_INT - mov a2, a12 - #else - mov a2, a6 - call4 XT_RTOS_TIMER_INT - #endif - .endif - - #ifdef XT_USE_SWPRI - j 8f - #else - j .L_xt_user_int_&level& /* check for more interrupts */ - #endif - - #ifdef XT_USE_SWPRI -8: - /* Restore old value of _xt_vpri_mask from a2. Also update INTENABLE from - virtual _xt_intenable which _could_ have changed during interrupt - processing. */ - - movi a3, _xt_intdata - l32i a4, a3, 0 /* a4 = _xt_intenable */ - s32i a2, a3, 4 /* update _xt_vpri_mask */ - and a4, a4, a2 /* a4 = masked intenable */ - wsr a4, INTENABLE /* update INTENABLE */ - #endif - -9: - /* done */ - - .endm - - -/* --------------------------------------------------------------------------------- - Panic handler. - Should be reached by call0 (preferable) or jump only. If call0, a0 says where - from. If on simulator, display panic message and abort, else loop indefinitely. --------------------------------------------------------------------------------- -*/ - - .text - .global _xt_panic - .type _xt_panic,@function - .align 4 - -_xt_panic: - #ifdef XT_SIMULATOR - addi a4, a0, -3 /* point to call0 */ - movi a3, _xt_panic_message - movi a2, SYS_log_msg - simcall - movi a2, SYS_gdb_abort - simcall - #else - rsil a2, XCHAL_EXCM_LEVEL /* disable all low & med ints */ -1: j 1b /* loop infinitely */ - #endif - - .section .rodata, "a" - .align 4 - -_xt_panic_message: - .string "\n*** _xt_panic() was called from 0x%08x or jumped to. ***\n" - - -/* --------------------------------------------------------------------------------- - Hooks to dynamically install handlers for exceptions and interrupts. - Allows automated regression frameworks to install handlers per test. - Consists of an array of function pointers indexed by interrupt level, - with index 0 containing the entry for user exceptions. - Initialized with all 0s, meaning no handler is installed at each level. - See comment in xtensa_rtos.h for more details. --------------------------------------------------------------------------------- -*/ - - #ifdef XT_INTEXC_HOOKS - .data - .global _xt_intexc_hooks - .type _xt_intexc_hooks,@object - .align 4 - -_xt_intexc_hooks: - .fill XT_INTEXC_HOOK_NUM, 4, 0 - #endif - - -/* --------------------------------------------------------------------------------- - EXCEPTION AND LEVEL 1 INTERRUPT VECTORS AND LOW LEVEL HANDLERS - (except window exception vectors). - - Each vector goes at a predetermined location according to the Xtensa - hardware configuration, which is ensured by its placement in a special - section known to the Xtensa linker support package (LSP). It performs - the minimum necessary before jumping to the handler in the .text section. - - The corresponding handler goes in the normal .text section. It sets up - the appropriate stack frame, saves a few vector-specific registers and - calls XT_RTOS_INT_ENTER to save the rest of the interrupted context - and enter the RTOS, then sets up a C environment. It then calls the - user's interrupt handler code (which may be coded in C) and finally - calls XT_RTOS_INT_EXIT to transfer control to the RTOS for scheduling. - - While XT_RTOS_INT_EXIT does not return directly to the interruptee, - eventually the RTOS scheduler will want to dispatch the interrupted - task or handler. The scheduler will return to the exit point that was - saved in the interrupt stack frame at XT_STK_EXIT. --------------------------------------------------------------------------------- -*/ - - -/* --------------------------------------------------------------------------------- -Debug Exception. --------------------------------------------------------------------------------- -*/ - -#if XCHAL_HAVE_DEBUG - - .begin literal_prefix .DebugExceptionVector - .section .DebugExceptionVector.text, "ax" - .global _DebugExceptionVector - .align 4 - -_DebugExceptionVector: - - #ifdef XT_SIMULATOR - /* - In the simulator, let the debugger (if any) handle the debug exception, - or simply stop the simulation: - */ - wsr a2, EXCSAVE+XCHAL_DEBUGLEVEL /* save a2 where sim expects it */ - movi a2, SYS_gdb_enter_sktloop - simcall /* have ISS handle debug exc. */ - #elif 0 /* change condition to 1 to use the HAL minimal debug handler */ - wsr a3, EXCSAVE+XCHAL_DEBUGLEVEL - movi a3, xthal_debugexc_defhndlr_nw /* use default debug handler */ - jx a3 - #else - wsr a0, EXCSAVE+XCHAL_DEBUGLEVEL /* save original a0 somewhere */ - call0 _xt_panic /* does not return */ - rfi XCHAL_DEBUGLEVEL /* make a0 point here not later */ - #endif - - .end literal_prefix - -#endif - -/* --------------------------------------------------------------------------------- -Double Exception. -Double exceptions are not a normal occurrence. They indicate a bug of some kind. --------------------------------------------------------------------------------- -*/ - -#ifdef XCHAL_DOUBLEEXC_VECTOR_VADDR - - .begin literal_prefix .DoubleExceptionVector - .section .DoubleExceptionVector.text, "ax" - .global _DoubleExceptionVector - .align 4 - -_DoubleExceptionVector: - - #if XCHAL_HAVE_DEBUG - break 1, 4 /* unhandled double exception */ - #endif - call0 _xt_panic /* does not return */ - rfde /* make a0 point here not later */ - - .end literal_prefix - -#endif /* XCHAL_DOUBLEEXC_VECTOR_VADDR */ - -/* --------------------------------------------------------------------------------- -Kernel Exception (including Level 1 Interrupt from kernel mode). --------------------------------------------------------------------------------- -*/ - - .begin literal_prefix .KernelExceptionVector - .section .KernelExceptionVector.text, "ax" - .global _KernelExceptionVector - .align 4 - -_KernelExceptionVector: - - wsr a0, EXCSAVE_1 /* preserve a0 */ - call0 _xt_kernel_exc /* kernel exception handler */ - /* never returns here - call0 is used as a jump (see note at top) */ - - .end literal_prefix - - .text - .align 4 - -_xt_kernel_exc: - #if XCHAL_HAVE_DEBUG - break 1, 0 /* unhandled kernel exception */ - #endif - call0 _xt_panic /* does not return */ - rfe /* make a0 point here not there */ - - -/* --------------------------------------------------------------------------------- -User Exception (including Level 1 Interrupt from user mode). --------------------------------------------------------------------------------- -*/ - - .begin literal_prefix .UserExceptionVector - .section .UserExceptionVector.text, "ax" - .global _UserExceptionVector - .type _UserExceptionVector,@function - .align 4 - -_UserExceptionVector: - - wsr a0, EXCSAVE_1 /* preserve a0 */ - call0 _xt_user_exc /* user exception handler */ - /* never returns here - call0 is used as a jump (see note at top) */ - - .end literal_prefix - -/* --------------------------------------------------------------------------------- - Insert some waypoints for jumping beyond the signed 8-bit range of - conditional branch instructions, so the conditional branchces to specific - exception handlers are not taken in the mainline. Saves some cycles in the - mainline. --------------------------------------------------------------------------------- -*/ - - .text - - #if XCHAL_HAVE_WINDOWED - .align 4 -_xt_to_alloca_exc: - call0 _xt_alloca_exc /* in window vectors section */ - /* never returns here - call0 is used as a jump (see note at top) */ - #endif - - .align 4 -_xt_to_syscall_exc: - call0 _xt_syscall_exc - /* never returns here - call0 is used as a jump (see note at top) */ - - #if XCHAL_CP_NUM > 0 - .align 4 -_xt_to_coproc_exc: - call0 _xt_coproc_exc - /* never returns here - call0 is used as a jump (see note at top) */ - #endif - - -/* --------------------------------------------------------------------------------- - User exception handler. --------------------------------------------------------------------------------- -*/ - - .type _xt_user_exc,@function - .align 4 - -_xt_user_exc: - - /* If level 1 interrupt then jump to the dispatcher */ - rsr a0, EXCCAUSE - beqi a0, EXCCAUSE_LEVEL1INTERRUPT, _xt_lowint1 - - /* Handle any coprocessor exceptions. Rely on the fact that exception - numbers above EXCCAUSE_CP0_DISABLED all relate to the coprocessors. - */ - #if XCHAL_CP_NUM > 0 - bgeui a0, EXCCAUSE_CP0_DISABLED, _xt_to_coproc_exc - #endif - - /* Handle alloca and syscall exceptions */ - #if XCHAL_HAVE_WINDOWED - beqi a0, EXCCAUSE_ALLOCA, _xt_to_alloca_exc - #endif - beqi a0, EXCCAUSE_SYSCALL, _xt_to_syscall_exc - - /* Handle all other exceptions. All can have user-defined handlers. */ - /* NOTE: we'll stay on the user stack for exception handling. */ - - /* Allocate exception frame and save minimal context. */ - mov a0, sp - addi sp, sp, -XT_STK_FRMSZ - s32i a0, sp, XT_STK_A1 - #if XCHAL_HAVE_WINDOWED - s32e a0, sp, -12 /* for debug backtrace */ - #endif - rsr a0, PS /* save interruptee's PS */ - s32i a0, sp, XT_STK_PS - rsr a0, EPC_1 /* save interruptee's PC */ - s32i a0, sp, XT_STK_PC - rsr a0, EXCSAVE_1 /* save interruptee's a0 */ - s32i a0, sp, XT_STK_A0 - #if XCHAL_HAVE_WINDOWED - s32e a0, sp, -16 /* for debug backtrace */ - #endif - s32i a12, sp, XT_STK_A12 /* _xt_context_save requires A12- */ - s32i a13, sp, XT_STK_A13 /* A13 to have already been saved */ - call0 _xt_context_save - - /* Save exc cause and vaddr into exception frame */ - rsr a0, EXCCAUSE - s32i a0, sp, XT_STK_EXCCAUSE - rsr a0, EXCVADDR - s32i a0, sp, XT_STK_EXCVADDR - - /* Set up PS for C, reenable hi-pri interrupts, and clear EXCM. */ - #ifdef __XTENSA_CALL0_ABI__ - movi a0, PS_INTLEVEL(XCHAL_EXCM_LEVEL) | PS_UM - #else - movi a0, PS_INTLEVEL(XCHAL_EXCM_LEVEL) | PS_UM | PS_WOE - #endif - wsr a0, PS - - #ifdef XT_DEBUG_BACKTRACE - #ifndef __XTENSA_CALL0_ABI__ - rsr a0, EPC_1 /* return address for debug backtrace */ - movi a5, 0xC0000000 /* constant with top 2 bits set (call size) */ - rsync /* wait for WSR.PS to complete */ - or a0, a0, a5 /* set top 2 bits */ - addx2 a0, a5, a0 /* clear top bit -- thus simulating call4 size */ - #else - rsync /* wait for WSR.PS to complete */ - #endif - #endif - - rsr a2, EXCCAUSE /* recover exc cause */ - - #ifdef XT_INTEXC_HOOKS - /* - Call exception hook to pre-handle exceptions (if installed). - Pass EXCCAUSE in a2, and check result in a2 (if -1, skip default handling). - */ - movi a4, _xt_intexc_hooks - l32i a4, a4, 0 /* user exception hook index 0 */ - beqz a4, 1f -.Ln_xt_user_exc_call_hook: - #ifdef __XTENSA_CALL0_ABI__ - callx0 a4 - beqi a2, -1, .L_xt_user_done - #else - mov a6, a2 - callx4 a4 - beqi a6, -1, .L_xt_user_done - mov a2, a6 - #endif -1: - #endif - - rsr a2, EXCCAUSE /* recover exc cause */ - movi a3, _xt_exception_table - addx4 a4, a2, a3 /* a4 = address of exception table entry */ - l32i a4, a4, 0 /* a4 = handler address */ - #ifdef __XTENSA_CALL0_ABI__ - mov a2, sp /* a2 = pointer to exc frame */ - callx0 a4 /* call handler */ - #else - mov a6, sp /* a6 = pointer to exc frame */ - callx4 a4 /* call handler */ - #endif - -.L_xt_user_done: - - /* Restore context and return */ - call0 _xt_context_restore - l32i a0, sp, XT_STK_PS /* retrieve interruptee's PS */ - wsr a0, PS - l32i a0, sp, XT_STK_PC /* retrieve interruptee's PC */ - wsr a0, EPC_1 - l32i a0, sp, XT_STK_A0 /* retrieve interruptee's A0 */ - l32i sp, sp, XT_STK_A1 /* remove exception frame */ - rsync /* ensure PS and EPC written */ - rfe /* PS.EXCM is cleared */ - - -/* --------------------------------------------------------------------------------- - Exit point for dispatch. Saved in interrupt stack frame at XT_STK_EXIT - on entry and used to return to a thread or interrupted interrupt handler. --------------------------------------------------------------------------------- -*/ - - .global _xt_user_exit - .type _xt_user_exit,@function - .align 4 -_xt_user_exit: - l32i a0, sp, XT_STK_PS /* retrieve interruptee's PS */ - wsr a0, PS - l32i a0, sp, XT_STK_PC /* retrieve interruptee's PC */ - wsr a0, EPC_1 - l32i a0, sp, XT_STK_A0 /* retrieve interruptee's A0 */ - l32i sp, sp, XT_STK_A1 /* remove interrupt stack frame */ - rsync /* ensure PS and EPC written */ - rfe /* PS.EXCM is cleared */ - - -/* --------------------------------------------------------------------------------- -Syscall Exception Handler (jumped to from User Exception Handler). -Syscall 0 is required to spill the register windows (no-op in Call 0 ABI). -Only syscall 0 is handled here. Other syscalls return -1 to caller in a2. --------------------------------------------------------------------------------- -*/ - - .text - .type _xt_syscall_exc,@function - .align 4 -_xt_syscall_exc: - - #ifdef __XTENSA_CALL0_ABI__ - /* - Save minimal regs for scratch. Syscall 0 does nothing in Call0 ABI. - Use a minimal stack frame (16B) to save A2 & A3 for scratch. - PS.EXCM could be cleared here, but unlikely to improve worst-case latency. - rsr a0, PS - addi a0, a0, -PS_EXCM_MASK - wsr a0, PS - */ - addi sp, sp, -16 - s32i a2, sp, 8 - s32i a3, sp, 12 - #else /* Windowed ABI */ - /* - Save necessary context and spill the register windows. - PS.EXCM is still set and must remain set until after the spill. - Reuse context save function though it saves more than necessary. - For this reason, a full interrupt stack frame is allocated. - */ - addi sp, sp, -XT_STK_FRMSZ /* allocate interrupt stack frame */ - s32i a12, sp, XT_STK_A12 /* _xt_context_save requires A12- */ - s32i a13, sp, XT_STK_A13 /* A13 to have already been saved */ - call0 _xt_context_save - #endif - - /* - Grab the interruptee's PC and skip over the 'syscall' instruction. - If it's at the end of a zero-overhead loop and it's not on the last - iteration, decrement loop counter and skip to beginning of loop. - */ - rsr a2, EPC_1 /* a2 = PC of 'syscall' */ - addi a3, a2, 3 /* ++PC */ - #if XCHAL_HAVE_LOOPS - rsr a0, LEND /* if (PC == LEND */ - bne a3, a0, 1f - rsr a0, LCOUNT /* && LCOUNT != 0) */ - beqz a0, 1f /* { */ - addi a0, a0, -1 /* --LCOUNT */ - rsr a3, LBEG /* PC = LBEG */ - wsr a0, LCOUNT /* } */ - #endif -1: wsr a3, EPC_1 /* update PC */ - - /* Restore interruptee's context and return from exception. */ - #ifdef __XTENSA_CALL0_ABI__ - l32i a2, sp, 8 - l32i a3, sp, 12 - addi sp, sp, 16 - #else - call0 _xt_context_restore - addi sp, sp, XT_STK_FRMSZ - #endif - movi a0, -1 - movnez a2, a0, a2 /* return -1 if not syscall 0 */ - rsr a0, EXCSAVE_1 - rfe - -/* --------------------------------------------------------------------------------- -Co-Processor Exception Handler (jumped to from User Exception Handler). -These exceptions are generated by co-processor instructions, which are only -allowed in thread code (not in interrupts or kernel code). This restriction is -deliberately imposed to reduce the burden of state-save/restore in interrupts. --------------------------------------------------------------------------------- -*/ -#if XCHAL_CP_NUM > 0 - - .section .rodata, "a" - -/* Offset to CP n save area in thread's CP save area. */ - .global _xt_coproc_sa_offset - .type _xt_coproc_sa_offset,@object - .align 16 /* minimize crossing cache boundaries */ -_xt_coproc_sa_offset: - .word XT_CP0_SA, XT_CP1_SA, XT_CP2_SA, XT_CP3_SA - .word XT_CP4_SA, XT_CP5_SA, XT_CP6_SA, XT_CP7_SA - -/* Bitmask for CP n's CPENABLE bit. */ - .type _xt_coproc_mask,@object - .align 16,,8 /* try to keep it all in one cache line */ - .set i, 0 -_xt_coproc_mask: - .rept XCHAL_CP_MAX - .long (i<<16) | (1<= 2 - - .begin literal_prefix .Level2InterruptVector - .section .Level2InterruptVector.text, "ax" - .global _Level2Vector - .type _Level2Vector,@function - .align 4 -_Level2Vector: - wsr a0, EXCSAVE_2 /* preserve a0 */ - call0 _xt_medint2 /* load interrupt handler */ - /* never returns here - call0 is used as a jump (see note at top) */ - - .end literal_prefix - - .text - .type _xt_medint2,@function - .align 4 -_xt_medint2: - mov a0, sp /* sp == a1 */ - addi sp, sp, -XT_STK_FRMSZ /* allocate interrupt stack frame */ - s32i a0, sp, XT_STK_A1 /* save pre-interrupt SP */ - rsr a0, EPS_2 /* save interruptee's PS */ - s32i a0, sp, XT_STK_PS - rsr a0, EPC_2 /* save interruptee's PC */ - s32i a0, sp, XT_STK_PC - rsr a0, EXCSAVE_2 /* save interruptee's a0 */ - s32i a0, sp, XT_STK_A0 - movi a0, _xt_medint2_exit /* save exit point for dispatch */ - s32i a0, sp, XT_STK_EXIT - - /* Save rest of interrupt context and enter RTOS. */ - call0 XT_RTOS_INT_ENTER /* common RTOS interrupt entry */ - - /* !! We are now on the RTOS system stack !! */ - - /* Set up PS for C, enable interrupts above this level and clear EXCM. */ - #ifdef __XTENSA_CALL0_ABI__ - movi a0, PS_INTLEVEL(2) | PS_UM - #else - movi a0, PS_INTLEVEL(2) | PS_UM | PS_WOE - #endif - wsr a0, PS - rsync - - /* OK to call C code at this point, dispatch user ISRs */ - - dispatch_c_isr 2 XCHAL_INTLEVEL2_MASK - - /* Done handling interrupts, transfer control to OS */ - call0 XT_RTOS_INT_EXIT /* does not return directly here */ - - /* - Exit point for dispatch. Saved in interrupt stack frame at XT_STK_EXIT - on entry and used to return to a thread or interrupted interrupt handler. - */ - .global _xt_medint2_exit - .type _xt_medint2_exit,@function - .align 4 -_xt_medint2_exit: - /* Restore only level-specific regs (the rest were already restored) */ - l32i a0, sp, XT_STK_PS /* retrieve interruptee's PS */ - wsr a0, EPS_2 - l32i a0, sp, XT_STK_PC /* retrieve interruptee's PC */ - wsr a0, EPC_2 - l32i a0, sp, XT_STK_A0 /* retrieve interruptee's A0 */ - l32i sp, sp, XT_STK_A1 /* remove interrupt stack frame */ - rsync /* ensure EPS and EPC written */ - rfi 2 - -#endif /* Level 2 */ - -#if XCHAL_EXCM_LEVEL >= 3 - - .begin literal_prefix .Level3InterruptVector - .section .Level3InterruptVector.text, "ax" - .global _Level3Vector - .type _Level3Vector,@function - .align 4 -_Level3Vector: - wsr a0, EXCSAVE_3 /* preserve a0 */ - call0 _xt_medint3 /* load interrupt handler */ - /* never returns here - call0 is used as a jump (see note at top) */ - - .end literal_prefix - - .text - .type _xt_medint3,@function - .align 4 -_xt_medint3: - mov a0, sp /* sp == a1 */ - addi sp, sp, -XT_STK_FRMSZ /* allocate interrupt stack frame */ - s32i a0, sp, XT_STK_A1 /* save pre-interrupt SP */ - rsr a0, EPS_3 /* save interruptee's PS */ - s32i a0, sp, XT_STK_PS - rsr a0, EPC_3 /* save interruptee's PC */ - s32i a0, sp, XT_STK_PC - rsr a0, EXCSAVE_3 /* save interruptee's a0 */ - s32i a0, sp, XT_STK_A0 - movi a0, _xt_medint3_exit /* save exit point for dispatch */ - s32i a0, sp, XT_STK_EXIT - - /* Save rest of interrupt context and enter RTOS. */ - call0 XT_RTOS_INT_ENTER /* common RTOS interrupt entry */ - - /* !! We are now on the RTOS system stack !! */ - - /* Set up PS for C, enable interrupts above this level and clear EXCM. */ - #ifdef __XTENSA_CALL0_ABI__ - movi a0, PS_INTLEVEL(3) | PS_UM - #else - movi a0, PS_INTLEVEL(3) | PS_UM | PS_WOE - #endif - wsr a0, PS - rsync - - /* OK to call C code at this point, dispatch user ISRs */ - - dispatch_c_isr 3 XCHAL_INTLEVEL3_MASK - - /* Done handling interrupts, transfer control to OS */ - call0 XT_RTOS_INT_EXIT /* does not return directly here */ - - /* - Exit point for dispatch. Saved in interrupt stack frame at XT_STK_EXIT - on entry and used to return to a thread or interrupted interrupt handler. - */ - .global _xt_medint3_exit - .type _xt_medint3_exit,@function - .align 4 -_xt_medint3_exit: - /* Restore only level-specific regs (the rest were already restored) */ - l32i a0, sp, XT_STK_PS /* retrieve interruptee's PS */ - wsr a0, EPS_3 - l32i a0, sp, XT_STK_PC /* retrieve interruptee's PC */ - wsr a0, EPC_3 - l32i a0, sp, XT_STK_A0 /* retrieve interruptee's A0 */ - l32i sp, sp, XT_STK_A1 /* remove interrupt stack frame */ - rsync /* ensure EPS and EPC written */ - rfi 3 - -#endif /* Level 3 */ - -#if XCHAL_EXCM_LEVEL >= 4 - - .begin literal_prefix .Level4InterruptVector - .section .Level4InterruptVector.text, "ax" - .global _Level4Vector - .type _Level4Vector,@function - .align 4 -_Level4Vector: - wsr a0, EXCSAVE_4 /* preserve a0 */ - call0 _xt_medint4 /* load interrupt handler */ - - .end literal_prefix - - .text - .type _xt_medint4,@function - .align 4 -_xt_medint4: - mov a0, sp /* sp == a1 */ - addi sp, sp, -XT_STK_FRMSZ /* allocate interrupt stack frame */ - s32i a0, sp, XT_STK_A1 /* save pre-interrupt SP */ - rsr a0, EPS_4 /* save interruptee's PS */ - s32i a0, sp, XT_STK_PS - rsr a0, EPC_4 /* save interruptee's PC */ - s32i a0, sp, XT_STK_PC - rsr a0, EXCSAVE_4 /* save interruptee's a0 */ - s32i a0, sp, XT_STK_A0 - movi a0, _xt_medint4_exit /* save exit point for dispatch */ - s32i a0, sp, XT_STK_EXIT - - /* Save rest of interrupt context and enter RTOS. */ - call0 XT_RTOS_INT_ENTER /* common RTOS interrupt entry */ - - /* !! We are now on the RTOS system stack !! */ - - /* Set up PS for C, enable interrupts above this level and clear EXCM. */ - #ifdef __XTENSA_CALL0_ABI__ - movi a0, PS_INTLEVEL(4) | PS_UM - #else - movi a0, PS_INTLEVEL(4) | PS_UM | PS_WOE - #endif - wsr a0, PS - rsync - - /* OK to call C code at this point, dispatch user ISRs */ - - dispatch_c_isr 4 XCHAL_INTLEVEL4_MASK - - /* Done handling interrupts, transfer control to OS */ - call0 XT_RTOS_INT_EXIT /* does not return directly here */ - - /* - Exit point for dispatch. Saved in interrupt stack frame at XT_STK_EXIT - on entry and used to return to a thread or interrupted interrupt handler. - */ - .global _xt_medint4_exit - .type _xt_medint4_exit,@function - .align 4 -_xt_medint4_exit: - /* Restore only level-specific regs (the rest were already restored) */ - l32i a0, sp, XT_STK_PS /* retrieve interruptee's PS */ - wsr a0, EPS_4 - l32i a0, sp, XT_STK_PC /* retrieve interruptee's PC */ - wsr a0, EPC_4 - l32i a0, sp, XT_STK_A0 /* retrieve interruptee's A0 */ - l32i sp, sp, XT_STK_A1 /* remove interrupt stack frame */ - rsync /* ensure EPS and EPC written */ - rfi 4 - -#endif /* Level 4 */ - -#if XCHAL_EXCM_LEVEL >= 5 - - .begin literal_prefix .Level5InterruptVector - .section .Level5InterruptVector.text, "ax" - .global _Level5Vector - .type _Level5Vector,@function - .align 4 -_Level5Vector: - wsr a0, EXCSAVE_5 /* preserve a0 */ - call0 _xt_medint5 /* load interrupt handler */ - - .end literal_prefix - - .text - .type _xt_medint5,@function - .align 4 -_xt_medint5: - mov a0, sp /* sp == a1 */ - addi sp, sp, -XT_STK_FRMSZ /* allocate interrupt stack frame */ - s32i a0, sp, XT_STK_A1 /* save pre-interrupt SP */ - rsr a0, EPS_5 /* save interruptee's PS */ - s32i a0, sp, XT_STK_PS - rsr a0, EPC_5 /* save interruptee's PC */ - s32i a0, sp, XT_STK_PC - rsr a0, EXCSAVE_5 /* save interruptee's a0 */ - s32i a0, sp, XT_STK_A0 - movi a0, _xt_medint5_exit /* save exit point for dispatch */ - s32i a0, sp, XT_STK_EXIT - - /* Save rest of interrupt context and enter RTOS. */ - call0 XT_RTOS_INT_ENTER /* common RTOS interrupt entry */ - - /* !! We are now on the RTOS system stack !! */ - - /* Set up PS for C, enable interrupts above this level and clear EXCM. */ - #ifdef __XTENSA_CALL0_ABI__ - movi a0, PS_INTLEVEL(5) | PS_UM - #else - movi a0, PS_INTLEVEL(5) | PS_UM | PS_WOE - #endif - wsr a0, PS - rsync - - /* OK to call C code at this point, dispatch user ISRs */ - - dispatch_c_isr 5 XCHAL_INTLEVEL5_MASK - - /* Done handling interrupts, transfer control to OS */ - call0 XT_RTOS_INT_EXIT /* does not return directly here */ - - /* - Exit point for dispatch. Saved in interrupt stack frame at XT_STK_EXIT - on entry and used to return to a thread or interrupted interrupt handler. - */ - .global _xt_medint5_exit - .type _xt_medint5_exit,@function - .align 4 -_xt_medint5_exit: - /* Restore only level-specific regs (the rest were already restored) */ - l32i a0, sp, XT_STK_PS /* retrieve interruptee's PS */ - wsr a0, EPS_5 - l32i a0, sp, XT_STK_PC /* retrieve interruptee's PC */ - wsr a0, EPC_5 - l32i a0, sp, XT_STK_A0 /* retrieve interruptee's A0 */ - l32i sp, sp, XT_STK_A1 /* remove interrupt stack frame */ - rsync /* ensure EPS and EPC written */ - rfi 5 - -#endif /* Level 5 */ - -#if XCHAL_EXCM_LEVEL >= 6 - - .begin literal_prefix .Level6InterruptVector - .section .Level6InterruptVector.text, "ax" - .global _Level6Vector - .type _Level6Vector,@function - .align 4 -_Level6Vector: - wsr a0, EXCSAVE_6 /* preserve a0 */ - call0 _xt_medint6 /* load interrupt handler */ - - .end literal_prefix - - .text - .type _xt_medint6,@function - .align 4 -_xt_medint6: - mov a0, sp /* sp == a1 */ - addi sp, sp, -XT_STK_FRMSZ /* allocate interrupt stack frame */ - s32i a0, sp, XT_STK_A1 /* save pre-interrupt SP */ - rsr a0, EPS_6 /* save interruptee's PS */ - s32i a0, sp, XT_STK_PS - rsr a0, EPC_6 /* save interruptee's PC */ - s32i a0, sp, XT_STK_PC - rsr a0, EXCSAVE_6 /* save interruptee's a0 */ - s32i a0, sp, XT_STK_A0 - movi a0, _xt_medint6_exit /* save exit point for dispatch */ - s32i a0, sp, XT_STK_EXIT - - /* Save rest of interrupt context and enter RTOS. */ - call0 XT_RTOS_INT_ENTER /* common RTOS interrupt entry */ - - /* !! We are now on the RTOS system stack !! */ - - /* Set up PS for C, enable interrupts above this level and clear EXCM. */ - #ifdef __XTENSA_CALL0_ABI__ - movi a0, PS_INTLEVEL(6) | PS_UM - #else - movi a0, PS_INTLEVEL(6) | PS_UM | PS_WOE - #endif - wsr a0, PS - rsync - - /* OK to call C code at this point, dispatch user ISRs */ - - dispatch_c_isr 6 XCHAL_INTLEVEL6_MASK - - /* Done handling interrupts, transfer control to OS */ - call0 XT_RTOS_INT_EXIT /* does not return directly here */ - - /* - Exit point for dispatch. Saved in interrupt stack frame at XT_STK_EXIT - on entry and used to return to a thread or interrupted interrupt handler. - */ - .global _xt_medint6_exit - .type _xt_medint6_exit,@function - .align 4 -_xt_medint6_exit: - /* Restore only level-specific regs (the rest were already restored) */ - l32i a0, sp, XT_STK_PS /* retrieve interruptee's PS */ - wsr a0, EPS_6 - l32i a0, sp, XT_STK_PC /* retrieve interruptee's PC */ - wsr a0, EPC_6 - l32i a0, sp, XT_STK_A0 /* retrieve interruptee's A0 */ - l32i sp, sp, XT_STK_A1 /* remove interrupt stack frame */ - rsync /* ensure EPS and EPC written */ - rfi 6 - -#endif /* Level 6 */ - - -/******************************************************************************* - -HIGH PRIORITY (LEVEL > XCHAL_EXCM_LEVEL) INTERRUPT VECTORS AND HANDLERS - -High priority interrupts are by definition those with priorities greater -than XCHAL_EXCM_LEVEL. This includes non-maskable (NMI). High priority -interrupts cannot interact with the RTOS, that is they must save all regs -they use and not call any RTOS function. - -A further restriction imposed by the Xtensa windowed architecture is that -high priority interrupts must not modify the stack area even logically -"above" the top of the interrupted stack (they need to provide their -own stack or static save area). - -Cadence Design Systems recommends high priority interrupt handlers be coded in assembly -and used for purposes requiring very short service times. - -Here are templates for high priority (level 2+) interrupt vectors. -They assume only one interrupt per level to avoid the burden of identifying -which interrupts at this level are pending and enabled. This allows for -minimum latency and avoids having to save/restore a2 in addition to a0. -If more than one interrupt per high priority level is configured, this burden -is on the handler which in any case must provide a way to save and restore -registers it uses without touching the interrupted stack. - -Each vector goes at a predetermined location according to the Xtensa -hardware configuration, which is ensured by its placement in a special -section known to the Xtensa linker support package (LSP). It performs -the minimum necessary before jumping to the handler in the .text section. - -*******************************************************************************/ - -/* -Currently only shells for high priority interrupt handlers are provided -here. However a template and example can be found in the Cadence Design Systems tools -documentation: "Microprocessor Programmer's Guide". -*/ - -#if XCHAL_NUM_INTLEVELS >=2 && XCHAL_EXCM_LEVEL <2 && XCHAL_DEBUGLEVEL !=2 - - .begin literal_prefix .Level2InterruptVector - .section .Level2InterruptVector.text, "ax" - .global _Level2Vector - .type _Level2Vector,@function - .align 4 -_Level2Vector: - wsr a0, EXCSAVE_2 /* preserve a0 */ - call0 _xt_highint2 /* load interrupt handler */ - - .end literal_prefix - - .text - .type _xt_highint2,@function - .align 4 -_xt_highint2: - - #ifdef XT_INTEXC_HOOKS - /* Call interrupt hook if present to (pre)handle interrupts. */ - movi a0, _xt_intexc_hooks - l32i a0, a0, 2<<2 - beqz a0, 1f -.Ln_xt_highint2_call_hook: - callx0 a0 /* must NOT disturb stack! */ -1: - #endif - - /* USER_EDIT: - ADD HIGH PRIORITY LEVEL 2 INTERRUPT HANDLER CODE HERE. - */ - - .align 4 -.L_xt_highint2_exit: - rsr a0, EXCSAVE_2 /* restore a0 */ - rfi 2 - -#endif /* Level 2 */ - -#if XCHAL_NUM_INTLEVELS >=3 && XCHAL_EXCM_LEVEL <3 && XCHAL_DEBUGLEVEL !=3 - - .begin literal_prefix .Level3InterruptVector - .section .Level3InterruptVector.text, "ax" - .global _Level3Vector - .type _Level3Vector,@function - .align 4 -_Level3Vector: - wsr a0, EXCSAVE_3 /* preserve a0 */ - call0 _xt_highint3 /* load interrupt handler */ - /* never returns here - call0 is used as a jump (see note at top) */ - - .end literal_prefix - - .text - .type _xt_highint3,@function - .align 4 -_xt_highint3: - - #ifdef XT_INTEXC_HOOKS - /* Call interrupt hook if present to (pre)handle interrupts. */ - movi a0, _xt_intexc_hooks - l32i a0, a0, 3<<2 - beqz a0, 1f -.Ln_xt_highint3_call_hook: - callx0 a0 /* must NOT disturb stack! */ -1: - #endif - - /* USER_EDIT: - ADD HIGH PRIORITY LEVEL 3 INTERRUPT HANDLER CODE HERE. - */ - - .align 4 -.L_xt_highint3_exit: - rsr a0, EXCSAVE_3 /* restore a0 */ - rfi 3 - -#endif /* Level 3 */ - -#if XCHAL_NUM_INTLEVELS >=4 && XCHAL_EXCM_LEVEL <4 && XCHAL_DEBUGLEVEL !=4 - - .begin literal_prefix .Level4InterruptVector - .section .Level4InterruptVector.text, "ax" - .global _Level4Vector - .type _Level4Vector,@function - .align 4 -_Level4Vector: - wsr a0, EXCSAVE_4 /* preserve a0 */ - call0 _xt_highint4 /* load interrupt handler */ - /* never returns here - call0 is used as a jump (see note at top) */ - - .end literal_prefix - - .text - .type _xt_highint4,@function - .align 4 -_xt_highint4: - - #ifdef XT_INTEXC_HOOKS - /* Call interrupt hook if present to (pre)handle interrupts. */ - movi a0, _xt_intexc_hooks - l32i a0, a0, 4<<2 - beqz a0, 1f -.Ln_xt_highint4_call_hook: - callx0 a0 /* must NOT disturb stack! */ -1: - #endif - - /* USER_EDIT: - ADD HIGH PRIORITY LEVEL 4 INTERRUPT HANDLER CODE HERE. - */ - - .align 4 -.L_xt_highint4_exit: - rsr a0, EXCSAVE_4 /* restore a0 */ - rfi 4 - -#endif /* Level 4 */ - -#if XCHAL_NUM_INTLEVELS >=5 && XCHAL_EXCM_LEVEL <5 && XCHAL_DEBUGLEVEL !=5 - - .begin literal_prefix .Level5InterruptVector - .section .Level5InterruptVector.text, "ax" - .global _Level5Vector - .type _Level5Vector,@function - .align 4 -_Level5Vector: - wsr a0, EXCSAVE_5 /* preserve a0 */ - call0 _xt_highint5 /* load interrupt handler */ - /* never returns here - call0 is used as a jump (see note at top) */ - - .end literal_prefix - - .text - .type _xt_highint5,@function - .align 4 -_xt_highint5: - - #ifdef XT_INTEXC_HOOKS - /* Call interrupt hook if present to (pre)handle interrupts. */ - movi a0, _xt_intexc_hooks - l32i a0, a0, 5<<2 - beqz a0, 1f -.Ln_xt_highint5_call_hook: - callx0 a0 /* must NOT disturb stack! */ -1: - #endif - - /* USER_EDIT: - ADD HIGH PRIORITY LEVEL 5 INTERRUPT HANDLER CODE HERE. - */ - - .align 4 -.L_xt_highint5_exit: - rsr a0, EXCSAVE_5 /* restore a0 */ - rfi 5 - -#endif /* Level 5 */ - -#if XCHAL_NUM_INTLEVELS >=6 && XCHAL_EXCM_LEVEL <6 && XCHAL_DEBUGLEVEL !=6 - - .begin literal_prefix .Level6InterruptVector - .section .Level6InterruptVector.text, "ax" - .global _Level6Vector - .type _Level6Vector,@function - .align 4 -_Level6Vector: - wsr a0, EXCSAVE_6 /* preserve a0 */ - call0 _xt_highint6 /* load interrupt handler */ - /* never returns here - call0 is used as a jump (see note at top) */ - - .end literal_prefix - - .text - .type _xt_highint6,@function - .align 4 -_xt_highint6: - - #ifdef XT_INTEXC_HOOKS - /* Call interrupt hook if present to (pre)handle interrupts. */ - movi a0, _xt_intexc_hooks - l32i a0, a0, 6<<2 - beqz a0, 1f -.Ln_xt_highint6_call_hook: - callx0 a0 /* must NOT disturb stack! */ -1: - #endif - - /* USER_EDIT: - ADD HIGH PRIORITY LEVEL 6 INTERRUPT HANDLER CODE HERE. - */ - - .align 4 -.L_xt_highint6_exit: - rsr a0, EXCSAVE_6 /* restore a0 */ - rfi 6 - -#endif /* Level 6 */ - -#if XCHAL_HAVE_NMI - - .begin literal_prefix .NMIExceptionVector - .section .NMIExceptionVector.text, "ax" - .global _NMIExceptionVector - .type _NMIExceptionVector,@function - .align 4 -_NMIExceptionVector: - wsr a0, EXCSAVE + XCHAL_NMILEVEL _ /* preserve a0 */ - call0 _xt_nmi /* load interrupt handler */ - /* never returns here - call0 is used as a jump (see note at top) */ - - .end literal_prefix - - .text - .type _xt_nmi,@function - .align 4 -_xt_nmi: - - #ifdef XT_INTEXC_HOOKS - /* Call interrupt hook if present to (pre)handle interrupts. */ - movi a0, _xt_intexc_hooks - l32i a0, a0, XCHAL_NMILEVEL<<2 - beqz a0, 1f -.Ln_xt_nmi_call_hook: - callx0 a0 /* must NOT disturb stack! */ -1: - #endif - - /* USER_EDIT: - ADD HIGH PRIORITY NON-MASKABLE INTERRUPT (NMI) HANDLER CODE HERE. - */ - - .align 4 -.L_xt_nmi_exit: - rsr a0, EXCSAVE + XCHAL_NMILEVEL /* restore a0 */ - rfi XCHAL_NMILEVEL - -#endif /* NMI */ - - -/******************************************************************************* - -WINDOW OVERFLOW AND UNDERFLOW EXCEPTION VECTORS AND ALLOCA EXCEPTION HANDLER - -Here is the code for each window overflow/underflow exception vector and -(interspersed) efficient code for handling the alloca exception cause. -Window exceptions are handled entirely in the vector area and are very -tight for performance. The alloca exception is also handled entirely in -the window vector area so comes at essentially no cost in code size. -Users should never need to modify them and Cadence Design Systems recommends -they do not. - -Window handlers go at predetermined vector locations according to the -Xtensa hardware configuration, which is ensured by their placement in a -special section known to the Xtensa linker support package (LSP). Since -their offsets in that section are always the same, the LSPs do not define -a section per vector. - -These things are coded for XEA2 only (XEA1 is not supported). - -Note on Underflow Handlers: -The underflow handler for returning from call[i+1] to call[i] -must preserve all the registers from call[i+1]'s window. -In particular, a0 and a1 must be preserved because the RETW instruction -will be reexecuted (and may even underflow if an intervening exception -has flushed call[i]'s registers). -Registers a2 and up may contain return values. - -*******************************************************************************/ - -#if XCHAL_HAVE_WINDOWED - - .section .WindowVectors.text, "ax" - -/* --------------------------------------------------------------------------------- -Window Overflow Exception for Call4. - -Invoked if a call[i] referenced a register (a4-a15) -that contains data from ancestor call[j]; -call[j] had done a call4 to call[j+1]. -On entry here: - window rotated to call[j] start point; - a0-a3 are registers to be saved; - a4-a15 must be preserved; - a5 is call[j+1]'s stack pointer. --------------------------------------------------------------------------------- -*/ - - .org 0x0 - .global _WindowOverflow4 -_WindowOverflow4: - - s32e a0, a5, -16 /* save a0 to call[j+1]'s stack frame */ - s32e a1, a5, -12 /* save a1 to call[j+1]'s stack frame */ - s32e a2, a5, -8 /* save a2 to call[j+1]'s stack frame */ - s32e a3, a5, -4 /* save a3 to call[j+1]'s stack frame */ - rfwo /* rotates back to call[i] position */ - -/* --------------------------------------------------------------------------------- -Window Underflow Exception for Call4 - -Invoked by RETW returning from call[i+1] to call[i] -where call[i]'s registers must be reloaded (not live in ARs); -where call[i] had done a call4 to call[i+1]. -On entry here: - window rotated to call[i] start point; - a0-a3 are undefined, must be reloaded with call[i].reg[0..3]; - a4-a15 must be preserved (they are call[i+1].reg[0..11]); - a5 is call[i+1]'s stack pointer. --------------------------------------------------------------------------------- -*/ - - .org 0x40 - .global _WindowUnderflow4 -_WindowUnderflow4: - - l32e a0, a5, -16 /* restore a0 from call[i+1]'s stack frame */ - l32e a1, a5, -12 /* restore a1 from call[i+1]'s stack frame */ - l32e a2, a5, -8 /* restore a2 from call[i+1]'s stack frame */ - l32e a3, a5, -4 /* restore a3 from call[i+1]'s stack frame */ - rfwu - -/* --------------------------------------------------------------------------------- -Handle alloca exception generated by interruptee executing 'movsp'. -This uses space between the window vectors, so is essentially "free". -All interruptee's regs are intact except a0 which is saved in EXCSAVE_1, -and PS.EXCM has been set by the exception hardware (can't be interrupted). -The fact the alloca exception was taken means the registers associated with -the base-save area have been spilled and will be restored by the underflow -handler, so those 4 registers are available for scratch. -The code is optimized to avoid unaligned branches and minimize cache misses. --------------------------------------------------------------------------------- -*/ - - .align 4 - .global _xt_alloca_exc -_xt_alloca_exc: - - rsr a0, WINDOWBASE /* grab WINDOWBASE before rotw changes it */ - rotw -1 /* WINDOWBASE goes to a4, new a0-a3 are scratch */ - rsr a2, PS - extui a3, a2, XCHAL_PS_OWB_SHIFT, XCHAL_PS_OWB_BITS - xor a3, a3, a4 /* bits changed from old to current windowbase */ - rsr a4, EXCSAVE_1 /* restore original a0 (now in a4) */ - slli a3, a3, XCHAL_PS_OWB_SHIFT - xor a2, a2, a3 /* flip changed bits in old window base */ - wsr a2, PS /* update PS.OWB to new window base */ - rsync - - _bbci.l a4, 31, _WindowUnderflow4 - rotw -1 /* original a0 goes to a8 */ - _bbci.l a8, 30, _WindowUnderflow8 - rotw -1 - j _WindowUnderflow12 - -/* --------------------------------------------------------------------------------- -Window Overflow Exception for Call8 - -Invoked if a call[i] referenced a register (a4-a15) -that contains data from ancestor call[j]; -call[j] had done a call8 to call[j+1]. -On entry here: - window rotated to call[j] start point; - a0-a7 are registers to be saved; - a8-a15 must be preserved; - a9 is call[j+1]'s stack pointer. --------------------------------------------------------------------------------- -*/ - - .org 0x80 - .global _WindowOverflow8 -_WindowOverflow8: - - s32e a0, a9, -16 /* save a0 to call[j+1]'s stack frame */ - l32e a0, a1, -12 /* a0 <- call[j-1]'s sp - (used to find end of call[j]'s frame) */ - s32e a1, a9, -12 /* save a1 to call[j+1]'s stack frame */ - s32e a2, a9, -8 /* save a2 to call[j+1]'s stack frame */ - s32e a3, a9, -4 /* save a3 to call[j+1]'s stack frame */ - s32e a4, a0, -32 /* save a4 to call[j]'s stack frame */ - s32e a5, a0, -28 /* save a5 to call[j]'s stack frame */ - s32e a6, a0, -24 /* save a6 to call[j]'s stack frame */ - s32e a7, a0, -20 /* save a7 to call[j]'s stack frame */ - rfwo /* rotates back to call[i] position */ - -/* --------------------------------------------------------------------------------- -Window Underflow Exception for Call8 - -Invoked by RETW returning from call[i+1] to call[i] -where call[i]'s registers must be reloaded (not live in ARs); -where call[i] had done a call8 to call[i+1]. -On entry here: - window rotated to call[i] start point; - a0-a7 are undefined, must be reloaded with call[i].reg[0..7]; - a8-a15 must be preserved (they are call[i+1].reg[0..7]); - a9 is call[i+1]'s stack pointer. --------------------------------------------------------------------------------- -*/ - - .org 0xC0 - .global _WindowUnderflow8 -_WindowUnderflow8: - - l32e a0, a9, -16 /* restore a0 from call[i+1]'s stack frame */ - l32e a1, a9, -12 /* restore a1 from call[i+1]'s stack frame */ - l32e a2, a9, -8 /* restore a2 from call[i+1]'s stack frame */ - l32e a7, a1, -12 /* a7 <- call[i-1]'s sp - (used to find end of call[i]'s frame) */ - l32e a3, a9, -4 /* restore a3 from call[i+1]'s stack frame */ - l32e a4, a7, -32 /* restore a4 from call[i]'s stack frame */ - l32e a5, a7, -28 /* restore a5 from call[i]'s stack frame */ - l32e a6, a7, -24 /* restore a6 from call[i]'s stack frame */ - l32e a7, a7, -20 /* restore a7 from call[i]'s stack frame */ - rfwu - -/* --------------------------------------------------------------------------------- -Window Overflow Exception for Call12 - -Invoked if a call[i] referenced a register (a4-a15) -that contains data from ancestor call[j]; -call[j] had done a call12 to call[j+1]. -On entry here: - window rotated to call[j] start point; - a0-a11 are registers to be saved; - a12-a15 must be preserved; - a13 is call[j+1]'s stack pointer. --------------------------------------------------------------------------------- -*/ - - .org 0x100 - .global _WindowOverflow12 -_WindowOverflow12: - - s32e a0, a13, -16 /* save a0 to call[j+1]'s stack frame */ - l32e a0, a1, -12 /* a0 <- call[j-1]'s sp - (used to find end of call[j]'s frame) */ - s32e a1, a13, -12 /* save a1 to call[j+1]'s stack frame */ - s32e a2, a13, -8 /* save a2 to call[j+1]'s stack frame */ - s32e a3, a13, -4 /* save a3 to call[j+1]'s stack frame */ - s32e a4, a0, -48 /* save a4 to end of call[j]'s stack frame */ - s32e a5, a0, -44 /* save a5 to end of call[j]'s stack frame */ - s32e a6, a0, -40 /* save a6 to end of call[j]'s stack frame */ - s32e a7, a0, -36 /* save a7 to end of call[j]'s stack frame */ - s32e a8, a0, -32 /* save a8 to end of call[j]'s stack frame */ - s32e a9, a0, -28 /* save a9 to end of call[j]'s stack frame */ - s32e a10, a0, -24 /* save a10 to end of call[j]'s stack frame */ - s32e a11, a0, -20 /* save a11 to end of call[j]'s stack frame */ - rfwo /* rotates back to call[i] position */ - -/* --------------------------------------------------------------------------------- -Window Underflow Exception for Call12 - -Invoked by RETW returning from call[i+1] to call[i] -where call[i]'s registers must be reloaded (not live in ARs); -where call[i] had done a call12 to call[i+1]. -On entry here: - window rotated to call[i] start point; - a0-a11 are undefined, must be reloaded with call[i].reg[0..11]; - a12-a15 must be preserved (they are call[i+1].reg[0..3]); - a13 is call[i+1]'s stack pointer. --------------------------------------------------------------------------------- -*/ - - .org 0x140 - .global _WindowUnderflow12 -_WindowUnderflow12: - - l32e a0, a13, -16 /* restore a0 from call[i+1]'s stack frame */ - l32e a1, a13, -12 /* restore a1 from call[i+1]'s stack frame */ - l32e a2, a13, -8 /* restore a2 from call[i+1]'s stack frame */ - l32e a11, a1, -12 /* a11 <- call[i-1]'s sp - (used to find end of call[i]'s frame) */ - l32e a3, a13, -4 /* restore a3 from call[i+1]'s stack frame */ - l32e a4, a11, -48 /* restore a4 from end of call[i]'s stack frame */ - l32e a5, a11, -44 /* restore a5 from end of call[i]'s stack frame */ - l32e a6, a11, -40 /* restore a6 from end of call[i]'s stack frame */ - l32e a7, a11, -36 /* restore a7 from end of call[i]'s stack frame */ - l32e a8, a11, -32 /* restore a8 from end of call[i]'s stack frame */ - l32e a9, a11, -28 /* restore a9 from end of call[i]'s stack frame */ - l32e a10, a11, -24 /* restore a10 from end of call[i]'s stack frame */ - l32e a11, a11, -20 /* restore a11 from end of call[i]'s stack frame */ - rfwu - -#endif /* XCHAL_HAVE_WINDOWED */ - - .section .UserEnter.text, "ax" - .global call_user_start - .type call_user_start,@function - .align 4 - .literal_position - - -call_user_start: - - movi a2, 0x40040000 /* note: absolute symbol, not a ptr */ - wsr a2, vecbase - call0 user_start /* user exception handler */ diff --git a/components/freertos/xtensa_vectors.S-old b/components/freertos/xtensa_vectors.S-old deleted file mode 100644 index 2d0f7a99d..000000000 --- a/components/freertos/xtensa_vectors.S-old +++ /dev/null @@ -1,2064 +0,0 @@ -/******************************************************************************* -Copyright (c) 2006-2015 Cadence Design Systems Inc. - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------------------------------- - - XTENSA VECTORS AND LOW LEVEL HANDLERS FOR AN RTOS - - Xtensa low level exception and interrupt vectors and handlers for an RTOS. - - Interrupt handlers and user exception handlers support interaction with - the RTOS by calling XT_RTOS_INT_ENTER and XT_RTOS_INT_EXIT before and - after user's specific interrupt handlers. These macros are defined in - xtensa_.h to call suitable functions in a specific RTOS. - - Users can install application-specific interrupt handlers for low and - medium level interrupts, by calling xt_set_interrupt_handler(). These - handlers can be written in C, and must obey C calling convention. The - handler table is indexed by the interrupt number. Each handler may be - provided with an argument. - - Note that the system timer interrupt is handled specially, and is - dispatched to the RTOS-specific handler. This timer cannot be hooked - by application code. - - Optional hooks are also provided to install a handler per level at - run-time, made available by compiling this source file with - '-DXT_INTEXC_HOOKS' (useful for automated testing). - -!! This file is a template that usually needs to be modified to handle !! -!! application specific interrupts. Search USER_EDIT for helpful comments !! -!! on where to insert handlers and how to write them. !! - - Users can also install application-specific exception handlers in the - same way, by calling xt_set_exception_handler(). One handler slot is - provided for each exception type. Note that some exceptions are handled - by the porting layer itself, and cannot be taken over by application - code in this manner. These are the alloca, syscall, and coprocessor - exceptions. - - The exception handlers can be written in C, and must follow C calling - convention. Each handler is passed a pointer to an exception frame as - its single argument. The exception frame is created on the stack, and - holds the saved context of the thread that took the exception. If the - handler returns, the context will be restored and the instruction that - caused the exception will be retried. If the handler makes any changes - to the saved state in the exception frame, the changes will be applied - when restoring the context. - - Because Xtensa is a configurable architecture, this port supports all user - generated configurations (except restrictions stated in the release notes). - This is accomplished by conditional compilation using macros and functions - defined in the Xtensa HAL (hardware adaptation layer) for your configuration. - Only the relevant parts of this file will be included in your RTOS build. - For example, this file provides interrupt vector templates for all types and - all priority levels, but only the ones in your configuration are built. - - NOTES on the use of 'call0' for long jumps instead of 'j': - 1. This file should be assembled with the -mlongcalls option to xt-xcc. - 2. The -mlongcalls compiler option causes 'call0 dest' to be expanded to - a sequence 'l32r a0, dest' 'callx0 a0' which works regardless of the - distance from the call to the destination. The linker then relaxes - it back to 'call0 dest' if it determines that dest is within range. - This allows more flexibility in locating code without the performance - overhead of the 'l32r' literal data load in cases where the destination - is in range of 'call0'. There is an additional benefit in that 'call0' - has a longer range than 'j' due to the target being word-aligned, so - the 'l32r' sequence is less likely needed. - 3. The use of 'call0' with -mlongcalls requires that register a0 not be - live at the time of the call, which is always the case for a function - call but needs to be ensured if 'call0' is used as a jump in lieu of 'j'. - 4. This use of 'call0' is independent of the C function call ABI. - -*******************************************************************************/ - -#include "xtensa_rtos.h" - - -/* Enable stack backtrace across exception/interrupt - see below */ -#define XT_DEBUG_BACKTRACE 0 - - -/* --------------------------------------------------------------------------------- - Defines used to access _xtos_interrupt_table. --------------------------------------------------------------------------------- -*/ -#define XIE_HANDLER 0 -#define XIE_ARG 4 -#define XIE_SIZE 8 - -/* --------------------------------------------------------------------------------- - Macro extract_msb - return the input with only the highest bit set. - - Input : "ain" - Input value, clobbered. - Output : "aout" - Output value, has only one bit set, MSB of "ain". - The two arguments must be different AR registers. --------------------------------------------------------------------------------- -*/ - - .macro extract_msb aout ain -1: - addi \aout, \ain, -1 /* aout = ain - 1 */ - and \ain, \ain, \aout /* ain = ain & aout */ - bnez \ain, 1b /* repeat until ain == 0 */ - addi \aout, \aout, 1 /* return aout + 1 */ - .endm - -/* --------------------------------------------------------------------------------- - Macro dispatch_c_isr - dispatch interrupts to user ISRs. - This will dispatch to user handlers (if any) that are registered in the - XTOS dispatch table (_xtos_interrupt_table). These handlers would have - been registered by calling _xtos_set_interrupt_handler(). There is one - exception - the timer interrupt used by the OS will not be dispatched - to a user handler - this must be handled by the caller of this macro. - - Level triggered and software interrupts are automatically deasserted by - this code. - - ASSUMPTIONS: - -- PS.INTLEVEL is set to "level" at entry - -- PS.EXCM = 0, C calling enabled - - NOTE: For CALL0 ABI, a12-a15 have not yet been saved. - - NOTE: This macro will use registers a0 and a2-a6. The arguments are: - level -- interrupt level - mask -- interrupt bitmask for this level --------------------------------------------------------------------------------- -*/ - - .macro dispatch_c_isr level mask - - /* Get mask of pending, enabled interrupts at this level into a2. */ - -.L_xt_user_int_&level&: - rsr a2, INTENABLE - rsr a3, INTERRUPT - movi a4, \mask - and a2, a2, a3 - and a2, a2, a4 - beqz a2, 9f /* nothing to do */ - - /* This bit of code provides a nice debug backtrace in the debugger. - It does take a few more instructions, so undef XT_DEBUG_BACKTRACE - if you want to save the cycles. - */ - #if XT_DEBUG_BACKTRACE - #ifndef __XTENSA_CALL0_ABI__ - rsr a0, EPC_1 + \level - 1 /* return address */ - movi a4, 0xC0000000 /* constant with top 2 bits set (call size) */ - or a0, a0, a4 /* set top 2 bits */ - addx2 a0, a4, a0 /* clear top bit -- simulating call4 size */ - #endif - #endif - - #ifdef XT_INTEXC_HOOKS - /* Call interrupt hook if present to (pre)handle interrupts. */ - movi a4, _xt_intexc_hooks - l32i a4, a4, \level << 2 - beqz a4, 2f - #ifdef __XTENSA_CALL0_ABI__ - callx0 a4 - beqz a2, 9f - #else - mov a6, a2 - callx4 a4 - beqz a6, 9f - mov a2, a6 - #endif -2: - #endif - - /* Now look up in the dispatch table and call user ISR if any. */ - /* If multiple bits are set then MSB has highest priority. */ - - extract_msb a4, a2 /* a4 = MSB of a2, a2 trashed */ - - #ifdef XT_USE_SWPRI - /* Enable all interrupts at this level that are numerically higher - than the one we just selected, since they are treated as higher - priority. - */ - movi a3, \mask /* a3 = all interrupts at this level */ - add a2, a4, a4 /* a2 = a4 << 1 */ - addi a2, a2, -1 /* a2 = mask of 1's <= a4 bit */ - and a2, a2, a3 /* a2 = mask of all bits <= a4 at this level */ - movi a3, _xt_intdata - l32i a6, a3, 4 /* a6 = _xt_vpri_mask */ - neg a2, a2 - addi a2, a2, -1 /* a2 = mask to apply */ - and a5, a6, a2 /* mask off all bits <= a4 bit */ - s32i a5, a3, 4 /* update _xt_vpri_mask */ - rsr a3, INTENABLE - and a3, a3, a2 /* mask off all bits <= a4 bit */ - wsr a3, INTENABLE - rsil a3, \level - 1 /* lower interrupt level by 1 */ - #endif - - movi a3, XT_TIMER_INTEN /* a3 = timer interrupt bit */ - wsr a4, INTCLEAR /* clear sw or edge-triggered interrupt */ - beq a3, a4, 7f /* if timer interrupt then skip table */ - - find_ms_setbit a3, a4, a3, 0 /* a3 = interrupt number */ - - movi a4, _xt_interrupt_table - addx8 a3, a3, a4 /* a3 = address of interrupt table entry */ - l32i a4, a3, XIE_HANDLER /* a4 = handler address */ - #ifdef __XTENSA_CALL0_ABI__ - mov a12, a6 /* save in callee-saved reg */ - l32i a2, a3, XIE_ARG /* a2 = handler arg */ - callx0 a4 /* call handler */ - mov a2, a12 - #else - mov a2, a6 /* save in windowed reg */ - l32i a6, a3, XIE_ARG /* a6 = handler arg */ - callx4 a4 /* call handler */ - #endif - - #ifdef XT_USE_SWPRI - j 8f - #else - j .L_xt_user_int_&level& /* check for more interrupts */ - #endif - -7: - - .ifeq XT_TIMER_INTPRI - \level -.L_xt_user_int_timer_&level&: - /* - Interrupt handler for the RTOS tick timer if at this level. - We'll be reading the interrupt state again after this call - so no need to preserve any registers except a6 (vpri_mask). - */ - - #ifdef __XTENSA_CALL0_ABI__ - mov a12, a6 - call0 XT_RTOS_TIMER_INT - mov a2, a12 - #else - mov a2, a6 - call4 XT_RTOS_TIMER_INT - #endif - .endif - - #ifdef XT_USE_SWPRI - j 8f - #else - j .L_xt_user_int_&level& /* check for more interrupts */ - #endif - - #ifdef XT_USE_SWPRI -8: - /* Restore old value of _xt_vpri_mask from a2. Also update INTENABLE from - virtual _xt_intenable which _could_ have changed during interrupt - processing. */ - - movi a3, _xt_intdata - l32i a4, a3, 0 /* a4 = _xt_intenable */ - s32i a2, a3, 4 /* update _xt_vpri_mask */ - and a4, a4, a2 /* a4 = masked intenable */ - wsr a4, INTENABLE /* update INTENABLE */ - #endif - -9: - /* done */ - - .endm - - -/* --------------------------------------------------------------------------------- - Panic handler. - Should be reached by call0 (preferable) or jump only. If call0, a0 says where - from. If on simulator, display panic message and abort, else loop indefinitely. --------------------------------------------------------------------------------- -*/ - - .text - .global panicHandlerRegs - .global panicHandler - .global panicStack - - .global _xt_panic - .type _xt_panic,@function - .align 4 - -_xt_panic: - //ToDo: save registers - movi a2, panicHandlerRegs - s32i a0,a2,0 - s32i a1,a2,4 - s32i a2,a2,8 - s32i a3,a2,12 - s32i a4,a2,16 - s32i a5,a2,20 - s32i a6,a2,24 - s32i a7,a2,28 - s32i a8,a2,32 - s32i a9,a2,36 - s32i a10,a2,40 - s32i a11,a2,44 - s32i a12,a2,48 - s32i a13,a2,52 - s32i a14,a2,56 - s32i a15,a2,60 - - rsr a3, EXCSAVE - s32i a3,a2,64 - rsr a3, EXCSAVE+1 - s32i a3,a2,68 - rsr a3, EXCSAVE+2 - s32i a3,a2,72 - rsr a3, EXCSAVE+3 - s32i a3,a2,76 - rsr a3, EXCSAVE+4 - s32i a3,a2,80 - rsr a3, EXCSAVE+5 - s32i a3,a2,84 - rsr a3, EXCSAVE+6 - s32i a3,a2,88 - rsr a3, EXCSAVE+7 - s32i a3,a2,92 - - rsr a3, EPC - s32i a3,a2,96 - rsr a3, EPC+1 - s32i a3,a2,100 - rsr a3, EPC+2 - s32i a3,a2,104 - rsr a3, EPC+3 - s32i a3,a2,108 - rsr a3, EPC+4 - s32i a3,a2,112 - rsr a3, EPC+5 - s32i a3,a2,116 - rsr a3, EPC+6 - s32i a3,a2,120 - rsr a3, EPC+7 - s32i a3,a2,124 - - rsr a3, DEPC - s32i a3,a2,128 - rsr a3, EXCVADDR - s32i a3,a2,132 - - //Reset window start / base to 0 - movi a2, 1 - wsr a2, windowstart - movi a2, 0 - wsr a2, windowbase - rsync - - //Clear EXCM, set WOE flags - rsr a2, ps - movi a3, ~(PS_EXCM_MASK) - and a2, a2, a3 - movi a3, PS_WOE_MASK - or a2, a2, a3 - wsr a2, ps - rsync - - //Switch to private stack - movi a1,panicStack+(255*4) - rsil a2, XCHAL_EXCM_LEVEL /* disable all low & med ints */ - - - // Debug: output '!' on serport - movi a2,0x60000000 - movi a3,'!' - s32i a3,a2,0 - - movi a3, panicHandlerRegs - l32i a2,a3,0 - call0 panic_print_hex - rsr a2, EXCCAUSE - call0 panic_print_hex - rsr a2, EXCVADDR - call0 panic_print_hex - rsr a2,EPC+1 - call0 panic_print_hex - rsr a2,EPC+2 - call0 panic_print_hex - rsr a2,EPC+3 - call0 panic_print_hex - rsr a2,EPC+6 - call0 panic_print_hex - rsr a2,EXCSAVE_1 - call0 panic_print_hex - rsr a2,DEPC - call0 panic_print_hex - - //Call panic handler -// movi a4, panicHandler -// callx4 a4 - call4 panicHandler - - movi a2,0x60000000 - movi a3,'d' - s32i a3,a2,0 - - -1: j 1b /* loop infinitely */ - - .align 4 -panicHandlerz: - entry a1,32 - movi a2,0x60000000 - movi a3,'h' - memw - s32i a3,a2,0 - - retw - - - .align 4 -//Call using call0. Prints the hex char in a2. Kills a3, a4, a5 -panic_print_hex: - movi a3,0x60000000 - movi a4,8 -panic_print_hex_loop: - l32i a5, a3, 0x1c - extui a5, a5, 16, 8 - bgei a5,64,panic_print_hex_loop - - srli a5,a2,28 - bgei a5,10,panic_print_hex_a - addi a5,a5,'0' - j panic_print_hex_ok -panic_print_hex_a: - addi a5,a5,'A'-10 -panic_print_hex_ok: - s32i a5,a3,0 - slli a2,a2,4 - - addi a4,a4,-1 - bnei a4,0,panic_print_hex_loop - movi a5,' ' - s32i a5,a3,0 - - ret - - - - .section .rodata, "a" - .align 4 - - - -/* --------------------------------------------------------------------------------- - Hooks to dynamically install handlers for exceptions and interrupts. - Allows automated regression frameworks to install handlers per test. - Consists of an array of function pointers indexed by interrupt level, - with index 0 containing the entry for user exceptions. - Initialized with all 0s, meaning no handler is installed at each level. - See comment in xtensa_rtos.h for more details. --------------------------------------------------------------------------------- -*/ - - #ifdef XT_INTEXC_HOOKS - .data - .global _xt_intexc_hooks - .type _xt_intexc_hooks,@object - .align 4 - -_xt_intexc_hooks: - .fill XT_INTEXC_HOOK_NUM, 4, 0 - #endif - - -/* --------------------------------------------------------------------------------- - EXCEPTION AND LEVEL 1 INTERRUPT VECTORS AND LOW LEVEL HANDLERS - (except window exception vectors). - - Each vector goes at a predetermined location according to the Xtensa - hardware configuration, which is ensured by its placement in a special - section known to the Xtensa linker support package (LSP). It performs - the minimum necessary before jumping to the handler in the .text section. - - The corresponding handler goes in the normal .text section. It sets up - the appropriate stack frame, saves a few vector-specific registers and - calls XT_RTOS_INT_ENTER to save the rest of the interrupted context - and enter the RTOS, then sets up a C environment. It then calls the - user's interrupt handler code (which may be coded in C) and finally - calls XT_RTOS_INT_EXIT to transfer control to the RTOS for scheduling. - - While XT_RTOS_INT_EXIT does not return directly to the interruptee, - eventually the RTOS scheduler will want to dispatch the interrupted - task or handler. The scheduler will return to the exit point that was - saved in the interrupt stack frame at XT_STK_EXIT. --------------------------------------------------------------------------------- -*/ - - -/* --------------------------------------------------------------------------------- -Debug Exception. --------------------------------------------------------------------------------- -*/ - -#if XCHAL_HAVE_DEBUG - - .begin literal_prefix .DebugExceptionVector - .section .DebugExceptionVector.text, "ax" - .global _DebugExceptionVector - .align 4 - -_DebugExceptionVector: - - #ifdef XT_SIMULATOR - /* - In the simulator, let the debugger (if any) handle the debug exception, - or simply stop the simulation: - */ - wsr a2, EXCSAVE+XCHAL_DEBUGLEVEL /* save a2 where sim expects it */ - movi a2, SYS_gdb_enter_sktloop - simcall /* have ISS handle debug exc. */ - #elif 0 /* change condition to 1 to use the HAL minimal debug handler */ - wsr a3, EXCSAVE+XCHAL_DEBUGLEVEL - movi a3, xthal_debugexc_defhndlr_nw /* use default debug handler */ - jx a3 - #else - wsr a0, EXCSAVE+XCHAL_DEBUGLEVEL /* save original a0 somewhere */ - call0 _xt_panic /* does not return */ - rfi XCHAL_DEBUGLEVEL /* make a0 point here not later */ - #endif - - .end literal_prefix - -#endif - -/* --------------------------------------------------------------------------------- -Double Exception. -Double exceptions are not a normal occurrence. They indicate a bug of some kind. --------------------------------------------------------------------------------- -*/ - -#ifdef XCHAL_DOUBLEEXC_VECTOR_VADDR - - .begin literal_prefix .DoubleExceptionVector - .section .DoubleExceptionVector.text, "ax" - .global _DoubleExceptionVector - .align 4 - -_DoubleExceptionVector: - - #if XCHAL_HAVE_DEBUG - break 1, 4 /* unhandled double exception */ - #endif - call0 _xt_panic /* does not return */ - rfde /* make a0 point here not later */ - - .end literal_prefix - -#endif /* XCHAL_DOUBLEEXC_VECTOR_VADDR */ - -/* --------------------------------------------------------------------------------- -Kernel Exception (including Level 1 Interrupt from kernel mode). --------------------------------------------------------------------------------- -*/ - - .begin literal_prefix .KernelExceptionVector - .section .KernelExceptionVector.text, "ax" - .global _KernelExceptionVector - .align 4 - -_KernelExceptionVector: - - wsr a0, EXCSAVE_1 /* preserve a0 */ - call0 _xt_kernel_exc /* kernel exception handler */ - /* never returns here - call0 is used as a jump (see note at top) */ - - .end literal_prefix - - .text - .align 4 - -_xt_kernel_exc: - #if XCHAL_HAVE_DEBUG - break 1, 0 /* unhandled kernel exception */ - #endif - call0 _xt_panic /* does not return */ - rfe /* make a0 point here not there */ - - -/* --------------------------------------------------------------------------------- -User Exception (including Level 1 Interrupt from user mode). --------------------------------------------------------------------------------- -*/ - - .begin literal_prefix .UserExceptionVector - .section .UserExceptionVector.text, "ax" - .global _UserExceptionVector - .type _UserExceptionVector,@function - .align 4 - -_UserExceptionVector: - - wsr a0, EXCSAVE_1 /* preserve a0 */ - call0 _xt_user_exc /* user exception handler */ - /* never returns here - call0 is used as a jump (see note at top) */ - - .end literal_prefix - -/* --------------------------------------------------------------------------------- - Insert some waypoints for jumping beyond the signed 8-bit range of - conditional branch instructions, so the conditional branchces to specific - exception handlers are not taken in the mainline. Saves some cycles in the - mainline. --------------------------------------------------------------------------------- -*/ - - .text - - #if XCHAL_HAVE_WINDOWED - .align 4 -_xt_to_alloca_exc: - call0 _xt_alloca_exc /* in window vectors section */ - /* never returns here - call0 is used as a jump (see note at top) */ - #endif - - .align 4 -_xt_to_syscall_exc: - call0 _xt_syscall_exc - /* never returns here - call0 is used as a jump (see note at top) */ - - #if XCHAL_CP_NUM > 0 - .align 4 -_xt_to_coproc_exc: - call0 _xt_coproc_exc - /* never returns here - call0 is used as a jump (see note at top) */ - #endif - - -/* --------------------------------------------------------------------------------- - User exception handler. --------------------------------------------------------------------------------- -*/ - - .type _xt_user_exc,@function - .align 4 - -_xt_user_exc: - - /* If level 1 interrupt then jump to the dispatcher */ - rsr a0, EXCCAUSE - beqi a0, EXCCAUSE_LEVEL1INTERRUPT, _xt_lowint1 - - /* Handle any coprocessor exceptions. Rely on the fact that exception - numbers above EXCCAUSE_CP0_DISABLED all relate to the coprocessors. - */ - #if XCHAL_CP_NUM > 0 - bgeui a0, EXCCAUSE_CP0_DISABLED, _xt_to_coproc_exc - #endif - - /* Handle alloca and syscall exceptions */ - #if XCHAL_HAVE_WINDOWED - beqi a0, EXCCAUSE_ALLOCA, _xt_to_alloca_exc - #endif - beqi a0, EXCCAUSE_SYSCALL, _xt_to_syscall_exc - - //HACK! No custom vector stuff, just call panic handler directly. ToDo: remove this when possible. -JD - call0 _xt_panic - - /* Handle all other exceptions. All can have user-defined handlers. */ - /* NOTE: we'll stay on the user stack for exception handling. */ - - /* Allocate exception frame and save minimal context. */ - mov a0, sp - addi sp, sp, -XT_STK_FRMSZ - s32i a0, sp, XT_STK_A1 - #if XCHAL_HAVE_WINDOWED - s32e a0, sp, -12 /* for debug backtrace */ - #endif - rsr a0, PS /* save interruptee's PS */ - s32i a0, sp, XT_STK_PS - rsr a0, EPC_1 /* save interruptee's PC */ - s32i a0, sp, XT_STK_PC - rsr a0, EXCSAVE_1 /* save interruptee's a0 */ - s32i a0, sp, XT_STK_A0 - #if XCHAL_HAVE_WINDOWED - s32e a0, sp, -16 /* for debug backtrace */ - #endif - s32i a12, sp, XT_STK_A12 /* _xt_context_save requires A12- */ - s32i a13, sp, XT_STK_A13 /* A13 to have already been saved */ - call0 _xt_context_save - - /* Save exc cause and vaddr into exception frame */ - rsr a0, EXCCAUSE - s32i a0, sp, XT_STK_EXCCAUSE - rsr a0, EXCVADDR - s32i a0, sp, XT_STK_EXCVADDR - - /* Set up PS for C, reenable hi-pri interrupts, and clear EXCM. */ - #ifdef __XTENSA_CALL0_ABI__ - movi a0, PS_INTLEVEL(XCHAL_EXCM_LEVEL) | PS_UM - #else - movi a0, PS_INTLEVEL(XCHAL_EXCM_LEVEL) | PS_UM | PS_WOE - #endif - wsr a0, PS - - #ifdef XT_DEBUG_BACKTRACE - #ifndef __XTENSA_CALL0_ABI__ - rsr a0, EPC_1 /* return address for debug backtrace */ - movi a5, 0xC0000000 /* constant with top 2 bits set (call size) */ - rsync /* wait for WSR.PS to complete */ - or a0, a0, a5 /* set top 2 bits */ - addx2 a0, a5, a0 /* clear top bit -- thus simulating call4 size */ - #else - rsync /* wait for WSR.PS to complete */ - #endif - #endif - - rsr a2, EXCCAUSE /* recover exc cause */ - - #ifdef XT_INTEXC_HOOKS - /* - Call exception hook to pre-handle exceptions (if installed). - Pass EXCCAUSE in a2, and check result in a2 (if -1, skip default handling). - */ - movi a4, _xt_intexc_hooks - l32i a4, a4, 0 /* user exception hook index 0 */ - beqz a4, 1f -.Ln_xt_user_exc_call_hook: - #ifdef __XTENSA_CALL0_ABI__ - callx0 a4 - beqi a2, -1, .L_xt_user_done - #else - mov a6, a2 - callx4 a4 - beqi a6, -1, .L_xt_user_done - mov a2, a6 - #endif -1: - #endif - - rsr a2, EXCCAUSE /* recover exc cause */ - movi a3, _xt_exception_table - addx4 a4, a2, a3 /* a4 = address of exception table entry */ - l32i a4, a4, 0 /* a4 = handler address */ - #ifdef __XTENSA_CALL0_ABI__ - mov a2, sp /* a2 = pointer to exc frame */ - callx0 a4 /* call handler */ - #else - mov a6, sp /* a6 = pointer to exc frame */ - callx4 a4 /* call handler */ - #endif - -.L_xt_user_done: - - /* Restore context and return */ - call0 _xt_context_restore - l32i a0, sp, XT_STK_PS /* retrieve interruptee's PS */ - wsr a0, PS - l32i a0, sp, XT_STK_PC /* retrieve interruptee's PC */ - wsr a0, EPC_1 - l32i a0, sp, XT_STK_A0 /* retrieve interruptee's A0 */ - l32i sp, sp, XT_STK_A1 /* remove exception frame */ - rsync /* ensure PS and EPC written */ - rfe /* PS.EXCM is cleared */ - - -/* --------------------------------------------------------------------------------- - Exit point for dispatch. Saved in interrupt stack frame at XT_STK_EXIT - on entry and used to return to a thread or interrupted interrupt handler. --------------------------------------------------------------------------------- -*/ - - .global _xt_user_exit - .type _xt_user_exit,@function - .align 4 -_xt_user_exit: - l32i a0, sp, XT_STK_PS /* retrieve interruptee's PS */ - wsr a0, PS - l32i a0, sp, XT_STK_PC /* retrieve interruptee's PC */ - wsr a0, EPC_1 - l32i a0, sp, XT_STK_A0 /* retrieve interruptee's A0 */ - l32i sp, sp, XT_STK_A1 /* remove interrupt stack frame */ - rsync /* ensure PS and EPC written */ - rfe /* PS.EXCM is cleared */ - - -/* --------------------------------------------------------------------------------- -Syscall Exception Handler (jumped to from User Exception Handler). -Syscall 0 is required to spill the register windows (no-op in Call 0 ABI). -Only syscall 0 is handled here. Other syscalls return -1 to caller in a2. --------------------------------------------------------------------------------- -*/ - - .text - .type _xt_syscall_exc,@function - .align 4 -_xt_syscall_exc: - - #ifdef __XTENSA_CALL0_ABI__ - /* - Save minimal regs for scratch. Syscall 0 does nothing in Call0 ABI. - Use a minimal stack frame (16B) to save A2 & A3 for scratch. - PS.EXCM could be cleared here, but unlikely to improve worst-case latency. - rsr a0, PS - addi a0, a0, -PS_EXCM_MASK - wsr a0, PS - */ - addi sp, sp, -16 - s32i a2, sp, 8 - s32i a3, sp, 12 - #else /* Windowed ABI */ - /* - Save necessary context and spill the register windows. - PS.EXCM is still set and must remain set until after the spill. - Reuse context save function though it saves more than necessary. - For this reason, a full interrupt stack frame is allocated. - */ - addi sp, sp, -XT_STK_FRMSZ /* allocate interrupt stack frame */ - s32i a12, sp, XT_STK_A12 /* _xt_context_save requires A12- */ - s32i a13, sp, XT_STK_A13 /* A13 to have already been saved */ - call0 _xt_context_save - #endif - - /* - Grab the interruptee's PC and skip over the 'syscall' instruction. - If it's at the end of a zero-overhead loop and it's not on the last - iteration, decrement loop counter and skip to beginning of loop. - */ - rsr a2, EPC_1 /* a2 = PC of 'syscall' */ - addi a3, a2, 3 /* ++PC */ - #if XCHAL_HAVE_LOOPS - rsr a0, LEND /* if (PC == LEND */ - bne a3, a0, 1f - rsr a0, LCOUNT /* && LCOUNT != 0) */ - beqz a0, 1f /* { */ - addi a0, a0, -1 /* --LCOUNT */ - rsr a3, LBEG /* PC = LBEG */ - wsr a0, LCOUNT /* } */ - #endif -1: wsr a3, EPC_1 /* update PC */ - - /* Restore interruptee's context and return from exception. */ - #ifdef __XTENSA_CALL0_ABI__ - l32i a2, sp, 8 - l32i a3, sp, 12 - addi sp, sp, 16 - #else - call0 _xt_context_restore - addi sp, sp, XT_STK_FRMSZ - #endif - movi a0, -1 - movnez a2, a0, a2 /* return -1 if not syscall 0 */ - rsr a0, EXCSAVE_1 - rfe - -/* --------------------------------------------------------------------------------- -Co-Processor Exception Handler (jumped to from User Exception Handler). -These exceptions are generated by co-processor instructions, which are only -allowed in thread code (not in interrupts or kernel code). This restriction is -deliberately imposed to reduce the burden of state-save/restore in interrupts. --------------------------------------------------------------------------------- -*/ -#if XCHAL_CP_NUM > 0 - - .section .rodata, "a" - -/* Offset to CP n save area in thread's CP save area. */ - .global _xt_coproc_sa_offset - .type _xt_coproc_sa_offset,@object - .align 16 /* minimize crossing cache boundaries */ -_xt_coproc_sa_offset: - .word XT_CP0_SA, XT_CP1_SA, XT_CP2_SA, XT_CP3_SA - .word XT_CP4_SA, XT_CP5_SA, XT_CP6_SA, XT_CP7_SA - -/* Bitmask for CP n's CPENABLE bit. */ - .type _xt_coproc_mask,@object - .align 16,,8 /* try to keep it all in one cache line */ - .set i, 0 -_xt_coproc_mask: - .rept XCHAL_CP_MAX - .long (i<<16) | (1<= 2 - - .begin literal_prefix .Level2InterruptVector - .section .Level2InterruptVector.text, "ax" - .global _Level2Vector - .type _Level2Vector,@function - .align 4 -_Level2Vector: - wsr a0, EXCSAVE_2 /* preserve a0 */ - call0 _xt_medint2 /* load interrupt handler */ - /* never returns here - call0 is used as a jump (see note at top) */ - - .end literal_prefix - - .text - .type _xt_medint2,@function - .align 4 -_xt_medint2: - mov a0, sp /* sp == a1 */ - addi sp, sp, -XT_STK_FRMSZ /* allocate interrupt stack frame */ - s32i a0, sp, XT_STK_A1 /* save pre-interrupt SP */ - rsr a0, EPS_2 /* save interruptee's PS */ - s32i a0, sp, XT_STK_PS - rsr a0, EPC_2 /* save interruptee's PC */ - s32i a0, sp, XT_STK_PC - rsr a0, EXCSAVE_2 /* save interruptee's a0 */ - s32i a0, sp, XT_STK_A0 - movi a0, _xt_medint2_exit /* save exit point for dispatch */ - s32i a0, sp, XT_STK_EXIT - - /* Save rest of interrupt context and enter RTOS. */ - call0 XT_RTOS_INT_ENTER /* common RTOS interrupt entry */ - - /* !! We are now on the RTOS system stack !! */ - - /* Set up PS for C, enable interrupts above this level and clear EXCM. */ - #ifdef __XTENSA_CALL0_ABI__ - movi a0, PS_INTLEVEL(2) | PS_UM - #else - movi a0, PS_INTLEVEL(2) | PS_UM | PS_WOE - #endif - wsr a0, PS - rsync - - /* OK to call C code at this point, dispatch user ISRs */ - - dispatch_c_isr 2 XCHAL_INTLEVEL2_MASK - - /* Done handling interrupts, transfer control to OS */ - call0 XT_RTOS_INT_EXIT /* does not return directly here */ - - /* - Exit point for dispatch. Saved in interrupt stack frame at XT_STK_EXIT - on entry and used to return to a thread or interrupted interrupt handler. - */ - .global _xt_medint2_exit - .type _xt_medint2_exit,@function - .align 4 -_xt_medint2_exit: - /* Restore only level-specific regs (the rest were already restored) */ - l32i a0, sp, XT_STK_PS /* retrieve interruptee's PS */ - wsr a0, EPS_2 - l32i a0, sp, XT_STK_PC /* retrieve interruptee's PC */ - wsr a0, EPC_2 - l32i a0, sp, XT_STK_A0 /* retrieve interruptee's A0 */ - l32i sp, sp, XT_STK_A1 /* remove interrupt stack frame */ - rsync /* ensure EPS and EPC written */ - rfi 2 - -#endif /* Level 2 */ - -#if XCHAL_EXCM_LEVEL >= 3 - - .begin literal_prefix .Level3InterruptVector - .section .Level3InterruptVector.text, "ax" - .global _Level3Vector - .type _Level3Vector,@function - .align 4 -_Level3Vector: - wsr a0, EXCSAVE_3 /* preserve a0 */ - call0 _xt_medint3 /* load interrupt handler */ - /* never returns here - call0 is used as a jump (see note at top) */ - - .end literal_prefix - - .text - .type _xt_medint3,@function - .align 4 -_xt_medint3: - mov a0, sp /* sp == a1 */ - addi sp, sp, -XT_STK_FRMSZ /* allocate interrupt stack frame */ - s32i a0, sp, XT_STK_A1 /* save pre-interrupt SP */ - rsr a0, EPS_3 /* save interruptee's PS */ - s32i a0, sp, XT_STK_PS - rsr a0, EPC_3 /* save interruptee's PC */ - s32i a0, sp, XT_STK_PC - rsr a0, EXCSAVE_3 /* save interruptee's a0 */ - s32i a0, sp, XT_STK_A0 - movi a0, _xt_medint3_exit /* save exit point for dispatch */ - s32i a0, sp, XT_STK_EXIT - - /* Save rest of interrupt context and enter RTOS. */ - call0 XT_RTOS_INT_ENTER /* common RTOS interrupt entry */ - - /* !! We are now on the RTOS system stack !! */ - - /* Set up PS for C, enable interrupts above this level and clear EXCM. */ - #ifdef __XTENSA_CALL0_ABI__ - movi a0, PS_INTLEVEL(3) | PS_UM - #else - movi a0, PS_INTLEVEL(3) | PS_UM | PS_WOE - #endif - wsr a0, PS - rsync - - /* OK to call C code at this point, dispatch user ISRs */ - - dispatch_c_isr 3 XCHAL_INTLEVEL3_MASK - - /* Done handling interrupts, transfer control to OS */ - call0 XT_RTOS_INT_EXIT /* does not return directly here */ - - /* - Exit point for dispatch. Saved in interrupt stack frame at XT_STK_EXIT - on entry and used to return to a thread or interrupted interrupt handler. - */ - .global _xt_medint3_exit - .type _xt_medint3_exit,@function - .align 4 -_xt_medint3_exit: - /* Restore only level-specific regs (the rest were already restored) */ - l32i a0, sp, XT_STK_PS /* retrieve interruptee's PS */ - wsr a0, EPS_3 - l32i a0, sp, XT_STK_PC /* retrieve interruptee's PC */ - wsr a0, EPC_3 - l32i a0, sp, XT_STK_A0 /* retrieve interruptee's A0 */ - l32i sp, sp, XT_STK_A1 /* remove interrupt stack frame */ - rsync /* ensure EPS and EPC written */ - rfi 3 - -#endif /* Level 3 */ - -#if XCHAL_EXCM_LEVEL >= 4 - - .begin literal_prefix .Level4InterruptVector - .section .Level4InterruptVector.text, "ax" - .global _Level4Vector - .type _Level4Vector,@function - .align 4 -_Level4Vector: - wsr a0, EXCSAVE_4 /* preserve a0 */ - call0 _xt_medint4 /* load interrupt handler */ - - .end literal_prefix - - .text - .type _xt_medint4,@function - .align 4 -_xt_medint4: - mov a0, sp /* sp == a1 */ - addi sp, sp, -XT_STK_FRMSZ /* allocate interrupt stack frame */ - s32i a0, sp, XT_STK_A1 /* save pre-interrupt SP */ - rsr a0, EPS_4 /* save interruptee's PS */ - s32i a0, sp, XT_STK_PS - rsr a0, EPC_4 /* save interruptee's PC */ - s32i a0, sp, XT_STK_PC - rsr a0, EXCSAVE_4 /* save interruptee's a0 */ - s32i a0, sp, XT_STK_A0 - movi a0, _xt_medint4_exit /* save exit point for dispatch */ - s32i a0, sp, XT_STK_EXIT - - /* Save rest of interrupt context and enter RTOS. */ - call0 XT_RTOS_INT_ENTER /* common RTOS interrupt entry */ - - /* !! We are now on the RTOS system stack !! */ - - /* Set up PS for C, enable interrupts above this level and clear EXCM. */ - #ifdef __XTENSA_CALL0_ABI__ - movi a0, PS_INTLEVEL(4) | PS_UM - #else - movi a0, PS_INTLEVEL(4) | PS_UM | PS_WOE - #endif - wsr a0, PS - rsync - - /* OK to call C code at this point, dispatch user ISRs */ - - dispatch_c_isr 4 XCHAL_INTLEVEL4_MASK - - /* Done handling interrupts, transfer control to OS */ - call0 XT_RTOS_INT_EXIT /* does not return directly here */ - - /* - Exit point for dispatch. Saved in interrupt stack frame at XT_STK_EXIT - on entry and used to return to a thread or interrupted interrupt handler. - */ - .global _xt_medint4_exit - .type _xt_medint4_exit,@function - .align 4 -_xt_medint4_exit: - /* Restore only level-specific regs (the rest were already restored) */ - l32i a0, sp, XT_STK_PS /* retrieve interruptee's PS */ - wsr a0, EPS_4 - l32i a0, sp, XT_STK_PC /* retrieve interruptee's PC */ - wsr a0, EPC_4 - l32i a0, sp, XT_STK_A0 /* retrieve interruptee's A0 */ - l32i sp, sp, XT_STK_A1 /* remove interrupt stack frame */ - rsync /* ensure EPS and EPC written */ - rfi 4 - -#endif /* Level 4 */ - -#if XCHAL_EXCM_LEVEL >= 5 - - .begin literal_prefix .Level5InterruptVector - .section .Level5InterruptVector.text, "ax" - .global _Level5Vector - .type _Level5Vector,@function - .align 4 -_Level5Vector: - wsr a0, EXCSAVE_5 /* preserve a0 */ - call0 _xt_medint5 /* load interrupt handler */ - - .end literal_prefix - - .text - .type _xt_medint5,@function - .align 4 -_xt_medint5: - mov a0, sp /* sp == a1 */ - addi sp, sp, -XT_STK_FRMSZ /* allocate interrupt stack frame */ - s32i a0, sp, XT_STK_A1 /* save pre-interrupt SP */ - rsr a0, EPS_5 /* save interruptee's PS */ - s32i a0, sp, XT_STK_PS - rsr a0, EPC_5 /* save interruptee's PC */ - s32i a0, sp, XT_STK_PC - rsr a0, EXCSAVE_5 /* save interruptee's a0 */ - s32i a0, sp, XT_STK_A0 - movi a0, _xt_medint5_exit /* save exit point for dispatch */ - s32i a0, sp, XT_STK_EXIT - - /* Save rest of interrupt context and enter RTOS. */ - call0 XT_RTOS_INT_ENTER /* common RTOS interrupt entry */ - - /* !! We are now on the RTOS system stack !! */ - - /* Set up PS for C, enable interrupts above this level and clear EXCM. */ - #ifdef __XTENSA_CALL0_ABI__ - movi a0, PS_INTLEVEL(5) | PS_UM - #else - movi a0, PS_INTLEVEL(5) | PS_UM | PS_WOE - #endif - wsr a0, PS - rsync - - /* OK to call C code at this point, dispatch user ISRs */ - - dispatch_c_isr 5 XCHAL_INTLEVEL5_MASK - - /* Done handling interrupts, transfer control to OS */ - call0 XT_RTOS_INT_EXIT /* does not return directly here */ - - /* - Exit point for dispatch. Saved in interrupt stack frame at XT_STK_EXIT - on entry and used to return to a thread or interrupted interrupt handler. - */ - .global _xt_medint5_exit - .type _xt_medint5_exit,@function - .align 4 -_xt_medint5_exit: - /* Restore only level-specific regs (the rest were already restored) */ - l32i a0, sp, XT_STK_PS /* retrieve interruptee's PS */ - wsr a0, EPS_5 - l32i a0, sp, XT_STK_PC /* retrieve interruptee's PC */ - wsr a0, EPC_5 - l32i a0, sp, XT_STK_A0 /* retrieve interruptee's A0 */ - l32i sp, sp, XT_STK_A1 /* remove interrupt stack frame */ - rsync /* ensure EPS and EPC written */ - rfi 5 - -#endif /* Level 5 */ - -#if XCHAL_EXCM_LEVEL >= 6 - - .begin literal_prefix .Level6InterruptVector - .section .Level6InterruptVector.text, "ax" - .global _Level6Vector - .type _Level6Vector,@function - .align 4 -_Level6Vector: - wsr a0, EXCSAVE_6 /* preserve a0 */ - call0 _xt_medint6 /* load interrupt handler */ - - .end literal_prefix - - .text - .type _xt_medint6,@function - .align 4 -_xt_medint6: - mov a0, sp /* sp == a1 */ - addi sp, sp, -XT_STK_FRMSZ /* allocate interrupt stack frame */ - s32i a0, sp, XT_STK_A1 /* save pre-interrupt SP */ - rsr a0, EPS_6 /* save interruptee's PS */ - s32i a0, sp, XT_STK_PS - rsr a0, EPC_6 /* save interruptee's PC */ - s32i a0, sp, XT_STK_PC - rsr a0, EXCSAVE_6 /* save interruptee's a0 */ - s32i a0, sp, XT_STK_A0 - movi a0, _xt_medint6_exit /* save exit point for dispatch */ - s32i a0, sp, XT_STK_EXIT - - /* Save rest of interrupt context and enter RTOS. */ - call0 XT_RTOS_INT_ENTER /* common RTOS interrupt entry */ - - /* !! We are now on the RTOS system stack !! */ - - /* Set up PS for C, enable interrupts above this level and clear EXCM. */ - #ifdef __XTENSA_CALL0_ABI__ - movi a0, PS_INTLEVEL(6) | PS_UM - #else - movi a0, PS_INTLEVEL(6) | PS_UM | PS_WOE - #endif - wsr a0, PS - rsync - - /* OK to call C code at this point, dispatch user ISRs */ - - dispatch_c_isr 6 XCHAL_INTLEVEL6_MASK - - /* Done handling interrupts, transfer control to OS */ - call0 XT_RTOS_INT_EXIT /* does not return directly here */ - - /* - Exit point for dispatch. Saved in interrupt stack frame at XT_STK_EXIT - on entry and used to return to a thread or interrupted interrupt handler. - */ - .global _xt_medint6_exit - .type _xt_medint6_exit,@function - .align 4 -_xt_medint6_exit: - /* Restore only level-specific regs (the rest were already restored) */ - l32i a0, sp, XT_STK_PS /* retrieve interruptee's PS */ - wsr a0, EPS_6 - l32i a0, sp, XT_STK_PC /* retrieve interruptee's PC */ - wsr a0, EPC_6 - l32i a0, sp, XT_STK_A0 /* retrieve interruptee's A0 */ - l32i sp, sp, XT_STK_A1 /* remove interrupt stack frame */ - rsync /* ensure EPS and EPC written */ - rfi 6 - -#endif /* Level 6 */ - - -/******************************************************************************* - -HIGH PRIORITY (LEVEL > XCHAL_EXCM_LEVEL) INTERRUPT VECTORS AND HANDLERS - -High priority interrupts are by definition those with priorities greater -than XCHAL_EXCM_LEVEL. This includes non-maskable (NMI). High priority -interrupts cannot interact with the RTOS, that is they must save all regs -they use and not call any RTOS function. - -A further restriction imposed by the Xtensa windowed architecture is that -high priority interrupts must not modify the stack area even logically -"above" the top of the interrupted stack (they need to provide their -own stack or static save area). - -Cadence Design Systems recommends high priority interrupt handlers be coded in assembly -and used for purposes requiring very short service times. - -Here are templates for high priority (level 2+) interrupt vectors. -They assume only one interrupt per level to avoid the burden of identifying -which interrupts at this level are pending and enabled. This allows for -minimum latency and avoids having to save/restore a2 in addition to a0. -If more than one interrupt per high priority level is configured, this burden -is on the handler which in any case must provide a way to save and restore -registers it uses without touching the interrupted stack. - -Each vector goes at a predetermined location according to the Xtensa -hardware configuration, which is ensured by its placement in a special -section known to the Xtensa linker support package (LSP). It performs -the minimum necessary before jumping to the handler in the .text section. - -*******************************************************************************/ - -/* -Currently only shells for high priority interrupt handlers are provided -here. However a template and example can be found in the Cadence Design Systems tools -documentation: "Microprocessor Programmer's Guide". -*/ - -#if XCHAL_NUM_INTLEVELS >=2 && XCHAL_EXCM_LEVEL <2 && XCHAL_DEBUGLEVEL !=2 - - .begin literal_prefix .Level2InterruptVector - .section .Level2InterruptVector.text, "ax" - .global _Level2Vector - .type _Level2Vector,@function - .align 4 -_Level2Vector: - wsr a0, EXCSAVE_2 /* preserve a0 */ - call0 _xt_highint2 /* load interrupt handler */ - - .end literal_prefix - - .text - .type _xt_highint2,@function - .align 4 -_xt_highint2: - - #ifdef XT_INTEXC_HOOKS - /* Call interrupt hook if present to (pre)handle interrupts. */ - movi a0, _xt_intexc_hooks - l32i a0, a0, 2<<2 - beqz a0, 1f -.Ln_xt_highint2_call_hook: - callx0 a0 /* must NOT disturb stack! */ -1: - #endif - - /* USER_EDIT: - ADD HIGH PRIORITY LEVEL 2 INTERRUPT HANDLER CODE HERE. - */ - - .align 4 -.L_xt_highint2_exit: - rsr a0, EXCSAVE_2 /* restore a0 */ - rfi 2 - -#endif /* Level 2 */ - -#if XCHAL_NUM_INTLEVELS >=3 && XCHAL_EXCM_LEVEL <3 && XCHAL_DEBUGLEVEL !=3 - - .begin literal_prefix .Level3InterruptVector - .section .Level3InterruptVector.text, "ax" - .global _Level3Vector - .type _Level3Vector,@function - .align 4 -_Level3Vector: - wsr a0, EXCSAVE_3 /* preserve a0 */ - call0 _xt_highint3 /* load interrupt handler */ - /* never returns here - call0 is used as a jump (see note at top) */ - - .end literal_prefix - - .text - .type _xt_highint3,@function - .align 4 -_xt_highint3: - - #ifdef XT_INTEXC_HOOKS - /* Call interrupt hook if present to (pre)handle interrupts. */ - movi a0, _xt_intexc_hooks - l32i a0, a0, 3<<2 - beqz a0, 1f -.Ln_xt_highint3_call_hook: - callx0 a0 /* must NOT disturb stack! */ -1: - #endif - - /* USER_EDIT: - ADD HIGH PRIORITY LEVEL 3 INTERRUPT HANDLER CODE HERE. - */ - - .align 4 -.L_xt_highint3_exit: - rsr a0, EXCSAVE_3 /* restore a0 */ - rfi 3 - -#endif /* Level 3 */ - -#if XCHAL_NUM_INTLEVELS >=4 && XCHAL_EXCM_LEVEL <4 && XCHAL_DEBUGLEVEL !=4 - - .begin literal_prefix .Level4InterruptVector - .section .Level4InterruptVector.text, "ax" - .global _Level4Vector - .type _Level4Vector,@function - .align 4 -_Level4Vector: - wsr a0, EXCSAVE_4 /* preserve a0 */ - call0 _xt_highint4 /* load interrupt handler */ - /* never returns here - call0 is used as a jump (see note at top) */ - - .end literal_prefix - - .text - .type _xt_highint4,@function - .align 4 -_xt_highint4: - - #ifdef XT_INTEXC_HOOKS - /* Call interrupt hook if present to (pre)handle interrupts. */ - movi a0, _xt_intexc_hooks - l32i a0, a0, 4<<2 - beqz a0, 1f -.Ln_xt_highint4_call_hook: - callx0 a0 /* must NOT disturb stack! */ -1: - #endif - - /* USER_EDIT: - ADD HIGH PRIORITY LEVEL 4 INTERRUPT HANDLER CODE HERE. - */ - - .align 4 -.L_xt_highint4_exit: - rsr a0, EXCSAVE_4 /* restore a0 */ - rfi 4 - -#endif /* Level 4 */ - -#if XCHAL_NUM_INTLEVELS >=5 && XCHAL_EXCM_LEVEL <5 && XCHAL_DEBUGLEVEL !=5 - - .begin literal_prefix .Level5InterruptVector - .section .Level5InterruptVector.text, "ax" - .global _Level5Vector - .type _Level5Vector,@function - .align 4 -_Level5Vector: - wsr a0, EXCSAVE_5 /* preserve a0 */ - call0 _xt_highint5 /* load interrupt handler */ - /* never returns here - call0 is used as a jump (see note at top) */ - - .end literal_prefix - - .text - .type _xt_highint5,@function - .align 4 -_xt_highint5: - - #ifdef XT_INTEXC_HOOKS - /* Call interrupt hook if present to (pre)handle interrupts. */ - movi a0, _xt_intexc_hooks - l32i a0, a0, 5<<2 - beqz a0, 1f -.Ln_xt_highint5_call_hook: - callx0 a0 /* must NOT disturb stack! */ -1: - #endif - - /* USER_EDIT: - ADD HIGH PRIORITY LEVEL 5 INTERRUPT HANDLER CODE HERE. - */ - - .align 4 -.L_xt_highint5_exit: - rsr a0, EXCSAVE_5 /* restore a0 */ - rfi 5 - -#endif /* Level 5 */ - -#if XCHAL_NUM_INTLEVELS >=6 && XCHAL_EXCM_LEVEL <6 && XCHAL_DEBUGLEVEL !=6 - - .begin literal_prefix .Level6InterruptVector - .section .Level6InterruptVector.text, "ax" - .global _Level6Vector - .type _Level6Vector,@function - .align 4 -_Level6Vector: - wsr a0, EXCSAVE_6 /* preserve a0 */ - call0 _xt_highint6 /* load interrupt handler */ - /* never returns here - call0 is used as a jump (see note at top) */ - - .end literal_prefix - - .text - .type _xt_highint6,@function - .align 4 -_xt_highint6: - - #ifdef XT_INTEXC_HOOKS - /* Call interrupt hook if present to (pre)handle interrupts. */ - movi a0, _xt_intexc_hooks - l32i a0, a0, 6<<2 - beqz a0, 1f -.Ln_xt_highint6_call_hook: - callx0 a0 /* must NOT disturb stack! */ -1: - #endif - - /* USER_EDIT: - ADD HIGH PRIORITY LEVEL 6 INTERRUPT HANDLER CODE HERE. - */ - - .align 4 -.L_xt_highint6_exit: - rsr a0, EXCSAVE_6 /* restore a0 */ - rfi 6 - -#endif /* Level 6 */ - -#if XCHAL_HAVE_NMI - - .begin literal_prefix .NMIExceptionVector - .section .NMIExceptionVector.text, "ax" - .global _NMIExceptionVector - .type _NMIExceptionVector,@function - .align 4 -_NMIExceptionVector: - wsr a0, EXCSAVE + XCHAL_NMILEVEL _ /* preserve a0 */ - call0 _xt_nmi /* load interrupt handler */ - /* never returns here - call0 is used as a jump (see note at top) */ - - .end literal_prefix - - .text - .type _xt_nmi,@function - .align 4 -_xt_nmi: - - #ifdef XT_INTEXC_HOOKS - /* Call interrupt hook if present to (pre)handle interrupts. */ - movi a0, _xt_intexc_hooks - l32i a0, a0, XCHAL_NMILEVEL<<2 - beqz a0, 1f -.Ln_xt_nmi_call_hook: - callx0 a0 /* must NOT disturb stack! */ -1: - #endif - - /* USER_EDIT: - ADD HIGH PRIORITY NON-MASKABLE INTERRUPT (NMI) HANDLER CODE HERE. - */ - - .align 4 -.L_xt_nmi_exit: - rsr a0, EXCSAVE + XCHAL_NMILEVEL /* restore a0 */ - rfi XCHAL_NMILEVEL - -#endif /* NMI */ - - -/******************************************************************************* - -WINDOW OVERFLOW AND UNDERFLOW EXCEPTION VECTORS AND ALLOCA EXCEPTION HANDLER - -Here is the code for each window overflow/underflow exception vector and -(interspersed) efficient code for handling the alloca exception cause. -Window exceptions are handled entirely in the vector area and are very -tight for performance. The alloca exception is also handled entirely in -the window vector area so comes at essentially no cost in code size. -Users should never need to modify them and Cadence Design Systems recommends -they do not. - -Window handlers go at predetermined vector locations according to the -Xtensa hardware configuration, which is ensured by their placement in a -special section known to the Xtensa linker support package (LSP). Since -their offsets in that section are always the same, the LSPs do not define -a section per vector. - -These things are coded for XEA2 only (XEA1 is not supported). - -Note on Underflow Handlers: -The underflow handler for returning from call[i+1] to call[i] -must preserve all the registers from call[i+1]'s window. -In particular, a0 and a1 must be preserved because the RETW instruction -will be reexecuted (and may even underflow if an intervening exception -has flushed call[i]'s registers). -Registers a2 and up may contain return values. - -*******************************************************************************/ - -#if XCHAL_HAVE_WINDOWED - - .section .WindowVectors.text, "ax" - -/* --------------------------------------------------------------------------------- -Window Overflow Exception for Call4. - -Invoked if a call[i] referenced a register (a4-a15) -that contains data from ancestor call[j]; -call[j] had done a call4 to call[j+1]. -On entry here: - window rotated to call[j] start point; - a0-a3 are registers to be saved; - a4-a15 must be preserved; - a5 is call[j+1]'s stack pointer. --------------------------------------------------------------------------------- -*/ - - .org 0x0 - .global _WindowOverflow4 -_WindowOverflow4: - - s32e a0, a5, -16 /* save a0 to call[j+1]'s stack frame */ - s32e a1, a5, -12 /* save a1 to call[j+1]'s stack frame */ - s32e a2, a5, -8 /* save a2 to call[j+1]'s stack frame */ - s32e a3, a5, -4 /* save a3 to call[j+1]'s stack frame */ - rfwo /* rotates back to call[i] position */ - -/* --------------------------------------------------------------------------------- -Window Underflow Exception for Call4 - -Invoked by RETW returning from call[i+1] to call[i] -where call[i]'s registers must be reloaded (not live in ARs); -where call[i] had done a call4 to call[i+1]. -On entry here: - window rotated to call[i] start point; - a0-a3 are undefined, must be reloaded with call[i].reg[0..3]; - a4-a15 must be preserved (they are call[i+1].reg[0..11]); - a5 is call[i+1]'s stack pointer. --------------------------------------------------------------------------------- -*/ - - .org 0x40 - .global _WindowUnderflow4 -_WindowUnderflow4: - - l32e a0, a5, -16 /* restore a0 from call[i+1]'s stack frame */ - l32e a1, a5, -12 /* restore a1 from call[i+1]'s stack frame */ - l32e a2, a5, -8 /* restore a2 from call[i+1]'s stack frame */ - l32e a3, a5, -4 /* restore a3 from call[i+1]'s stack frame */ - rfwu - -/* --------------------------------------------------------------------------------- -Handle alloca exception generated by interruptee executing 'movsp'. -This uses space between the window vectors, so is essentially "free". -All interruptee's regs are intact except a0 which is saved in EXCSAVE_1, -and PS.EXCM has been set by the exception hardware (can't be interrupted). -The fact the alloca exception was taken means the registers associated with -the base-save area have been spilled and will be restored by the underflow -handler, so those 4 registers are available for scratch. -The code is optimized to avoid unaligned branches and minimize cache misses. --------------------------------------------------------------------------------- -*/ - - .align 4 - .global _xt_alloca_exc -_xt_alloca_exc: - - rsr a0, WINDOWBASE /* grab WINDOWBASE before rotw changes it */ - rotw -1 /* WINDOWBASE goes to a4, new a0-a3 are scratch */ - rsr a2, PS - extui a3, a2, XCHAL_PS_OWB_SHIFT, XCHAL_PS_OWB_BITS - xor a3, a3, a4 /* bits changed from old to current windowbase */ - rsr a4, EXCSAVE_1 /* restore original a0 (now in a4) */ - slli a3, a3, XCHAL_PS_OWB_SHIFT - xor a2, a2, a3 /* flip changed bits in old window base */ - wsr a2, PS /* update PS.OWB to new window base */ - rsync - - _bbci.l a4, 31, _WindowUnderflow4 - rotw -1 /* original a0 goes to a8 */ - _bbci.l a8, 30, _WindowUnderflow8 - rotw -1 - j _WindowUnderflow12 - -/* --------------------------------------------------------------------------------- -Window Overflow Exception for Call8 - -Invoked if a call[i] referenced a register (a4-a15) -that contains data from ancestor call[j]; -call[j] had done a call8 to call[j+1]. -On entry here: - window rotated to call[j] start point; - a0-a7 are registers to be saved; - a8-a15 must be preserved; - a9 is call[j+1]'s stack pointer. --------------------------------------------------------------------------------- -*/ - - .org 0x80 - .global _WindowOverflow8 -_WindowOverflow8: - - s32e a0, a9, -16 /* save a0 to call[j+1]'s stack frame */ - l32e a0, a1, -12 /* a0 <- call[j-1]'s sp - (used to find end of call[j]'s frame) */ - s32e a1, a9, -12 /* save a1 to call[j+1]'s stack frame */ - s32e a2, a9, -8 /* save a2 to call[j+1]'s stack frame */ - s32e a3, a9, -4 /* save a3 to call[j+1]'s stack frame */ - s32e a4, a0, -32 /* save a4 to call[j]'s stack frame */ - s32e a5, a0, -28 /* save a5 to call[j]'s stack frame */ - s32e a6, a0, -24 /* save a6 to call[j]'s stack frame */ - s32e a7, a0, -20 /* save a7 to call[j]'s stack frame */ - rfwo /* rotates back to call[i] position */ - -/* --------------------------------------------------------------------------------- -Window Underflow Exception for Call8 - -Invoked by RETW returning from call[i+1] to call[i] -where call[i]'s registers must be reloaded (not live in ARs); -where call[i] had done a call8 to call[i+1]. -On entry here: - window rotated to call[i] start point; - a0-a7 are undefined, must be reloaded with call[i].reg[0..7]; - a8-a15 must be preserved (they are call[i+1].reg[0..7]); - a9 is call[i+1]'s stack pointer. --------------------------------------------------------------------------------- -*/ - - .org 0xC0 - .global _WindowUnderflow8 -_WindowUnderflow8: - - l32e a0, a9, -16 /* restore a0 from call[i+1]'s stack frame */ - l32e a1, a9, -12 /* restore a1 from call[i+1]'s stack frame */ - l32e a2, a9, -8 /* restore a2 from call[i+1]'s stack frame */ - l32e a7, a1, -12 /* a7 <- call[i-1]'s sp - (used to find end of call[i]'s frame) */ - l32e a3, a9, -4 /* restore a3 from call[i+1]'s stack frame */ - l32e a4, a7, -32 /* restore a4 from call[i]'s stack frame */ - l32e a5, a7, -28 /* restore a5 from call[i]'s stack frame */ - l32e a6, a7, -24 /* restore a6 from call[i]'s stack frame */ - l32e a7, a7, -20 /* restore a7 from call[i]'s stack frame */ - rfwu - -/* --------------------------------------------------------------------------------- -Window Overflow Exception for Call12 - -Invoked if a call[i] referenced a register (a4-a15) -that contains data from ancestor call[j]; -call[j] had done a call12 to call[j+1]. -On entry here: - window rotated to call[j] start point; - a0-a11 are registers to be saved; - a12-a15 must be preserved; - a13 is call[j+1]'s stack pointer. --------------------------------------------------------------------------------- -*/ - - .org 0x100 - .global _WindowOverflow12 -_WindowOverflow12: - - s32e a0, a13, -16 /* save a0 to call[j+1]'s stack frame */ - l32e a0, a1, -12 /* a0 <- call[j-1]'s sp - (used to find end of call[j]'s frame) */ - s32e a1, a13, -12 /* save a1 to call[j+1]'s stack frame */ - s32e a2, a13, -8 /* save a2 to call[j+1]'s stack frame */ - s32e a3, a13, -4 /* save a3 to call[j+1]'s stack frame */ - s32e a4, a0, -48 /* save a4 to end of call[j]'s stack frame */ - s32e a5, a0, -44 /* save a5 to end of call[j]'s stack frame */ - s32e a6, a0, -40 /* save a6 to end of call[j]'s stack frame */ - s32e a7, a0, -36 /* save a7 to end of call[j]'s stack frame */ - s32e a8, a0, -32 /* save a8 to end of call[j]'s stack frame */ - s32e a9, a0, -28 /* save a9 to end of call[j]'s stack frame */ - s32e a10, a0, -24 /* save a10 to end of call[j]'s stack frame */ - s32e a11, a0, -20 /* save a11 to end of call[j]'s stack frame */ - rfwo /* rotates back to call[i] position */ - -/* --------------------------------------------------------------------------------- -Window Underflow Exception for Call12 - -Invoked by RETW returning from call[i+1] to call[i] -where call[i]'s registers must be reloaded (not live in ARs); -where call[i] had done a call12 to call[i+1]. -On entry here: - window rotated to call[i] start point; - a0-a11 are undefined, must be reloaded with call[i].reg[0..11]; - a12-a15 must be preserved (they are call[i+1].reg[0..3]); - a13 is call[i+1]'s stack pointer. --------------------------------------------------------------------------------- -*/ - - .org 0x140 - .global _WindowUnderflow12 -_WindowUnderflow12: - - l32e a0, a13, -16 /* restore a0 from call[i+1]'s stack frame */ - l32e a1, a13, -12 /* restore a1 from call[i+1]'s stack frame */ - l32e a2, a13, -8 /* restore a2 from call[i+1]'s stack frame */ - l32e a11, a1, -12 /* a11 <- call[i-1]'s sp - (used to find end of call[i]'s frame) */ - l32e a3, a13, -4 /* restore a3 from call[i+1]'s stack frame */ - l32e a4, a11, -48 /* restore a4 from end of call[i]'s stack frame */ - l32e a5, a11, -44 /* restore a5 from end of call[i]'s stack frame */ - l32e a6, a11, -40 /* restore a6 from end of call[i]'s stack frame */ - l32e a7, a11, -36 /* restore a7 from end of call[i]'s stack frame */ - l32e a8, a11, -32 /* restore a8 from end of call[i]'s stack frame */ - l32e a9, a11, -28 /* restore a9 from end of call[i]'s stack frame */ - l32e a10, a11, -24 /* restore a10 from end of call[i]'s stack frame */ - l32e a11, a11, -20 /* restore a11 from end of call[i]'s stack frame */ - rfwu - -#endif /* XCHAL_HAVE_WINDOWED */ - - .section .UserEnter.text, "ax" - .global call_user_start - .type call_user_start,@function - .align 4 - .literal_position - - -call_user_start: - - movi a2, 0x40040000 /* note: absolute symbol, not a ptr */ - wsr a2, vecbase - call0 user_start /* user exception handler */ From 74a658c76596eed678f7595867606d2a23ca38ad Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Wed, 26 Oct 2016 12:25:53 +0800 Subject: [PATCH 060/149] nvs: fix memory leaks in HashList and nvs_close Fixes TW8162. Associated test case is run under Instruments on macOS, until I set up valgrind to test this automatically on Linux. --- components/nvs_flash/src/nvs_api.cpp | 1 + .../nvs_flash/src/nvs_item_hash_list.cpp | 11 ++++++++++- .../nvs_flash/src/nvs_item_hash_list.hpp | 10 ++++++++-- components/nvs_flash/src/nvs_page.cpp | 2 +- components/nvs_flash/test/test_nvs.cpp | 18 ++++++++++++++++++ 5 files changed, 38 insertions(+), 4 deletions(-) diff --git a/components/nvs_flash/src/nvs_api.cpp b/components/nvs_flash/src/nvs_api.cpp index 0a56925c0..c1a910260 100644 --- a/components/nvs_flash/src/nvs_api.cpp +++ b/components/nvs_flash/src/nvs_api.cpp @@ -116,6 +116,7 @@ extern "C" void nvs_close(nvs_handle handle) return; } s_nvs_handles.erase(it); + delete static_cast(it); } extern "C" esp_err_t nvs_erase_key(nvs_handle handle, const char* key) diff --git a/components/nvs_flash/src/nvs_item_hash_list.cpp b/components/nvs_flash/src/nvs_item_hash_list.cpp index 7fa019dff..cf48477d6 100644 --- a/components/nvs_flash/src/nvs_item_hash_list.cpp +++ b/components/nvs_flash/src/nvs_item_hash_list.cpp @@ -17,7 +17,11 @@ namespace nvs { -HashList::~HashList() +HashList::HashList() +{ +} + +void HashList::clear() { for (auto it = mBlockList.begin(); it != mBlockList.end();) { auto tmp = it; @@ -26,6 +30,11 @@ HashList::~HashList() delete static_cast(tmp); } } + +HashList::~HashList() +{ + clear(); +} HashList::HashListBlock::HashListBlock() { diff --git a/components/nvs_flash/src/nvs_item_hash_list.hpp b/components/nvs_flash/src/nvs_item_hash_list.hpp index b40a53d61..3f8dcc850 100644 --- a/components/nvs_flash/src/nvs_item_hash_list.hpp +++ b/components/nvs_flash/src/nvs_item_hash_list.hpp @@ -25,11 +25,18 @@ namespace nvs class HashList { public: + HashList(); ~HashList(); + void insert(const Item& item, size_t index); void erase(const size_t index); size_t find(size_t start, const Item& item); - + void clear(); + +private: + HashList(const HashList& other); + const HashList& operator= (const HashList& rhs); + protected: struct HashListNode { @@ -57,7 +64,6 @@ protected: HashListNode mNodes[ENTRY_COUNT]; }; - typedef intrusive_list TBlockList; TBlockList mBlockList; }; // class HashList diff --git a/components/nvs_flash/src/nvs_page.cpp b/components/nvs_flash/src/nvs_page.cpp index f4fc5430c..64f7bd985 100644 --- a/components/nvs_flash/src/nvs_page.cpp +++ b/components/nvs_flash/src/nvs_page.cpp @@ -741,7 +741,7 @@ esp_err_t Page::erase() mFirstUsedEntry = INVALID_ENTRY; mNextFreeEntry = INVALID_ENTRY; mState = PageState::UNINITIALIZED; - mHashList = HashList(); + mHashList.clear(); return ESP_OK; } diff --git a/components/nvs_flash/test/test_nvs.cpp b/components/nvs_flash/test/test_nvs.cpp index 3db9b45ae..ce552578d 100644 --- a/components/nvs_flash/test/test_nvs.cpp +++ b/components/nvs_flash/test/test_nvs.cpp @@ -933,6 +933,24 @@ TEST_CASE("test recovery from sudden poweroff", "[.][long][nvs][recovery][monkey } } +TEST_CASE("test for memory leaks in open/set", "[leaks]") +{ + SpiFlashEmulator emu(10); + const uint32_t NVS_FLASH_SECTOR = 6; + const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; + emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN); + TEST_ESP_OK(nvs_flash_init_custom(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN)); + + for (int i = 0; i < 100000; ++i) { + nvs_handle light_handle = 0; + char lightbulb[1024] = {12, 13, 14, 15, 16}; + TEST_ESP_OK(nvs_open("light", NVS_READWRITE, &light_handle)); + TEST_ESP_OK(nvs_set_blob(light_handle, "key", lightbulb, sizeof(lightbulb))); + TEST_ESP_OK(nvs_commit(light_handle)); + nvs_close(light_handle); + } +} + TEST_CASE("dump all performance data", "[nvs]") { std::cout << "====================" << std::endl << "Dumping benchmarks" << std::endl; From 612aaa69e4927baa9ce413237006bb5123361954 Mon Sep 17 00:00:00 2001 From: liuzhifu Date: Wed, 26 Oct 2016 13:23:35 +0800 Subject: [PATCH 061/149] lwip/esp32: move the extern wifi calls into esp_wifi_internal.h 1. Add esp_wifi_internal.h 2. Rename system_pp_recycle_rx_pkt to esp_wifi_internal_free_rx_buffer 3. rename esp_wifi_tx_is_stop to esp_wifi_internal_tx_is_stop 4. rename ieee80211_output to esp_wifi_internal_tx --- components/esp32/include/esp_wifi_internal.h | 80 +++++++++++++++++++ components/lwip/api/sockets.c | 8 +- components/lwip/core/pbuf.c | 4 +- .../lwip/include/lwip/port/netif/wlanif.h | 4 +- components/lwip/port/netif/wlanif.c | 4 +- 5 files changed, 89 insertions(+), 11 deletions(-) create mode 100644 components/esp32/include/esp_wifi_internal.h diff --git a/components/esp32/include/esp_wifi_internal.h b/components/esp32/include/esp_wifi_internal.h new file mode 100644 index 000000000..5100010e5 --- /dev/null +++ b/components/esp32/include/esp_wifi_internal.h @@ -0,0 +1,80 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/* + * All the APIs declared here are internal only APIs, it can only be used by + * espressif internal modules, such as SSC, LWIP, TCPIP adapter etc, espressif + * customers are not recommended to use them. + * + * If someone really want to use specified APIs declared in here, please contact + * espressif AE/developer to make sure you know the limitations or risk of + * the API, otherwise you may get unexpected behavior!!! + * + */ + + +#ifndef __ESP_WIFI_INTERNAL_H__ +#define __ESP_WIFI_INTERNAL_H__ + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "rom/queue.h" +#include "esp_err.h" +#include "esp_wifi_types.h" +#include "esp_event.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief get whether the wifi driver is allowed to transmit data or not + * + * @param none + * + * @return true : upper layer should stop to transmit data to wifi driver + * @return false : upper layer can transmit data to wifi driver + */ +bool esp_wifi_internal_tx_is_stop(void); + +/** + * @brief free the rx buffer which allocated by wifi driver + * + * @param void* buffer: rx buffer pointer + * + * @return nonoe + */ +void esp_wifi_internal_free_rx_buffer(void* buffer); + +/** + * @brief free the rx buffer which allocated by wifi driver + * + * @attention1 TODO should modify the return type from bool to int + * + * @param wifi_interface_t wifi_if : wifi interface id + * @param void *buffer : the buffer to be tansmit + * @param u16_t len : the length of buffer + * + * @return True : success transmit the buffer to wifi driver + * False : failed to transmit the buffer to wifi driver + */ +bool esp_wifi_internal_tx(wifi_interface_t wifi_if, void *buffer, u16_t len); + +#ifdef __cplusplus +} +#endif + +#endif /* __ESP_WIFI_H__ */ diff --git a/components/lwip/api/sockets.c b/components/lwip/api/sockets.c index 350847b57..47f25bb07 100755 --- a/components/lwip/api/sockets.c +++ b/components/lwip/api/sockets.c @@ -388,10 +388,8 @@ static void lwip_socket_drop_registered_memberships(int s); #endif /* LWIP_IGMP */ #ifdef LWIP_ESP8266 - -/* Since esp_wifi_tx_is_stop/system_get_free_heap_size are not an public wifi API, so extern them here*/ -extern size_t system_get_free_heap_size(void); -extern bool esp_wifi_tx_is_stop(void); +#include "esp_wifi_internal.h" +#include "esp_system.h" /* Please be notified that this flow control is just a workaround for fixing wifi Q full issue. * Under UDP/TCP pressure test, we found that the sockets may cause wifi tx queue full if the socket @@ -404,7 +402,7 @@ static inline void esp32_tx_flow_ctrl(void) { uint8_t _wait_delay = 0; - while ((system_get_free_heap_size() < HEAP_HIGHWAT) || esp_wifi_tx_is_stop()){ + while ((system_get_free_heap_size() < HEAP_HIGHWAT) || esp_wifi_internal_tx_is_stop()){ vTaskDelay(_wait_delay/portTICK_RATE_MS); if (_wait_delay < 64) _wait_delay *= 2; } diff --git a/components/lwip/core/pbuf.c b/components/lwip/core/pbuf.c index e35f8a6b7..044d765cd 100755 --- a/components/lwip/core/pbuf.c +++ b/components/lwip/core/pbuf.c @@ -83,6 +83,7 @@ static const char mem_debug_file[] ICACHE_RODATA_ATTR STORE_ATTR = __FILE__; #endif #ifdef LWIP_ESP8266 +#include "esp_wifi_internal.h" #define EP_OFFSET 0 #endif @@ -764,8 +765,7 @@ pbuf_free(struct pbuf *p) } else if (type == PBUF_ROM || type == PBUF_REF) { #ifdef LWIP_ESP8266 - extern void system_pp_recycle_rx_pkt(void*); - if (type == PBUF_REF && p->eb != NULL ) system_pp_recycle_rx_pkt(p->eb); + if (type == PBUF_REF && p->eb != NULL ) esp_wifi_internal_free_rx_buffer(p->eb); #endif memp_free(MEMP_PBUF, p); diff --git a/components/lwip/include/lwip/port/netif/wlanif.h b/components/lwip/include/lwip/port/netif/wlanif.h index 7eb303eab..c6f7831b3 100755 --- a/components/lwip/include/lwip/port/netif/wlanif.h +++ b/components/lwip/include/lwip/port/netif/wlanif.h @@ -8,6 +8,8 @@ #include "esp_wifi.h" +#include "esp_wifi_internal.h" + #include "lwip/err.h" #ifdef __cplusplus @@ -18,8 +20,6 @@ err_t wlanif_init(struct netif *netif); void wlanif_input(struct netif *netif, void *buffer, u16_t len, void* eb); -bool ieee80211_output(wifi_interface_t wifi_if, void *buffer, u16_t len); - wifi_interface_t wifi_get_interface(void *dev); void netif_reg_addr_change_cb(void* cb); diff --git a/components/lwip/port/netif/wlanif.c b/components/lwip/port/netif/wlanif.c index 9832c41af..0b4fe70ba 100755 --- a/components/lwip/port/netif/wlanif.c +++ b/components/lwip/port/netif/wlanif.c @@ -150,12 +150,12 @@ low_level_output(struct netif *netif, struct pbuf *p) } } - ieee80211_output(wifi_if, q->payload, pbuf_x_len); + esp_wifi_internal_tx(wifi_if, q->payload, pbuf_x_len); return ERR_OK; #else for(q = p; q != NULL; q = q->next) { - ieee80211_output(wifi_if, q->payload, q->len); + esp_wifi_internal_tx(wifi_if, q->payload, q->len); } #endif From 48301909eba52562f60ad5a1aa8657a9d42709ba Mon Sep 17 00:00:00 2001 From: liuzhifu Date: Wed, 26 Oct 2016 13:53:00 +0800 Subject: [PATCH 062/149] components/esp32: update wifi lib 6ce01d76: rename some wifi internal APIs aa4d2aa9: last rx buffer is reserved for mgmt frame bb0ff4a8: tw7775 fix assert when rx auth frame before create bss --- components/esp32/lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp32/lib b/components/esp32/lib index a1e5f8b95..d3920845c 160000 --- a/components/esp32/lib +++ b/components/esp32/lib @@ -1 +1 @@ -Subproject commit a1e5f8b953c7934677ba7a6ed0a6dd2da0e6bd0f +Subproject commit d3920845c9501f6ebae178186c3f63a80fab1ed1 From a90217e201a2340ddfb3b5adc2c29c777b8bfd82 Mon Sep 17 00:00:00 2001 From: liuzhifu Date: Wed, 26 Oct 2016 14:09:54 +0800 Subject: [PATCH 063/149] components esp32/lwip: modify code according to review comments 1. Modify comments for esp_wifi_internal_tx 2. Fix delay time error in esp32_tx_flow_ctrl which is found in code review, modify _wait_delay init value from 0 to 1 --- components/esp32/include/esp_wifi_internal.h | 2 +- components/lwip/api/sockets.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/esp32/include/esp_wifi_internal.h b/components/esp32/include/esp_wifi_internal.h index 5100010e5..217d5f6d1 100644 --- a/components/esp32/include/esp_wifi_internal.h +++ b/components/esp32/include/esp_wifi_internal.h @@ -60,7 +60,7 @@ bool esp_wifi_internal_tx_is_stop(void); void esp_wifi_internal_free_rx_buffer(void* buffer); /** - * @brief free the rx buffer which allocated by wifi driver + * @brief transmit the buffer via wifi driver * * @attention1 TODO should modify the return type from bool to int * diff --git a/components/lwip/api/sockets.c b/components/lwip/api/sockets.c index 47f25bb07..487bec8e8 100755 --- a/components/lwip/api/sockets.c +++ b/components/lwip/api/sockets.c @@ -400,7 +400,7 @@ static void lwip_socket_drop_registered_memberships(int s); */ static inline void esp32_tx_flow_ctrl(void) { - uint8_t _wait_delay = 0; + uint8_t _wait_delay = 1; while ((system_get_free_heap_size() < HEAP_HIGHWAT) || esp_wifi_internal_tx_is_stop()){ vTaskDelay(_wait_delay/portTICK_RATE_MS); From 9546ad5b5ebaef3230c73b6fc248f1dbad1138d0 Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Wed, 26 Oct 2016 14:54:50 +0800 Subject: [PATCH 064/149] Move write key and stage action select constants into headers --- components/esp32/include/soc/rtc_cntl_reg.h | 3 ++- components/esp32/include/soc/timer_group_reg.h | 10 +++++++++- components/esp32/int_wdt.c | 16 ++++++++-------- components/esp32/panic.c | 10 +++++----- components/esp32/task_wdt.c | 16 ++++++++-------- 5 files changed, 32 insertions(+), 23 deletions(-) diff --git a/components/esp32/include/soc/rtc_cntl_reg.h b/components/esp32/include/soc/rtc_cntl_reg.h index 24e0f1403..bb4e2afce 100644 --- a/components/esp32/include/soc/rtc_cntl_reg.h +++ b/components/esp32/include/soc/rtc_cntl_reg.h @@ -14,7 +14,8 @@ #ifndef _SOC_RTC_CNTL_REG_H_ #define _SOC_RTC_CNTL_REG_H_ -#define WDT_WRITE_KEY 0x50D83AA1 +/* The value that needs to be written to RTC_CNTL_WDT_WKEY to write-enable the wdt registers */ +#define RTC_CNTL_WDT_WKEY_VALUE 0x50D83AA1 #include "soc.h" diff --git a/components/esp32/include/soc/timer_group_reg.h b/components/esp32/include/soc/timer_group_reg.h index 0d67cab51..2db2a7e3f 100644 --- a/components/esp32/include/soc/timer_group_reg.h +++ b/components/esp32/include/soc/timer_group_reg.h @@ -15,7 +15,15 @@ #define __TIMG_REG_H__ #include "soc.h" -#define WDT_WRITE_KEY 0x50D83AA1 +/* The value that needs to be written to TIMG_WDT_WKEY to write-enable the wdt registers */ +#define TIMG_WDT_WKEY_VALUE 0x50D83AA1 + +/* Possible values for TIMG_WDT_STGx */ +#define TIMG_WDT_STG_SEL_OFF 0 +#define TIMG_WDT_STG_SEL_INT 1 +#define TIMG_WDT_STG_SEL_RESET_CPU 2 +#define TIMG_WDT_STG_SEL_RESET_SYSTEM 3 + #define REG_TIMG_BASE(i) (DR_REG_TIMERGROUP0_BASE + i*0x1000) #define TIMG_T0CONFIG_REG(i) (REG_TIMG_BASE(i) + 0x0000) diff --git a/components/esp32/int_wdt.c b/components/esp32/int_wdt.c index 5f123ee36..93a4d9fe6 100644 --- a/components/esp32/int_wdt.c +++ b/components/esp32/int_wdt.c @@ -36,13 +36,13 @@ void esp_int_wdt_init() { - TIMERG1.wdt_wprotect=WDT_WRITE_KEY; - TIMERG1.wdt_config0.sys_reset_length=7; //3.2uS - TIMERG1.wdt_config0.cpu_reset_length=7; //3.2uS + TIMERG1.wdt_wprotect=TIMG_WDT_WKEY_VALUE; + TIMERG1.wdt_config0.sys_reset_length=7; //3.2uS + TIMERG1.wdt_config0.cpu_reset_length=7; //3.2uS TIMERG1.wdt_config0.level_int_en=1; - TIMERG1.wdt_config0.stg0=1; //1st stage timeout: interrupt - TIMERG1.wdt_config0.stg1=3; //2nd stage timeout: reset system - TIMERG1.wdt_config1.clk_prescale=80*500; //Prescaler: wdt counts in ticks of 0.5mS + TIMERG1.wdt_config0.stg0=TIMG_WDT_STG_SEL_INT; //1st stage timeout: interrupt + TIMERG1.wdt_config0.stg1=TIMG_WDT_STG_SEL_RESET_SYSTEM; //2nd stage timeout: reset system + TIMERG1.wdt_config1.clk_prescale=80*500; //Prescaler: wdt counts in ticks of 0.5mS //The timer configs initially are set to 5 seconds, to make sure the CPU can start up. The tick hook sets //it to their actual value. TIMERG1.wdt_config2=10000; @@ -72,7 +72,7 @@ void vApplicationTickHook(void) { } else { //Only feed wdt if app cpu also ticked. if (int_wdt_app_cpu_ticked) { - TIMERG1.wdt_wprotect=WDT_WRITE_KEY; + TIMERG1.wdt_wprotect=TIMG_WDT_WKEY_VALUE; TIMERG1.wdt_config2=CONFIG_INT_WDT_TIMEOUT_MS*2; //Set timeout before interrupt TIMERG1.wdt_config3=CONFIG_INT_WDT_TIMEOUT_MS*4; //Set timeout before reset TIMERG1.wdt_feed=1; @@ -84,7 +84,7 @@ void vApplicationTickHook(void) { #else void vApplicationTickHook(void) { if (xPortGetCoreID()!=0) return; - TIMERG1.wdt_wprotect=WDT_WRITE_KEY; + TIMERG1.wdt_wprotect=TIMG_WDT_WKEY_VALUE; TIMERG1.wdt_config2=CONFIG_INT_WDT_TIMEOUT_MS*2; //Set timeout before interrupt TIMERG1.wdt_config3=CONFIG_INT_WDT_TIMEOUT_MS*4; //Set timeout before reset TIMERG1.wdt_feed=1; diff --git a/components/esp32/panic.c b/components/esp32/panic.c index 758d581d0..274364008 100644 --- a/components/esp32/panic.c +++ b/components/esp32/panic.c @@ -203,17 +203,17 @@ all watchdogs except the timer group 0 watchdog, and it reconfigures that to res one second. */ static void reconfigureAllWdts() { - TIMERG0.wdt_wprotect=WDT_WRITE_KEY; + TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; TIMERG0.wdt_feed=1; TIMERG0.wdt_config0.sys_reset_length=7; //3.2uS TIMERG0.wdt_config0.cpu_reset_length=7; //3.2uS - TIMERG0.wdt_config0.stg0=3; //1st stage timeout: reset system + TIMERG0.wdt_config0.stg0=TIMG_WDT_STG_SEL_RESET_SYSTEM; //1st stage timeout: reset system TIMERG0.wdt_config1.clk_prescale=80*500; //Prescaler: wdt counts in ticks of 0.5mS TIMERG0.wdt_config2=2000; //1 second before reset TIMERG0.wdt_config0.en=1; TIMERG0.wdt_wprotect=0; //Disable wdt 1 - TIMERG1.wdt_wprotect=WDT_WRITE_KEY; + TIMERG1.wdt_wprotect=TIMG_WDT_WKEY_VALUE; TIMERG1.wdt_config0.en=0; TIMERG1.wdt_wprotect=0; } @@ -222,10 +222,10 @@ static void reconfigureAllWdts() { This disables all the watchdogs for when we call the gdbstub. */ static void disableAllWdts() { - TIMERG0.wdt_wprotect=WDT_WRITE_KEY; + TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; TIMERG0.wdt_config0.en=0; TIMERG0.wdt_wprotect=0; - TIMERG1.wdt_wprotect=WDT_WRITE_KEY; + TIMERG1.wdt_wprotect=TIMG_WDT_WKEY_VALUE; TIMERG1.wdt_config0.en=0; TIMERG0.wdt_wprotect=0; } diff --git a/components/esp32/task_wdt.c b/components/esp32/task_wdt.c index 24d3977b1..bec1cadaa 100644 --- a/components/esp32/task_wdt.c +++ b/components/esp32/task_wdt.c @@ -49,7 +49,7 @@ static void IRAM_ATTR task_wdt_isr(void *arg) { wdt_task_t *wdttask; const char *cpu; //Feed the watchdog so we do not reset - TIMERG0.wdt_wprotect=WDT_WRITE_KEY; + TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; TIMERG0.wdt_feed=1; TIMERG0.wdt_wprotect=0; //Ack interrupt @@ -107,7 +107,7 @@ void esp_task_wdt_feed() { } if (do_feed_wdt) { //All tasks have checked in; time to feed the hw watchdog. - TIMERG0.wdt_wprotect=WDT_WRITE_KEY; + TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; TIMERG0.wdt_feed=1; TIMERG0.wdt_wprotect=0; //Reset fed_watchdog status @@ -141,13 +141,13 @@ void esp_task_wdt_delete() { } void esp_task_wdt_init() { - TIMERG0.wdt_wprotect=WDT_WRITE_KEY; - TIMERG0.wdt_config0.sys_reset_length=7; //3.2uS - TIMERG0.wdt_config0.cpu_reset_length=7; //3.2uS + TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; + TIMERG0.wdt_config0.sys_reset_length=7; //3.2uS + TIMERG0.wdt_config0.cpu_reset_length=7; //3.2uS TIMERG0.wdt_config0.level_int_en=1; - TIMERG0.wdt_config0.stg0=1; //1st stage timeout: interrupt - TIMERG0.wdt_config0.stg1=3; //2nd stage timeout: reset system - TIMERG0.wdt_config1.clk_prescale=80*500; //Prescaler: wdt counts in ticks of 0.5mS + TIMERG0.wdt_config0.stg0=TIMG_WDT_STG_SEL_INT; //1st stage timeout: interrupt + TIMERG0.wdt_config0.stg1=TIMG_WDT_STG_SEL_RESET_SYSTEM; //2nd stage timeout: reset system + TIMERG0.wdt_config1.clk_prescale=80*500; //Prescaler: wdt counts in ticks of 0.5mS TIMERG0.wdt_config2=CONFIG_TASK_WDT_TIMEOUT_S*2000; //Set timeout before interrupt TIMERG0.wdt_config3=CONFIG_TASK_WDT_TIMEOUT_S*4000; //Set timeout before reset TIMERG0.wdt_config0.en=1; From 3cca62dfa46bc0888b45e7a848657f0387e386dc Mon Sep 17 00:00:00 2001 From: liuzhifu Date: Wed, 26 Oct 2016 18:16:40 +0800 Subject: [PATCH 065/149] esp32/tcpip_adapter: refractor for some wifi APIs 1. Modify esp_wifi_get_station_list to esp_wifi_ap_get_sta_list 2. Modify tcpip_adapter_get_station_list to tcpip_adapter_get_sta_list 3. Remove esp_wifi_free_station_list 4. Remove tcpip_adapter_free_station_list 5. Modify related data struct accordingly --- components/esp32/include/esp_wifi.h | 7 ++-- components/esp32/include/esp_wifi_types.h | 13 ++++-- .../tcpip_adapter/include/tcpip_adapter.h | 28 +++++-------- components/tcpip_adapter/tcpip_adapter_lwip.c | 42 ++++--------------- 4 files changed, 29 insertions(+), 61 deletions(-) diff --git a/components/esp32/include/esp_wifi.h b/components/esp32/include/esp_wifi.h index 12378f334..3898bfec7 100644 --- a/components/esp32/include/esp_wifi.h +++ b/components/esp32/include/esp_wifi.h @@ -198,7 +198,7 @@ esp_err_t esp_wifi_clear_fast_connect(void); * @return ESP_OK : succeed * @return others : fail */ -esp_err_t esp_wifi_kick_station(uint16_t aid); +esp_err_t esp_wifi_kick_sta(uint16_t aid); /** * @brief Scan all available APs. @@ -471,14 +471,13 @@ esp_err_t esp_wifi_get_config(wifi_interface_t ifx, wifi_config_t *conf); * * @attention SSC only API * - * @param struct station_info **station : station list + * @param wifi_sta_list_t *sta: sta list * * @return ESP_OK : succeed * @return others : fail */ -esp_err_t esp_wifi_get_station_list(struct station_info **station); +esp_err_t esp_wifi_ap_get_sta_list(wifi_sta_list_t *sta); -esp_err_t esp_wifi_free_station_list(void); /** * @brief Set the WiFi API configuration storage type diff --git a/components/esp32/include/esp_wifi_types.h b/components/esp32/include/esp_wifi_types.h index b3474769e..a607ad9e9 100644 --- a/components/esp32/include/esp_wifi_types.h +++ b/components/esp32/include/esp_wifi_types.h @@ -150,10 +150,15 @@ typedef union { wifi_sta_config_t sta; /**< configuration of STA */ } wifi_config_t; -struct station_info { - STAILQ_ENTRY(station_info) next; - uint8_t bssid[6]; -}; +typedef struct { + uint8_t mac[6]; /**< mac address of sta that associated with ESP32 soft-AP */ +}wifi_sta_info_t; + +#define ESP_WIFI_MAX_CONN_NUM 8 /**< max number of sta the eSP32 soft-AP can connect */ +typedef struct { + wifi_sta_info_t sta[ESP_WIFI_MAX_CONN_NUM+2]; /**< sta list */ + uint8_t num; /**< number of sta that associated with ESP32 soft-AP */ +}wifi_sta_list_t; typedef enum { WIFI_STORAGE_FLASH, /**< all configuration will strore in both memory and flash */ diff --git a/components/tcpip_adapter/include/tcpip_adapter.h b/components/tcpip_adapter/include/tcpip_adapter.h index 5b0fc4c62..bbcf33727 100644 --- a/components/tcpip_adapter/include/tcpip_adapter.h +++ b/components/tcpip_adapter/include/tcpip_adapter.h @@ -67,11 +67,15 @@ typedef struct { typedef dhcps_lease_t tcpip_adapter_dhcps_lease_t; #if CONFIG_DHCP_STA_LIST -struct station_list { - STAILQ_ENTRY(station_list) next; +typedef struct { uint8_t mac[6]; ip4_addr_t ip; -}; +}tcpip_adapter_sta_info_t; + +typedef struct { + tcpip_adapter_sta_info_t sta[ESP_WIFI_MAX_CONN_NUM+2]; + uint8_t num; +}tcpip_adapter_sta_list_t; #endif #endif @@ -359,26 +363,14 @@ wifi_interface_t tcpip_adapter_get_wifi_if(void *dev); /** * @brief Get the station information list * - * @note This function should be called in AP mode and dhcp server started, and the list should - * be by using tcpip_adapter_free_sta_list. - * - * @param[in] sta_info: station information - * @param[out] sta_list: station information list + * @param[in] wifi_sta_list_t *wifi_sta_list: sta list info + * @param[out] tcpip_adapter_sta_list_t *tcpip_sta_list: sta list info * * @return ESP_OK * ESP_ERR_TCPIP_ADAPTER_NO_MEM * ESP_ERR_TCPIP_ADAPTER_INVALID_PARAMS */ -esp_err_t tcpip_adapter_get_sta_list(struct station_info *sta_info, struct station_list **sta_list); - -/** - * @brief Free the station information list's memory - * - * @param sta_list: station information list - * - * @return ESP_OK - */ -esp_err_t tcpip_adapter_free_sta_list(struct station_list *sta_list); +esp_err_t tcpip_adapter_get_sta_list(wifi_sta_list_t *wifi_sta_list, tcpip_adapter_sta_list_t *tcpip_sta_list); #ifdef __cplusplus } diff --git a/components/tcpip_adapter/tcpip_adapter_lwip.c b/components/tcpip_adapter/tcpip_adapter_lwip.c index 12cf05f95..677368008 100644 --- a/components/tcpip_adapter/tcpip_adapter_lwip.c +++ b/components/tcpip_adapter/tcpip_adapter_lwip.c @@ -590,45 +590,17 @@ wifi_interface_t tcpip_adapter_get_wifi_if(void *dev) return WIFI_IF_MAX; } -esp_err_t tcpip_adapter_get_sta_list(struct station_info *sta_info, struct station_list **sta_list) +esp_err_t tcpip_adapter_get_sta_list(wifi_sta_list_t *wifi_sta_list, tcpip_adapter_sta_list_t *tcpip_sta_list) { - struct station_info *info = sta_info; - struct station_list *list; - STAILQ_HEAD(, station_list) list_head; + int i; - if (sta_list == NULL) + if ((wifi_sta_list == NULL) || (tcpip_sta_list == NULL)) return ESP_ERR_TCPIP_ADAPTER_INVALID_PARAMS; - STAILQ_INIT(&list_head); - - while (info != NULL) { - list = (struct station_list *)malloc(sizeof(struct station_list)); - memset(list, 0, sizeof (struct station_list)); - - if (list == NULL) { - return ESP_ERR_TCPIP_ADAPTER_NO_MEM; - } - - memcpy(list->mac, info->bssid, 6); - dhcp_search_ip_on_mac(list->mac, &list->ip); - STAILQ_INSERT_TAIL(&list_head, list, next); - - info = STAILQ_NEXT(info, next); - } - - *sta_list = STAILQ_FIRST(&list_head); - - return ESP_OK; -} - -esp_err_t tcpip_adapter_free_sta_list(struct station_list *sta_list) -{ - struct station_list *list = sta_list; - - while (sta_list != NULL) { - list = sta_list; - sta_list = STAILQ_NEXT(sta_list, next); - free(list); + memset(tcpip_sta_list, 0, sizeof(tcpip_adapter_sta_list_t)); + for (i=0; inum; i++){ + memcpy(tcpip_sta_list->sta[i].mac, wifi_sta_list->sta[i].mac, 6); + dhcp_search_ip_on_mac(tcpip_sta_list->sta[i].mac, &tcpip_sta_list->sta[i].ip); } return ESP_OK; From 345cf333a89b2e1f9d5383e5b66b6b923afb20b1 Mon Sep 17 00:00:00 2001 From: liuzhifu Date: Wed, 26 Oct 2016 18:18:58 +0800 Subject: [PATCH 066/149] components/esp32: udpate wifi lib 1. cc5a5e29 - refractor for some wifi APIs 2. 8d787147 - move soft wdt to idf --- components/esp32/lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp32/lib b/components/esp32/lib index d3920845c..0ee0776b5 160000 --- a/components/esp32/lib +++ b/components/esp32/lib @@ -1 +1 @@ -Subproject commit d3920845c9501f6ebae178186c3f63a80fab1ed1 +Subproject commit 0ee0776b539db3e42a06a5b1ff8e4ea102f8630b From 750d6faf512184803bd9016ea612b4ecf99e7bc8 Mon Sep 17 00:00:00 2001 From: liuzhifu Date: Wed, 26 Oct 2016 20:02:39 +0800 Subject: [PATCH 067/149] esp32/tcpip_adapter: rework according to review comments 1. Modify sta to station in comments 2. Modify esp_wifi_get_ap_num to esp_wifi_scan_get_ap_num 3. Modify esp_wifi_get_ap_list to esp_wifi_scan_get_ap_records --- components/esp32/include/esp_wifi.h | 10 +++++----- components/esp32/include/esp_wifi_types.h | 8 ++++---- components/esp32/lib | 2 +- components/tcpip_adapter/include/tcpip_adapter.h | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/components/esp32/include/esp_wifi.h b/components/esp32/include/esp_wifi.h index 3898bfec7..6fc9d896a 100644 --- a/components/esp32/include/esp_wifi.h +++ b/components/esp32/include/esp_wifi.h @@ -235,19 +235,19 @@ esp_err_t esp_wifi_scan_stop(void); * @return ESP_OK : succeed * @return others : fail */ -esp_err_t esp_wifi_get_ap_num(uint16_t *number); +esp_err_t esp_wifi_scan_get_ap_num(uint16_t *number); /** * @brief Get AP list found in last scan * - * @param uint16_t *number : as input param, it stores max AP number ap_list can hold, as output param, it store + * @param uint16_t *number : as input param, it stores max AP number ap_records can hold, as output param, it store the actual AP number this API returns - * @param wifi_ap_list_t *ap_list : a list to hold the found APs + * @param wifi_ap_record_t *ap_records: an wifi_ap_record_t array to hold the found APs * * @return ESP_OK : succeed * @return others : fail */ -esp_err_t esp_wifi_get_ap_list(uint16_t *number, wifi_ap_list_t *ap_list); +esp_err_t esp_wifi_scan_get_ap_records(uint16_t *number, wifi_ap_record_t *ap_records); /** * @brief Set current power save type @@ -471,7 +471,7 @@ esp_err_t esp_wifi_get_config(wifi_interface_t ifx, wifi_config_t *conf); * * @attention SSC only API * - * @param wifi_sta_list_t *sta: sta list + * @param wifi_sta_list_t *sta: station list * * @return ESP_OK : succeed * @return others : fail diff --git a/components/esp32/include/esp_wifi_types.h b/components/esp32/include/esp_wifi_types.h index a607ad9e9..0ea719a65 100644 --- a/components/esp32/include/esp_wifi_types.h +++ b/components/esp32/include/esp_wifi_types.h @@ -109,7 +109,7 @@ typedef struct { wifi_second_chan_t second; /**< second channel of AP */ int8_t rssi; /**< signal strength of AP */ wifi_auth_mode_t authmode; /**< authmode of AP */ -} wifi_ap_list_t; +} wifi_ap_record_t; typedef enum { WIFI_PS_NONE, /**< No power save */ @@ -154,10 +154,10 @@ typedef struct { uint8_t mac[6]; /**< mac address of sta that associated with ESP32 soft-AP */ }wifi_sta_info_t; -#define ESP_WIFI_MAX_CONN_NUM 8 /**< max number of sta the eSP32 soft-AP can connect */ +#define ESP_WIFI_MAX_CONN_NUM (8+2) /**< max number of sta the eSP32 soft-AP can connect */ typedef struct { - wifi_sta_info_t sta[ESP_WIFI_MAX_CONN_NUM+2]; /**< sta list */ - uint8_t num; /**< number of sta that associated with ESP32 soft-AP */ + wifi_sta_info_t sta[ESP_WIFI_MAX_CONN_NUM]; /**< station list */ + uint8_t num; /**< number of station that associated with ESP32 soft-AP */ }wifi_sta_list_t; typedef enum { diff --git a/components/esp32/lib b/components/esp32/lib index 0ee0776b5..02063e8d4 160000 --- a/components/esp32/lib +++ b/components/esp32/lib @@ -1 +1 @@ -Subproject commit 0ee0776b539db3e42a06a5b1ff8e4ea102f8630b +Subproject commit 02063e8d40f72933622b2eafd78ce968085b0047 diff --git a/components/tcpip_adapter/include/tcpip_adapter.h b/components/tcpip_adapter/include/tcpip_adapter.h index bbcf33727..218325320 100644 --- a/components/tcpip_adapter/include/tcpip_adapter.h +++ b/components/tcpip_adapter/include/tcpip_adapter.h @@ -363,8 +363,8 @@ wifi_interface_t tcpip_adapter_get_wifi_if(void *dev); /** * @brief Get the station information list * - * @param[in] wifi_sta_list_t *wifi_sta_list: sta list info - * @param[out] tcpip_adapter_sta_list_t *tcpip_sta_list: sta list info + * @param[in] wifi_sta_list_t *wifi_sta_list: station list info + * @param[out] tcpip_adapter_sta_list_t *tcpip_sta_list: station list info * * @return ESP_OK * ESP_ERR_TCPIP_ADAPTER_NO_MEM From d3d9a8bc280900ffac9b61a3a1bc8532fa9aa8c6 Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Wed, 26 Oct 2016 21:09:55 +0800 Subject: [PATCH 068/149] Most code written. Interrupts still do not fire yet. --- components/esp32/crosscore_int.c | 107 +++++++++++++ components/esp32/include/esp_crosscore_int.h | 8 + .../freertos/include/freertos/portable.h | 8 + components/freertos/port.c | 8 + components/freertos/tasks.c | 147 ++++++++++++------ 5 files changed, 233 insertions(+), 45 deletions(-) create mode 100644 components/esp32/crosscore_int.c create mode 100644 components/esp32/include/esp_crosscore_int.h diff --git a/components/esp32/crosscore_int.c b/components/esp32/crosscore_int.c new file mode 100644 index 000000000..95f2762f2 --- /dev/null +++ b/components/esp32/crosscore_int.c @@ -0,0 +1,107 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include +#include + +#include "esp_attr.h" +#include "esp_err.h" +#include "esp_intr.h" + +#include "rom/ets_sys.h" +#include "rom/uart.h" + +#include "soc/cpu.h" +#include "soc/dport_reg.h" +#include "soc/io_mux_reg.h" +#include "soc/rtc_cntl_reg.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "freertos/queue.h" +#include "freertos/portmacro.h" + + +#define REASON_YIELD (1<<0) + +static portMUX_TYPE reasonSpinlock = portMUX_INITIALIZER_UNLOCKED; +static volatile uint32_t reason[ portNUM_PROCESSORS ]; + + +/* +ToDo: There is a small chance the CPU already has yielded when this ISR is serviced. In that case, it's running the intended task but +the ISR will cause it to switch _away_ from it. portYIELD_FROM_ISR will probably just schedule the task again, but have to check that. +*/ +static void esp_crosscore_isr(void *arg) { + volatile uint32_t myReasonVal; +#if 0 + //A pointer to the correct reason array item is passed to this ISR. + volatile uint32_t *myReason=arg; +#else + //Does not work yet, the interrupt code needs work to understand two separate interrupt and argument + //tables... + volatile uint32_t *myReason=&reason[xPortGetCoreID()]; +#endif + //Clear the interrupt first. + if (xPortGetCoreID()==0) { + WRITE_PERI_REG(DPORT_CPU_INTR_FROM_CPU_0_REG, 0); + } else { + WRITE_PERI_REG(DPORT_CPU_INTR_FROM_CPU_0_REG, 1); + } + //Grab the reason and clear it. + portENTER_CRITICAL(&reasonSpinlock); + myReasonVal=*myReason; + *myReason=0; + portEXIT_CRITICAL(&reasonSpinlock); + + //Check what we need to do. + if (myReasonVal&REASON_YIELD) { + portYIELD_FROM_ISR(); + } + + ets_printf("recv yield\n"); +} + +//Initialize the crosscore interrupt on this core. Call this once +//on each active core. +void esp_crosscore_int_init() { + portENTER_CRITICAL(&reasonSpinlock); + reason[xPortGetCoreID()]=0; + portEXIT_CRITICAL(&reasonSpinlock); + ESP_INTR_DISABLE(ETS_FROM_CPU_INUM); + if (xPortGetCoreID()==0) { + intr_matrix_set(xPortGetCoreID(), ETS_FROM_CPU_INTR0_SOURCE, ETS_FROM_CPU_INUM); + } else { + intr_matrix_set(xPortGetCoreID(), ETS_FROM_CPU_INTR1_SOURCE, ETS_FROM_CPU_INUM); + } + xt_set_interrupt_handler(ETS_FROM_CPU_INUM, esp_crosscore_isr, (void*)&reason[xPortGetCoreID()]); + ESP_INTR_ENABLE(ETS_FROM_CPU_INUM); + +} + +void esp_crosscore_int_send_yield(int coreId) { + ets_printf("send yield\n"); + assert(coreIduxPriority ] ), &( ( pxTCB )->xGenericListItem ) ) /*-----------------------------------------------------------*/ + +#define tskCAN_RUN_HERE( cpuid ) ( cpuid==xPortGetCoreID() || cpuid==tskNO_AFFINITY ) + /* * Several functions take an TaskHandle_t parameter that can optionally be NULL, * where NULL is used to indicate that the handle of the currently executing @@ -581,6 +584,35 @@ static void prvResetNextTaskUnblockTime( void ); /*-----------------------------------------------------------*/ +/* + * This routine tries to send an interrupt to another core if needed to make it execute a task + * of higher priority. We try to figure out if needed first by inspecting the pxTCB of the + * other CPU first. Specifically for Xtensa, we can do this because pxTCB is an atomic pointer. It + * is possible that it is inaccurate because the other CPU just did a task switch, but in that case + * at most a superfluous interrupt is generated. +*/ +static void taskYIELD_OTHER_CORE( BaseType_t xCoreID, UBaseType_t uxPriority ) +{ + BaseType_t i; + if (xCoreID != tskNO_AFFINITY) { + if ( pxCurrentTCB[ xCoreID ]->uxPriority < uxPriority ) { + vPortYieldOtherCore( xCoreID ); + } + } + else + { + /* The task has no affinity. See if we can find a CPU to put it on.*/ + for (i=0; iuxPriority < uxPriority) + { + vPortYieldOtherCore( i ); + break; + } + } + } +} + + BaseType_t xTaskGenericCreate( TaskFunction_t pxTaskCode, const char * const pcName, const uint16_t usStackDepth, void * const pvParameters, UBaseType_t uxPriority, TaskHandle_t * const pxCreatedTask, StackType_t * const puxStackBuffer, const MemoryRegion_t * const xRegions, const BaseType_t xCoreID) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */ { BaseType_t xReturn; @@ -753,7 +785,10 @@ BaseType_t i; the other processor will keep running the task it's working on, and only switch to the newer task on a timer interrupt. */ //No mux here, uxPriority is mostly atomic and there's not really any harm if this check misfires. - if( pxCurrentTCB[ xPortGetCoreID() ]->uxPriority < uxPriority ) + if( xCoreID != xPortGetCoreID() ) { + taskYIELD_OTHER_CORE(xCoreID, uxPriority); + } + else if( pxCurrentTCB[ xPortGetCoreID() ]->uxPriority < uxPriority ) { taskYIELD_IF_USING_PREEMPTION(); } @@ -834,7 +869,7 @@ BaseType_t i; after which it is not possible to yield away from this task - hence xYieldPending is used to latch that a context switch is required. */ - portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending ); + portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending[xPortGetCoreID()] ); portYIELD_WITHIN_API(); } else @@ -1188,10 +1223,14 @@ BaseType_t i; /* The priority of a task other than the currently running task is being raised. Is the priority being raised above that of the running task? */ - if( uxNewPriority >= pxCurrentTCB[ xPortGetCoreID() ]->uxPriority ) + if ( tskCAN_RUN_HERE(pxTCB->xCoreID) && uxNewPriority >= pxCurrentTCB[ xPortGetCoreID() ]->uxPriority ) { xYieldRequired = pdTRUE; } + else if ( pxTCB->xCoreID != xPortGetCoreID() ) + { + taskYIELD_OTHER_CORE( pxTCB->xCoreID, uxNewPriority ); + } else { mtCOVERAGE_TEST_MARKER(); @@ -1470,13 +1509,17 @@ BaseType_t i; prvAddTaskToReadyList( pxTCB ); /* We may have just resumed a higher priority task. */ - if( pxTCB->uxPriority >= pxCurrentTCB[ xPortGetCoreID() ]->uxPriority ) + if( tskCAN_RUN_HERE(pxTCB->xCoreID) && pxTCB->uxPriority >= pxCurrentTCB[ xPortGetCoreID() ]->uxPriority ) { /* This yield may not cause the task just resumed to run, but will leave the lists in the correct state for the next yield. */ taskYIELD_IF_USING_PREEMPTION_MUX(&xTaskQueueMutex); } + else if( pxTCB->xCoreID != xPortGetCoreID() ) + { + taskYIELD_OTHER_CORE( pxTCB->xCoreID, pxTCB->uxPriority ); + } else { mtCOVERAGE_TEST_MARKER(); @@ -1521,7 +1564,14 @@ BaseType_t i; { /* Ready lists can be accessed so move the task from the suspended list to the ready list directly. */ - if( pxTCB->uxPriority >= pxCurrentTCB[ xPortGetCoreID() ]->uxPriority ) + ( void ) uxListRemove( &( pxTCB->xGenericListItem ) ); + prvAddTaskToReadyList( pxTCB ); + + if ( pxTCB->xCoreID == xPortGetCoreID() ) + { + taskYIELD_OTHER_CORE( pxTCB->xCoreID, pxTCB->uxPriority); + } + else if( pxTCB->uxPriority >= pxCurrentTCB[ xPortGetCoreID() ]->uxPriority ) { xYieldRequired = pdTRUE; } @@ -1529,9 +1579,6 @@ BaseType_t i; { mtCOVERAGE_TEST_MARKER(); } - - ( void ) uxListRemove( &( pxTCB->xGenericListItem ) ); - prvAddTaskToReadyList( pxTCB ); } else { @@ -1732,11 +1779,16 @@ BaseType_t xAlreadyYielded = pdFALSE; /* If the moved task has a priority higher than the current task then a yield must be performed. */ - if( pxTCB->uxPriority >= pxCurrentTCB[ xPortGetCoreID() ]->uxPriority ) + if ( tskCAN_RUN_HERE(pxTCB->xCoreID) && pxTCB->uxPriority >= pxCurrentTCB[ xPortGetCoreID() ]->uxPriority ) { - xYieldPending = pdTRUE; + /* We can schedule the awoken task on this CPU. */ + xYieldPending[xPortGetCoreID()] = pdTRUE; break; } + else if ( pxTCB->xCoreID != xPortGetCoreID() ) + { + taskYIELD_OTHER_CORE( pxTCB->xCoreID, pxTCB->uxPriority ); + } else { mtCOVERAGE_TEST_MARKER(); @@ -1753,7 +1805,7 @@ BaseType_t xAlreadyYielded = pdFALSE; { if( xTaskIncrementTick() != pdFALSE ) { - xYieldPending = pdTRUE; + xYieldPending[ xPortGetCoreID() ] = pdTRUE; } else { @@ -1767,7 +1819,7 @@ BaseType_t xAlreadyYielded = pdFALSE; mtCOVERAGE_TEST_MARKER(); } - if( xYieldPending == pdTRUE ) + if( xYieldPending[ xPortGetCoreID() ] == pdTRUE ) { #if( configUSE_PREEMPTION != 0 ) { @@ -2135,7 +2187,7 @@ BaseType_t xSwitchRequired = pdFALSE; #if ( configUSE_PREEMPTION == 1 ) { - if( xYieldPending != pdFALSE ) + if( xYieldPending [ xPortGetCoreID() ] != pdFALSE ) { xSwitchRequired = pdTRUE; } @@ -2251,11 +2303,11 @@ void vTaskSwitchContext( void ) { /* The scheduler is currently suspended - do not allow a context switch. */ - xYieldPending = pdTRUE; + xYieldPending[ xPortGetCoreID() ] = pdTRUE; } else { - xYieldPending = pdFALSE; + xYieldPending[ xPortGetCoreID() ] = pdFALSE; traceTASK_SWITCHED_OUT(); #if ( configGENERATE_RUN_TIME_STATS == 1 ) @@ -2610,16 +2662,16 @@ BaseType_t xReturn; taskEXIT_CRITICAL(&xTaskQueueMutex); } - if( pxUnblockedTCB->uxPriority > pxCurrentTCB[ xPortGetCoreID() ]->uxPriority ) + if ( tskCAN_RUN_HERE(pxUnblockedTCB->xCoreID) && pxUnblockedTCB->uxPriority >= pxCurrentTCB[ xPortGetCoreID() ]->uxPriority ) { - /* Return true if the task removed from the event list has a higher - priority than the calling task. This allows the calling task to know if - it should force a context switch now. */ + /* We can schedule the awoken task on this CPU. */ + xYieldPending[xPortGetCoreID()] = pdTRUE; xReturn = pdTRUE; - - /* Mark that a yield is pending in case the user is not using the - "xHigherPriorityTaskWoken" parameter to an ISR safe FreeRTOS function. */ - xYieldPending = pdTRUE; + } + else if ( pxUnblockedTCB->xCoreID != xPortGetCoreID() ) + { + taskYIELD_OTHER_CORE( pxUnblockedTCB->xCoreID, pxUnblockedTCB->uxPriority ); + xReturn = pdFALSE; } else { @@ -2670,17 +2722,16 @@ BaseType_t xReturn; ( void ) uxListRemove( &( pxUnblockedTCB->xGenericListItem ) ); prvAddTaskToReadyList( pxUnblockedTCB ); - if( pxUnblockedTCB->uxPriority > pxCurrentTCB[ xPortGetCoreID() ]->uxPriority ) + if ( tskCAN_RUN_HERE(pxUnblockedTCB->xCoreID) && pxUnblockedTCB->uxPriority >= pxCurrentTCB[ xPortGetCoreID() ]->uxPriority ) { - /* Return true if the task removed from the event list has - a higher priority than the calling task. This allows - the calling task to know if it should force a context - switch now. */ + /* We can schedule the awoken task on this CPU. */ + xYieldPending[xPortGetCoreID()] = pdTRUE; xReturn = pdTRUE; - - /* Mark that a yield is pending in case the user is not using the - "xHigherPriorityTaskWoken" parameter to an ISR safe FreeRTOS function. */ - xYieldPending = pdTRUE; + } + else if ( pxUnblockedTCB->xCoreID != xPortGetCoreID() ) + { + taskYIELD_OTHER_CORE( pxUnblockedTCB->xCoreID, pxUnblockedTCB->uxPriority ); + xReturn = pdFALSE; } else { @@ -2751,7 +2802,7 @@ BaseType_t xReturn; void vTaskMissedYield( void ) { - xYieldPending = pdTRUE; + xYieldPending[ xPortGetCoreID() ] = pdTRUE; } /*-----------------------------------------------------------*/ @@ -2921,7 +2972,7 @@ static portTASK_FUNCTION( prvIdleTask, pvParameters ) /* A task was made ready while the scheduler was suspended. */ eReturn = eAbortSleep; } - else if( xYieldPending != pdFALSE ) + else if( xYieldPending[ xPortGetCoreID() ] != pdFALSE ) { /* A yield was pended while the scheduler was suspended. */ eReturn = eAbortSleep; @@ -3597,12 +3648,6 @@ TCB_t *pxTCB; #endif /* ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) ) */ /*-----------------------------------------------------------*/ -/* -ToDo: Mutexes haven't been tested or adapted to multicore at all. - -In fact, nothing below this line has/is. -*/ - #if ( configUSE_MUTEXES == 1 ) void vTaskPriorityInherit( TaskHandle_t const pxMutexHolder ) @@ -4434,12 +4479,16 @@ TickType_t uxReturn; /* The task should not have been on an event list. */ configASSERT( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) == NULL ); - if( pxTCB->uxPriority > pxCurrentTCB[ xPortGetCoreID() ]->uxPriority ) + if( tskCAN_RUN_HERE(pxTCB->xCoreID) && pxTCB->uxPriority > pxCurrentTCB[ xPortGetCoreID() ]->uxPriority ) { /* The notified task has a priority above the currently executing task so a yield is required. */ portYIELD_WITHIN_API(); } + else if ( pxTCB->xCoreID != xPortGetCoreID() ) + { + taskYIELD_OTHER_CORE(pxTCB->xCoreID, pxTCB->uxPriority); + } else { mtCOVERAGE_TEST_MARKER(); @@ -4530,7 +4579,7 @@ TickType_t uxReturn; vListInsertEnd( &( xPendingReadyList ), &( pxTCB->xEventListItem ) ); } - if( pxTCB->uxPriority > pxCurrentTCB[ xPortGetCoreID() ]->uxPriority ) + if( tskCAN_RUN_HERE(pxTCB->xCoreID) && pxTCB->uxPriority > pxCurrentTCB[ xPortGetCoreID() ]->uxPriority ) { /* The notified task has a priority above the currently executing task so a yield is required. */ @@ -4539,6 +4588,10 @@ TickType_t uxReturn; *pxHigherPriorityTaskWoken = pdTRUE; } } + else if ( pxTCB->xCoreID != xPortGetCoreID() ) + { + taskYIELD_OTHER_CORE( pxTCB->xCoreID, pxTCB->uxPriority ); + } else { mtCOVERAGE_TEST_MARKER(); @@ -4593,8 +4646,8 @@ TickType_t uxReturn; this task pending until the scheduler is resumed. */ vListInsertEnd( &( xPendingReadyList ), &( pxTCB->xEventListItem ) ); } - - if( pxTCB->uxPriority > pxCurrentTCB[ xPortGetCoreID() ]->uxPriority ) + + if( tskCAN_RUN_HERE(pxTCB->xCoreID) && pxTCB->uxPriority > pxCurrentTCB[ xPortGetCoreID() ]->uxPriority ) { /* The notified task has a priority above the currently executing task so a yield is required. */ @@ -4603,6 +4656,10 @@ TickType_t uxReturn; *pxHigherPriorityTaskWoken = pdTRUE; } } + else if ( pxTCB->xCoreID != xPortGetCoreID() ) + { + taskYIELD_OTHER_CORE( pxTCB->xCoreID, pxTCB->uxPriority ); + } else { mtCOVERAGE_TEST_MARKER(); From df1c2f0da5e9b1a00622e5eb5f81d2ab772967c2 Mon Sep 17 00:00:00 2001 From: liuzhifu Date: Wed, 26 Oct 2016 21:50:15 +0800 Subject: [PATCH 069/149] components/esp32: refractor according to review comments modify esp_wifi_kick_sta to esp_wifi_deauth_sta --- components/esp32/include/esp_wifi.h | 6 +++--- components/esp32/lib | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/esp32/include/esp_wifi.h b/components/esp32/include/esp_wifi.h index 6fc9d896a..7cfff6ee0 100644 --- a/components/esp32/include/esp_wifi.h +++ b/components/esp32/include/esp_wifi.h @@ -191,14 +191,14 @@ esp_err_t esp_wifi_disconnect(void); esp_err_t esp_wifi_clear_fast_connect(void); /** - * @brief Kick the all station or associated id equals to aid + * @brief deauthenticate all stations or associated id equals to aid * - * @param uint16_t aid : when aid is 0, kick all stations, otherwise kick station whose associated id is aid + * @param uint16_t aid : when aid is 0, deauthenticate all stations, otherwise deauthenticate station whose associated id is aid * * @return ESP_OK : succeed * @return others : fail */ -esp_err_t esp_wifi_kick_sta(uint16_t aid); +esp_err_t esp_wifi_deauth_sta(uint16_t aid); /** * @brief Scan all available APs. diff --git a/components/esp32/lib b/components/esp32/lib index 02063e8d4..b9561aa5d 160000 --- a/components/esp32/lib +++ b/components/esp32/lib @@ -1 +1 @@ -Subproject commit 02063e8d40f72933622b2eafd78ce968085b0047 +Subproject commit b9561aa5db15443d11f8bb5aefdfc5da540d8f2d From 6d54fb004d3ab2c85ac80184a5f1f2ae9a513639 Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Thu, 27 Oct 2016 09:15:43 +0800 Subject: [PATCH 070/149] Change inline to static inline functions. Ref Github issue 62. --- components/freertos/include/freertos/portable.h | 2 +- components/freertos/include/freertos/portmacro.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/freertos/include/freertos/portable.h b/components/freertos/include/freertos/portable.h index a3d39bd5a..f3474d49e 100644 --- a/components/freertos/include/freertos/portable.h +++ b/components/freertos/include/freertos/portable.h @@ -192,7 +192,7 @@ void vPortEndScheduler( void ) PRIVILEGED_FUNCTION; #endif /* Multi-core: get current core ID */ -inline uint32_t xPortGetCoreID() { +static inline uint32_t xPortGetCoreID() { int id; asm volatile( "rsr.prid %0\n" diff --git a/components/freertos/include/freertos/portmacro.h b/components/freertos/include/freertos/portmacro.h index 5e2386d72..f20a4a1e2 100644 --- a/components/freertos/include/freertos/portmacro.h +++ b/components/freertos/include/freertos/portmacro.h @@ -234,7 +234,7 @@ static inline unsigned portENTER_CRITICAL_NESTED() { unsigned state = XTOS_SET_I * *bitwise inverse* of the old mem if the mem wasn't written. This doesn't seem to happen on the * ESP32, though. (Would show up directly if it did because the magic wouldn't match.) */ -inline void uxPortCompareSet(volatile uint32_t *addr, uint32_t compare, uint32_t *set) { +static inline void uxPortCompareSet(volatile uint32_t *addr, uint32_t compare, uint32_t *set) { __asm__ __volatile__( "WSR %2,SCOMPARE1 \n" "ISYNC \n" From e0f49c2221de4face091f8a98085e395f6c0cd4f Mon Sep 17 00:00:00 2001 From: Liu Zhi Fu Date: Thu, 27 Oct 2016 10:42:01 +0800 Subject: [PATCH 071/149] esp32: add esp_wifi_sta_get_ap_info The customers need to get information about AP that associated with ESP32 station, these information includes RSSI, channel number etc, so add this new API --- components/esp32/include/esp_wifi.h | 13 ++++++++++++- components/esp32/lib | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/components/esp32/include/esp_wifi.h b/components/esp32/include/esp_wifi.h index 7cfff6ee0..80ced5dc6 100644 --- a/components/esp32/include/esp_wifi.h +++ b/components/esp32/include/esp_wifi.h @@ -242,13 +242,24 @@ esp_err_t esp_wifi_scan_get_ap_num(uint16_t *number); * * @param uint16_t *number : as input param, it stores max AP number ap_records can hold, as output param, it store the actual AP number this API returns - * @param wifi_ap_record_t *ap_records: an wifi_ap_record_t array to hold the found APs + * @param wifi_ap_record_t *ap_records: wifi_ap_record_t array to hold the found APs * * @return ESP_OK : succeed * @return others : fail */ esp_err_t esp_wifi_scan_get_ap_records(uint16_t *number, wifi_ap_record_t *ap_records); + +/** + * @brief Get information of AP associated with ESP32 station + * + * @param wifi_ap_record_t *ap_info: the wifi_ap_record_t to hold station assocated AP + * + * @return ESP_OK : succeed + * @return others : fail + */ +esp_err_t esp_wifi_sta_get_ap_info(wifi_ap_record_t *ap_info); + /** * @brief Set current power save type * diff --git a/components/esp32/lib b/components/esp32/lib index b9561aa5d..774f6073d 160000 --- a/components/esp32/lib +++ b/components/esp32/lib @@ -1 +1 @@ -Subproject commit b9561aa5db15443d11f8bb5aefdfc5da540d8f2d +Subproject commit 774f6073dee1b01da5f420c5d7513b3d88cd5729 From ff6b8addd90bc0da34fb83b914ea74d779b93089 Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Thu, 27 Oct 2016 11:17:24 +0800 Subject: [PATCH 072/149] Fix panic config ifdefs, un-stall app cpu on boot so it restarts after panic --- components/esp32/cpu_start.c | 4 ++++ components/esp32/panic.c | 10 ++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/components/esp32/cpu_start.c b/components/esp32/cpu_start.c index 37205dcd9..a649764ab 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -102,6 +102,10 @@ void IRAM_ATTR call_start_cpu0() #if !CONFIG_FREERTOS_UNICORE ESP_EARLY_LOGI(TAG, "Starting app cpu, entry point is %p", call_start_cpu1); + //Un-stall the app cpu; the panic handler may have stalled it. + CLEAR_PERI_REG_MASK(RTC_CNTL_SW_CPU_STALL_REG, RTC_CNTL_SW_STALL_APPCPU_C1_M); + CLEAR_PERI_REG_MASK(RTC_CNTL_OPTIONS0_REG, RTC_CNTL_SW_STALL_APPCPU_C0_M); + //Enable clock gating and reset the app cpu. SET_PERI_REG_MASK(DPORT_APPCPU_CTRL_B_REG, DPORT_APPCPU_CLKGATE_EN); CLEAR_PERI_REG_MASK(DPORT_APPCPU_CTRL_C_REG, DPORT_APPCPU_RUNSTALL); SET_PERI_REG_MASK(DPORT_APPCPU_CTRL_A_REG, DPORT_APPCPU_RESETTING); diff --git a/components/esp32/panic.c b/components/esp32/panic.c index 274364008..c806dace2 100644 --- a/components/esp32/panic.c +++ b/components/esp32/panic.c @@ -38,7 +38,7 @@ task switching / interrupt code runs into an unrecoverable error. The default ta overflow handler also is in here. */ -#if !CONFIG_FREERTOS_PANIC_SILENT_REBOOT +#if !CONFIG_ESP32_PANIC_SILENT_REBOOT //printf may be broken, so we fix our own printing fns... inline static void panicPutchar(char c) { while (((READ_PERI_REG(UART_STATUS_REG(0))>>UART_TXFIFO_CNT_S)&UART_TXFIFO_CNT)>=126) ; @@ -123,7 +123,7 @@ static void haltOtherCore() { //Returns true when a debugger is attached using JTAG. static int inOCDMode() { -#if CONFIG_FREERTOS_DEBUG_OCDAWARE +#if CONFIG_ESP32_DEBUG_OCDAWARE int dcr; int reg=0x10200C; //DSRSET register asm("rer %0,%1":"=r"(dcr):"r"(reg)); @@ -218,6 +218,7 @@ static void reconfigureAllWdts() { TIMERG1.wdt_wprotect=0; } +#if CONFIG_ESP32_PANIC_GDBSTUB || CONFIG_ESP32_PANIC_PRINT_HALT /* This disables all the watchdogs for when we call the gdbstub. */ @@ -230,6 +231,7 @@ static void disableAllWdts() { TIMERG0.wdt_wprotect=0; } +#endif /* We arrive here after a panic or unhandled exception, when no OCD is detected. Dump the registers to the @@ -259,11 +261,11 @@ void commonErrorHandler(XtExcFrame *frame) { } panicPutStr("\r\n"); } -#if CONFIG_FREERTOS_PANIC_GDBSTUB +#if CONFIG_ESP32_PANIC_GDBSTUB disableAllWdts(); panicPutStr("Entering gdb stub now.\r\n"); esp_gdbstub_panic_handler(frame); -#elif CONFIG_FREERTOS_PANIC_PRINT_REBOOT || CONFIG_FREERTOS_PANIC_SILENT_REBOOT +#elif CONFIG_ESP32_PANIC_PRINT_REBOOT || CONFIG_ESP32_PANIC_SILENT_REBOOT panicPutStr("Rebooting...\r\n"); for (x=0; x<100; x++) ets_delay_us(1000); software_reset(); From c6477ff10d983aa8dcb152be2bc673352d5bd69c Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Thu, 27 Oct 2016 12:37:19 +0800 Subject: [PATCH 073/149] Fix int clear, actually call int init code --- components/esp32/cpu_start.c | 3 +++ components/esp32/crosscore_int.c | 4 ++-- components/esp32/lib | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/components/esp32/cpu_start.c b/components/esp32/cpu_start.c index 5c7a411c8..0c94f39e3 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -41,6 +41,7 @@ #include "esp_event.h" #include "esp_spi_flash.h" #include "esp_ipc.h" +#include "esp_crosscore_int.h" #include "esp_log.h" #include "trax.h" @@ -146,6 +147,7 @@ void start_cpu0_default(void) uart_div_modify(0, (APB_CLK_FREQ << 4) / 115200); ets_setup_syscalls(); do_global_ctors(); + esp_crosscore_int_init(); esp_ipc_init(); spi_flash_init(); xTaskCreatePinnedToCore(&main_task, "main", @@ -165,6 +167,7 @@ void start_cpu1_default(void) while (port_xSchedulerRunning[0] == 0) { ; } + esp_crosscore_int_init(); ESP_LOGI(TAG, "Starting scheduler on APP CPU."); xPortStartScheduler(); } diff --git a/components/esp32/crosscore_int.c b/components/esp32/crosscore_int.c index 95f2762f2..a98b13583 100644 --- a/components/esp32/crosscore_int.c +++ b/components/esp32/crosscore_int.c @@ -57,7 +57,7 @@ static void esp_crosscore_isr(void *arg) { if (xPortGetCoreID()==0) { WRITE_PERI_REG(DPORT_CPU_INTR_FROM_CPU_0_REG, 0); } else { - WRITE_PERI_REG(DPORT_CPU_INTR_FROM_CPU_0_REG, 1); + WRITE_PERI_REG(DPORT_CPU_INTR_FROM_CPU_1_REG, 0); } //Grab the reason and clear it. portENTER_CRITICAL(&reasonSpinlock); @@ -77,6 +77,7 @@ static void esp_crosscore_isr(void *arg) { //on each active core. void esp_crosscore_int_init() { portENTER_CRITICAL(&reasonSpinlock); + ets_printf("init cpu %d\n", xPortGetCoreID()); reason[xPortGetCoreID()]=0; portEXIT_CRITICAL(&reasonSpinlock); ESP_INTR_DISABLE(ETS_FROM_CPU_INUM); @@ -87,7 +88,6 @@ void esp_crosscore_int_init() { } xt_set_interrupt_handler(ETS_FROM_CPU_INUM, esp_crosscore_isr, (void*)&reason[xPortGetCoreID()]); ESP_INTR_ENABLE(ETS_FROM_CPU_INUM); - } void esp_crosscore_int_send_yield(int coreId) { diff --git a/components/esp32/lib b/components/esp32/lib index a1e5f8b95..b9561aa5d 160000 --- a/components/esp32/lib +++ b/components/esp32/lib @@ -1 +1 @@ -Subproject commit a1e5f8b953c7934677ba7a6ed0a6dd2da0e6bd0f +Subproject commit b9561aa5db15443d11f8bb5aefdfc5da540d8f2d From 329ad14b8c79ac1ae0e30850909b2ef1f12b7621 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Thu, 27 Oct 2016 12:37:34 +0800 Subject: [PATCH 074/149] esp32: update libs for changes in FreeRTOS header files --- components/esp32/lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp32/lib b/components/esp32/lib index aac7d416a..12b3435fc 160000 --- a/components/esp32/lib +++ b/components/esp32/lib @@ -1 +1 @@ -Subproject commit aac7d416a702215f2501a6237caf034ec7e8e80a +Subproject commit 12b3435fc0cd04efc249d52d71efb1cdecda50f8 From 6e6e51426f2a5b2837d4bd9e81e54340aea226ee Mon Sep 17 00:00:00 2001 From: Liu Zhi Fu Date: Thu, 27 Oct 2016 14:11:01 +0800 Subject: [PATCH 075/149] lwip: refractor for lwip 1. All espressif specific code are prefix with ESP_ 2. Define all ESP_ options in lwipopts.h 3. Remove useless code added in 8266 --- components/lwip/api/api_lib.c | 21 +---- components/lwip/api/api_msg.c | 22 +---- components/lwip/api/netbuf.c | 5 -- components/lwip/api/netdb.c | 5 -- components/lwip/api/sockets.c | 86 +++++++------------ components/lwip/api/tcpip.c | 20 ++--- components/lwip/apps/dhcpserver.c | 6 +- components/lwip/core/dns.c | 8 +- components/lwip/core/init.c | 4 +- components/lwip/core/ipv4/autoip.c | 5 -- components/lwip/core/ipv4/dhcp.c | 10 +-- components/lwip/core/ipv4/icmp.c | 5 -- components/lwip/core/ipv4/igmp.c | 5 -- components/lwip/core/ipv4/ip4.c | 9 +- components/lwip/core/ipv4/ip4_addr.c | 14 +-- components/lwip/core/ipv4/ip_frag.c | 5 -- components/lwip/core/ipv6/icmp6.c | 4 - components/lwip/core/ipv6/ip6.c | 4 - components/lwip/core/ipv6/ip6_frag.c | 5 -- components/lwip/core/ipv6/mld6.c | 5 -- components/lwip/core/ipv6/nd6.c | 5 -- components/lwip/core/mem.c | 4 - components/lwip/core/memp.c | 4 - components/lwip/core/netif.c | 13 +-- components/lwip/core/pbuf.c | 55 ++---------- components/lwip/core/raw.c | 4 - components/lwip/core/stats.c | 4 - components/lwip/core/tcp.c | 61 +------------ components/lwip/core/tcp_in.c | 19 ---- components/lwip/core/tcp_out.c | 4 - components/lwip/core/timers.c | 17 +--- components/lwip/core/udp.c | 5 -- components/lwip/include/lwip/lwip/api.h | 4 - components/lwip/include/lwip/lwip/dhcp.h | 2 +- components/lwip/include/lwip/lwip/dns.h | 2 +- components/lwip/include/lwip/lwip/err.h | 2 +- components/lwip/include/lwip/lwip/mem.h | 37 -------- components/lwip/include/lwip/lwip/netif.h | 6 +- components/lwip/include/lwip/lwip/opt.h | 4 +- components/lwip/include/lwip/lwip/pbuf.h | 2 +- .../lwip/include/lwip/lwip/priv/api_msg.h | 6 +- components/lwip/include/lwip/lwip/sockets.h | 4 +- components/lwip/include/lwip/port/lwipopts.h | 33 ++++++- components/lwip/netif/etharp.c | 4 - components/lwip/port/freertos/sys_arch.c | 44 +++++----- components/lwip/port/netif/wlanif.c | 24 ++---- 46 files changed, 142 insertions(+), 475 deletions(-) diff --git a/components/lwip/api/api_lib.c b/components/lwip/api/api_lib.c index c38c76081..ecebf4f81 100755 --- a/components/lwip/api/api_lib.c +++ b/components/lwip/api/api_lib.c @@ -55,11 +55,6 @@ #include -#ifdef MEMLEAK_DEBUG -static const char mem_debug_file[] ICACHE_RODATA_ATTR STORE_ATTR = __FILE__; -#endif - - #define API_MSG_VAR_REF(name) API_VAR_REF(name) #define API_MSG_VAR_DECLARE(name) API_VAR_DECLARE(struct api_msg, name) #define API_MSG_VAR_ALLOC(name) API_VAR_ALLOC(struct api_msg, MEMP_API_MSG, name) @@ -178,8 +173,8 @@ netconn_delete(struct netconn *conn) return err; } -#if !LWIP_THREAD_SAFE - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("netconn_delete - free conn\n")); +#if !ESP_THREAD_SAFE + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("netconn_delete - free conn\n")); netconn_free(conn); #endif @@ -502,7 +497,7 @@ netconn_recv_data(struct netconn *conn, void **new_buf) #endif /* LWIP_TCP && (LWIP_UDP || LWIP_RAW) */ #if (LWIP_UDP || LWIP_RAW) { -#if LWIP_THREAD_SAFE +#if ESP_THREAD_SAFE if (buf == NULL){ API_EVENT(conn, NETCONN_EVT_RCVMINUS, 0); return ERR_CLSD; @@ -710,17 +705,7 @@ netconn_write_partly(struct netconn *conn, const void *dataptr, size_t size, } dontblock = netconn_is_nonblocking(conn) || (apiflags & NETCONN_DONTBLOCK); -#ifdef LWIP_ESP8266 - -#ifdef FOR_XIAOMI - if (dontblock && bytes_written) { -#else - if (dontblock && !bytes_written) { -#endif - -#else if (dontblock && !bytes_written) { -#endif /* This implies netconn_write() cannot be used for non-blocking send, since it has no way to return the number of bytes written. */ return ERR_VAL; diff --git a/components/lwip/api/api_msg.c b/components/lwip/api/api_msg.c index 9cac2e0e9..d504bfb87 100755 --- a/components/lwip/api/api_msg.c +++ b/components/lwip/api/api_msg.c @@ -55,10 +55,6 @@ #include -#ifdef MEMLEAK_DEBUG -static const char mem_debug_file[] ICACHE_RODATA_ATTR STORE_ATTR = __FILE__; -#endif - /* netconns are polled once per second (e.g. continue write on memory error) */ #define NETCONN_TCP_POLL_INTERVAL 2 @@ -1216,16 +1212,7 @@ lwip_netconn_do_connect(void *m) if (msg->conn->state == NETCONN_CONNECT) { msg->err = ERR_ALREADY; } else if (msg->conn->state != NETCONN_NONE) { - -#ifdef LWIP_ESP8266 - if( msg->conn->pcb.tcp->state == ESTABLISHED ) msg->err = ERR_ISCONN; - else - msg->err = ERR_ALREADY; -#else - msg->err = ERR_ISCONN; -#endif - } else { setup_tcp(msg->conn); msg->err = tcp_connect(msg->conn->pcb.tcp, API_EXPR_REF(msg->msg.bc.ipaddr), @@ -1646,14 +1633,7 @@ lwip_netconn_do_write(void *m) if (lwip_netconn_do_writemore(msg->conn, 0) != ERR_OK) { LWIP_ASSERT("state!", msg->conn->state == NETCONN_WRITE); UNLOCK_TCPIP_CORE(); - -#ifdef LWIP_ESP8266 -//#if 0 - sys_arch_sem_wait( LWIP_API_MSG_SND_SEM(msg), 0); -#else - sys_arch_sem_wait(LWIP_API_MSG_SEM(msg), 0); -#endif - + sys_arch_sem_wait(LWIP_API_MSG_SEM(msg), 0); LOCK_TCPIP_CORE(); LWIP_ASSERT("state!", msg->conn->state != NETCONN_WRITE); } diff --git a/components/lwip/api/netbuf.c b/components/lwip/api/netbuf.c index 6c6dc69cc..9ab76a463 100755 --- a/components/lwip/api/netbuf.c +++ b/components/lwip/api/netbuf.c @@ -45,11 +45,6 @@ #include -#ifdef MEMLEAK_DEBUG -static const char mem_debug_file[] ICACHE_RODATA_ATTR STORE_ATTR = __FILE__; -#endif - - /** * Create (allocate) and initialize a new netbuf. * The netbuf doesn't yet contain a packet buffer! diff --git a/components/lwip/api/netdb.c b/components/lwip/api/netdb.c index 8fd3f4186..65510f55e 100755 --- a/components/lwip/api/netdb.c +++ b/components/lwip/api/netdb.c @@ -47,11 +47,6 @@ #include #include -#ifdef MEMLEAK_DEBUG -static const char mem_debug_file[] ICACHE_RODATA_ATTR STORE_ATTR = __FILE__; -#endif - - /** helper struct for gethostbyname_r to access the char* buffer */ struct gethostbyname_r_helper { ip_addr_t *addr_list[2]; diff --git a/components/lwip/api/sockets.c b/components/lwip/api/sockets.c index 1cc5fbf7b..455d007ea 100755 --- a/components/lwip/api/sockets.c +++ b/components/lwip/api/sockets.c @@ -61,11 +61,6 @@ #include -#ifdef MEMLEAK_DEBUG -static const char mem_debug_file[] ICACHE_RODATA_ATTR STORE_ATTR = __FILE__; -#endif - - /* If the netconn API is not required publicly, then we include the necessary files here to get the implementation */ #if !LWIP_NETCONN @@ -216,7 +211,7 @@ struct lwip_sock { /** last error that occurred on this socket (in fact, all our errnos fit into an u8_t) */ u8_t err; -#if LWIP_THREAD_SAFE +#if ESP_THREAD_SAFE /* lock is used to protect state/ref field, however this lock is not a perfect lock, e.g * taskA and taskB can access sock X, then taskA freed sock X, before taskB detect * this, taskC reuse sock X, then when taskB try to access sock X, problem may happen. @@ -239,7 +234,7 @@ struct lwip_sock { SELWAIT_T select_waiting; }; -#if LWIP_THREAD_SAFE +#if ESP_THREAD_SAFE #define LWIP_SOCK_OPEN 0 #define LWIP_SOCK_CLOSING 1 @@ -247,25 +242,25 @@ struct lwip_sock { #define LWIP_SOCK_LOCK(sock) \ do{\ - /*LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("l\n"));*/\ + /*LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("l\n"));*/\ sys_mutex_lock(&sock->lock);\ - /*LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("l ok\n"));*/\ + /*LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("l ok\n"));*/\ }while(0) #define LWIP_SOCK_UNLOCK(sock) \ do{\ sys_mutex_unlock(&sock->lock);\ - /*LWIP_DEBUGF(THREAD_SAFE_DEBUG1, ("unl\n"));*/\ + /*LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG1, ("unl\n"));*/\ }while(0) #define LWIP_FREE_SOCK(sock) \ do{\ if(sock->conn && NETCONNTYPE_GROUP(netconn_type(sock->conn)) == NETCONN_TCP){\ - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("LWIP_FREE_SOCK:free tcp sock\n"));\ + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("LWIP_FREE_SOCK:free tcp sock\n"));\ free_socket(sock, 1);\ } else {\ - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("LWIP_FREE_SOCK:free non-tcp sock\n"));\ + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("LWIP_FREE_SOCK:free non-tcp sock\n"));\ free_socket(sock, 0);\ }\ }while(0) @@ -273,7 +268,7 @@ do{\ #define LWIP_SET_CLOSE_FLAG() \ do{\ LWIP_SOCK_LOCK(__sock);\ - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("mark sock closing\n"));\ + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("mark sock closing\n"));\ __sock->state = LWIP_SOCK_CLOSING;\ LWIP_SOCK_UNLOCK(__sock);\ }while(0) @@ -291,7 +286,7 @@ do{\ LWIP_SOCK_LOCK(__sock);\ __sock->ref ++;\ if (__sock->state != LWIP_SOCK_OPEN) {\ - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("LWIP_API_LOCK:soc is %d, return\n", __sock->state));\ + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("LWIP_API_LOCK:soc is %d, return\n", __sock->state));\ __sock->ref --;\ LWIP_SOCK_UNLOCK(__sock);\ return -1;\ @@ -306,12 +301,12 @@ do{\ __sock->ref --;\ if (__sock->state == LWIP_SOCK_CLOSING) {\ if (__sock->ref == 0){\ - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("LWIP_API_UNLOCK:ref 0, free __sock\n"));\ + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("LWIP_API_UNLOCK:ref 0, free __sock\n"));\ LWIP_FREE_SOCK(__sock);\ LWIP_SOCK_UNLOCK(__sock);\ return __ret;\ }\ - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("LWIP_API_UNLOCK: soc state is closing, return\n"));\ + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("LWIP_API_UNLOCK: soc state is closing, return\n"));\ LWIP_SOCK_UNLOCK(__sock);\ return __ret;\ }\ @@ -387,7 +382,7 @@ static void lwip_socket_unregister_membership(int s, const ip4_addr_t *if_addr, static void lwip_socket_drop_registered_memberships(int s); #endif /* LWIP_IGMP */ -#ifdef LWIP_ESP8266 +#if ESP_LWIP #include "esp_wifi_internal.h" #include "esp_system.h" @@ -414,7 +409,7 @@ static inline void esp32_tx_flow_ctrl(void) /** The global array of available sockets */ static struct lwip_sock sockets[NUM_SOCKETS]; -#if LWIP_THREAD_SAFE +#if ESP_THREAD_SAFE static bool sockets_init_flag = false; #endif /** The global list of tasks waiting for select */ @@ -425,13 +420,7 @@ static volatile int select_cb_ctr; /** Table to quickly map an lwIP error (err_t) to a socket error * by using -err as an index */ -#ifdef LWIP_ESP8266 -//TO_DO -//static const int err_to_errno_table[] ICACHE_RODATA_ATTR STORE_ATTR = { static const int err_to_errno_table[] = { -#else -static const int err_to_errno_table[] = { -#endif 0, /* ERR_OK 0 No error, everything OK. */ ENOMEM, /* ERR_MEM -1 Out of memory error. */ ENOBUFS, /* ERR_BUF -2 Buffer error. */ @@ -442,7 +431,7 @@ static const int err_to_errno_table[] = { EWOULDBLOCK, /* ERR_WOULDBLOCK -7 Operation would block. */ EADDRINUSE, /* ERR_USE -8 Address in use. */ -#ifdef LWIP_ESP8266 +#if ESP_LWIP EALREADY, /* ERR_ALREADY -9 Already connected. */ EISCONN, /* ERR_ISCONN -10 Conn already established */ ECONNABORTED, /* ERR_ABRT -11 Connection aborted. */ @@ -583,7 +572,7 @@ alloc_socket(struct netconn *newconn, int accepted) int i; SYS_ARCH_DECL_PROTECT(lev); -#if LWIP_THREAD_SAFE +#if ESP_THREAD_SAFE bool found = false; int oldest = -1; @@ -639,16 +628,16 @@ alloc_socket(struct netconn *newconn, int accepted) if (!sockets[oldest].lock){ /* one time init and never free */ if (sys_mutex_new(&sockets[oldest].lock) != ERR_OK){ - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("new sock lock fail\n")); + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("new sock lock fail\n")); return -1; } } - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("alloc_socket: alloc %d ok\n", oldest)); + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("alloc_socket: alloc %d ok\n", oldest)); return oldest + LWIP_SOCKET_OFFSET; } - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("alloc_socket: failed\n")); + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("alloc_socket: failed\n")); #else @@ -693,12 +682,12 @@ free_socket(struct lwip_sock *sock, int is_tcp) void *lastdata; SYS_ARCH_DECL_PROTECT(lev); - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("free_sockset:free socket s=%p is_tcp=%d\n", sock, is_tcp)); + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("free_sockset:free socket s=%p is_tcp=%d\n", sock, is_tcp)); lastdata = sock->lastdata; sock->lastdata = NULL; sock->lastoffset = 0; sock->err = 0; -#if LWIP_THREAD_SAFE +#if ESP_THREAD_SAFE if (sock->conn){ netconn_free(sock->conn); } @@ -716,10 +705,10 @@ free_socket(struct lwip_sock *sock, int is_tcp) if (lastdata != NULL) { if (is_tcp) { - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("free_sockset:free lastdata pbuf=%p\n", lastdata)); + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("free_sockset:free lastdata pbuf=%p\n", lastdata)); pbuf_free((struct pbuf *)lastdata); } else { - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("free_sockset:free lastdata, netbuf=%p\n", lastdata)); + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("free_sockset:free lastdata, netbuf=%p\n", lastdata)); netbuf_delete((struct netbuf *)lastdata); } } @@ -872,19 +861,19 @@ lwip_close(int s) int is_tcp = 0; err_t err; - LWIP_DEBUGF(SOCKETS_DEBUG|THREAD_SAFE_DEBUG, ("lwip_close: (%d)\n", s)); + LWIP_DEBUGF(SOCKETS_DEBUG|ESP_THREAD_SAFE_DEBUG, ("lwip_close: (%d)\n", s)); sock = get_socket(s); if (!sock) { - LWIP_DEBUGF(SOCKETS_DEBUG|THREAD_SAFE_DEBUG, ("lwip_close: sock is null, return -1\n")); + LWIP_DEBUGF(SOCKETS_DEBUG|ESP_THREAD_SAFE_DEBUG, ("lwip_close: sock is null, return -1\n")); return -1; } if (sock->conn != NULL) { is_tcp = NETCONNTYPE_GROUP(netconn_type(sock->conn)) == NETCONN_TCP; - LWIP_DEBUGF(SOCKETS_DEBUG|THREAD_SAFE_DEBUG, ("lwip_close: is_tcp=%d\n", is_tcp)); + LWIP_DEBUGF(SOCKETS_DEBUG|ESP_THREAD_SAFE_DEBUG, ("lwip_close: is_tcp=%d\n", is_tcp)); } else { - LWIP_DEBUGF(SOCKETS_DEBUG|THREAD_SAFE_DEBUG, ("conn is null\n")); + LWIP_DEBUGF(SOCKETS_DEBUG|ESP_THREAD_SAFE_DEBUG, ("conn is null\n")); LWIP_ASSERT("lwip_close: sock->lastdata == NULL", sock->lastdata == NULL); } @@ -895,12 +884,12 @@ lwip_close(int s) err = netconn_delete(sock->conn); if (err != ERR_OK) { - LWIP_DEBUGF(SOCKETS_DEBUG|THREAD_SAFE_DEBUG, ("netconn_delete fail, ret=%d\n", err)); + LWIP_DEBUGF(SOCKETS_DEBUG|ESP_THREAD_SAFE_DEBUG, ("netconn_delete fail, ret=%d\n", err)); sock_set_errno(sock, err_to_errno(err)); return -1; } -#if !LWIP_THREAD_SAFE +#if !ESP_THREAD_SAFE free_socket(sock, is_tcp); #endif @@ -1130,22 +1119,13 @@ lwip_recvfrom(int s, void *mem, size_t len, int flags, ip_addr_debug_print(SOCKETS_DEBUG, fromaddr); LWIP_DEBUGF(SOCKETS_DEBUG, (" port=%"U16_F" len=%d\n", port, off)); -#ifdef LWIP_ESP8266 if (from && fromlen) -#else - -#if SOCKETS_DEBUG - if (from && fromlen) -#endif /* SOCKETS_DEBUG */ - -#endif { if (*fromlen > saddr.sa.sa_len) { *fromlen = saddr.sa.sa_len; } MEMCPY(from, &saddr, *fromlen); - -#ifdef LWIP_ESP8266 +#if ESP_LWIP } else { /*fix the code for setting the UDP PROTO's remote infomation by liuh at 2014.8.27*/ if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) == NETCONN_UDP){ @@ -1437,7 +1417,7 @@ lwip_sendto(int s, const void *data, size_t size, int flags, SOCKADDR_TO_IPADDR_PORT(to, &buf.addr, remote_port); } else { -#ifdef LWIP_ESP8266 +#if ESP_LWIP /*fix the code for getting the UDP proto's remote information by liuh at 2014.8.27*/ if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) == NETCONN_UDP){ if(NETCONNTYPE_ISIPV6(netconn_type(sock->conn))) { @@ -1453,7 +1433,7 @@ lwip_sendto(int s, const void *data, size_t size, int flags, #endif remote_port = 0; ip_addr_set_any(NETCONNTYPE_ISIPV6(netconn_type(sock->conn)), &buf.addr); -#ifdef LWIP_ESP8266 +#if ESP_LWIP } #endif @@ -1986,7 +1966,7 @@ again: int lwip_shutdown(int s, int how) { -#ifndef LWIP_ESP8266 +#if ! ESP_LWIP struct lwip_sock *sock; err_t err; @@ -3126,7 +3106,7 @@ static void lwip_socket_drop_registered_memberships(int s) } #endif /* LWIP_IGMP */ -#if LWIP_THREAD_SAFE +#if ESP_THREAD_SAFE int lwip_sendto_r(int s, const void *data, size_t size, int flags, diff --git a/components/lwip/api/tcpip.c b/components/lwip/api/tcpip.c index 9df3c38a1..0ad60721e 100755 --- a/components/lwip/api/tcpip.c +++ b/components/lwip/api/tcpip.c @@ -50,18 +50,13 @@ #include "lwip/pbuf.h" #include "netif/etharp.h" -#ifdef MEMLEAK_DEBUG -static const char mem_debug_file[] ICACHE_RODATA_ATTR STORE_ATTR = __FILE__; -#endif - - #define TCPIP_MSG_VAR_REF(name) API_VAR_REF(name) #define TCPIP_MSG_VAR_DECLARE(name) API_VAR_DECLARE(struct tcpip_msg, name) #define TCPIP_MSG_VAR_ALLOC(name) API_VAR_ALLOC(struct tcpip_msg, MEMP_TCPIP_MSG_API, name) #define TCPIP_MSG_VAR_FREE(name) API_VAR_FREE(MEMP_TCPIP_MSG_API, name) /* global variables */ -#ifdef PERF +#if ESP_PERF uint32_t g_rx_post_mbox_fail_cnt = 0; #endif static tcpip_init_done_fn tcpip_init_done; @@ -144,13 +139,11 @@ tcpip_thread(void *arg) case TCPIP_MSG_INPKT: LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: PACKET %p\n", (void *)msg)); -#ifdef LWIP_ESP8266 -//#if 0 +#if ESP_LWIP if(msg->msg.inp.p != NULL && msg->msg.inp.netif != NULL) { #endif msg->msg.inp.input_fn(msg->msg.inp.p, msg->msg.inp.netif); -#ifdef LWIP_ESP8266 -//#if 0 +#if ESP_LWIP } #endif @@ -230,7 +223,7 @@ tcpip_inpkt(struct pbuf *p, struct netif *inp, netif_input_fn input_fn) msg->msg.inp.netif = inp; msg->msg.inp.input_fn = input_fn; if (sys_mbox_trypost(&mbox, msg) != ERR_OK) { -#ifdef PERF +#if ESP_PERF g_rx_post_mbox_fail_cnt ++; #endif memp_free(MEMP_TCPIP_MSG_INPKT, msg); @@ -503,7 +496,7 @@ tcpip_init(tcpip_init_done_fn initfunc, void *arg) #endif /* LWIP_TCPIP_CORE_LOCKING */ -#ifdef LWIP_ESP8266 +#if ESP_LWIP sys_thread_t xLwipTaskHandle = sys_thread_new(TCPIP_THREAD_NAME , tcpip_thread, NULL, TCPIP_THREAD_STACKSIZE, TCPIP_THREAD_PRIO); @@ -548,8 +541,7 @@ pbuf_free_callback(struct pbuf *p) * @return ERR_OK if callback could be enqueued, an err_t if not */ -#ifdef LWIP_ESP8266 -//#if 0 +#if ESP_LWIP static void mem_free_local(void *arg) { mem_free(arg); diff --git a/components/lwip/apps/dhcpserver.c b/components/lwip/apps/dhcpserver.c index 4cdef4123..22443e8cd 100644 --- a/components/lwip/apps/dhcpserver.c +++ b/components/lwip/apps/dhcpserver.c @@ -24,7 +24,7 @@ #include "apps/dhcpserver.h" -#ifdef LWIP_ESP8266 +#if ESP_DHCP #define BOOTP_BROADCAST 0x8000 @@ -71,10 +71,6 @@ #define DHCPS_STATE_IDLE 5 #define DHCPS_STATE_RELEASE 6 -#ifdef MEMLEAK_DEBUG -static const char mem_debug_file[] ICACHE_RODATA_ATTR STORE_ATTR = __FILE__; -#endif - //////////////////////////////////////////////////////////////////////////////////// static const u32_t magic_cookie = 0x63538263; diff --git a/components/lwip/core/dns.c b/components/lwip/core/dns.c index da8ac95b8..8f0ac5cc8 100755 --- a/components/lwip/core/dns.c +++ b/components/lwip/core/dns.c @@ -85,10 +85,6 @@ #include -#ifdef MEMLEAK_DEBUG -static const char mem_debug_file[] ICACHE_RODATA_ATTR STORE_ATTR = __FILE__; -#endif - /** Random generator function to create random TXIDs and source ports for queries */ #ifndef DNS_RAND_TXID #if ((LWIP_DNS_SECURE & LWIP_DNS_SECURE_RAND_XID) != 0) @@ -1091,7 +1087,7 @@ dns_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, u8_t dns_err; /* This entry is now completed. */ -#ifndef LWIP_ESP8266 +#if ! ESP_DNS entry->state = DNS_STATE_DONE; #endif dns_err = hdr.flags2 & DNS_FLAG2_ERR_MASK; @@ -1105,7 +1101,7 @@ dns_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, if (((hdr.flags1 & DNS_FLAG1_RESPONSE) == 0) || (dns_err != 0) || (nquestions != 1)) { LWIP_DEBUGF(DNS_DEBUG, ("dns_recv: \"%s\": error in flags\n", entry->name)); /* call callback to indicate error, clean up memory and return */ -#ifndef LWIP_ESP8266 +#if ! ESP_DNS goto responseerr; } #else diff --git a/components/lwip/core/init.c b/components/lwip/core/init.c index f974aedaf..774e9a2be 100755 --- a/components/lwip/core/init.c +++ b/components/lwip/core/init.c @@ -61,7 +61,7 @@ #include "lwip/api.h" #include "netif/ppp/ppp_impl.h" -#ifndef PERF +#if ! ESP_PERF /* Compile-time sanity checks for configuration errors. * These can be done independently of LWIP_DEBUG, without penalty. */ @@ -313,7 +313,7 @@ #error "lwip_sanity_check: WARNING: PBUF_POOL_BUFSIZE does not provide enough space for protocol headers. If you know what you are doing, define LWIP_DISABLE_TCP_SANITY_CHECKS to 1 to disable this error." #endif -#ifndef LWIP_ESP8266 +#if ! ESP_LWIP #if !MEMP_MEM_MALLOC && (TCP_WND > (PBUF_POOL_SIZE * (PBUF_POOL_BUFSIZE - (PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN)))) #error "lwip_sanity_check: WARNING: TCP_WND is larger than space provided by PBUF_POOL_SIZE * (PBUF_POOL_BUFSIZE - protocol headers). If you know what you are doing, define LWIP_DISABLE_TCP_SANITY_CHECKS to 1 to disable this error." #endif diff --git a/components/lwip/core/ipv4/autoip.c b/components/lwip/core/ipv4/autoip.c index 391e8eeae..19b192836 100755 --- a/components/lwip/core/ipv4/autoip.c +++ b/components/lwip/core/ipv4/autoip.c @@ -76,11 +76,6 @@ #include #include -#ifdef MEMLEAK_DEBUG -static const char mem_debug_file[] ICACHE_RODATA_ATTR STORE_ATTR = __FILE__; -#endif - - /* 169.254.0.0 */ #define AUTOIP_NET 0xA9FE0000 /* 169.254.1.0 */ diff --git a/components/lwip/core/ipv4/dhcp.c b/components/lwip/core/ipv4/dhcp.c index 1f3758fa9..33d13fb32 100755 --- a/components/lwip/core/ipv4/dhcp.c +++ b/components/lwip/core/ipv4/dhcp.c @@ -82,10 +82,6 @@ #include -#ifdef MEMLEAK_DEBUG -static const char mem_debug_file[] ICACHE_RODATA_ATTR STORE_ATTR = __FILE__; -#endif - /** DHCP_CREATE_RAND_XID: if this is set to 1, the xid is created using * LWIP_RAND() (this overrides DHCP_GLOBAL_XID) */ @@ -146,7 +142,7 @@ static u8_t dhcp_discover_select_options[] = { DHCP_OPTION_BROADCAST, DHCP_OPTION_DNS_SERVER -#ifdef LWIP_ESP8266 +#if ESP_DHCP /**add options for support more router by liuHan**/ , DHCP_OPTION_DOMAIN_NAME, DHCP_OPTION_NB_TINS, @@ -454,7 +450,7 @@ dhcp_fine_tmr(void) /* only act on DHCP configured interfaces */ if (netif->dhcp != NULL) { -//#ifdef LWIP_ESP8266 +//#if ESP_DHCP /*add DHCP retries processing by LiuHan*/ #if 0 if (DHCP_MAXRTX != 0) { @@ -997,7 +993,7 @@ dhcp_discover(struct netif *netif) dhcp_option(dhcp, DHCP_OPTION_MAX_MSG_SIZE, DHCP_OPTION_MAX_MSG_SIZE_LEN); dhcp_option_short(dhcp, DHCP_MAX_MSG_LEN(netif)); -#ifdef LWIP_ESP8266 +#if ESP_DHCP #if LWIP_NETIF_HOSTNAME dhcp_option_hostname(dhcp, netif); #endif /* LWIP_NETIF_HOSTNAME */ diff --git a/components/lwip/core/ipv4/icmp.c b/components/lwip/core/ipv4/icmp.c index c492ed75f..9202bb650 100755 --- a/components/lwip/core/ipv4/icmp.c +++ b/components/lwip/core/ipv4/icmp.c @@ -51,11 +51,6 @@ #include -#ifdef MEMLEAK_DEBUG -static const char mem_debug_file[] ICACHE_RODATA_ATTR STORE_ATTR = __FILE__; -#endif - - /** Small optimization: set to 0 if incoming PBUF_POOL pbuf always can be * used to modify and send a response packet (and to 1 if this is not the case, * e.g. when link header is stripped of when receiving) */ diff --git a/components/lwip/core/ipv4/igmp.c b/components/lwip/core/ipv4/igmp.c index d75fe15fd..03f3ae384 100755 --- a/components/lwip/core/ipv4/igmp.c +++ b/components/lwip/core/ipv4/igmp.c @@ -92,11 +92,6 @@ Steve Reynolds #include "string.h" -#ifdef MEMLEAK_DEBUG -static const char mem_debug_file[] ICACHE_RODATA_ATTR STORE_ATTR = __FILE__; -#endif - - /* * IGMP constants */ diff --git a/components/lwip/core/ipv4/ip4.c b/components/lwip/core/ipv4/ip4.c index 5f1e77a5e..1d581d4d8 100755 --- a/components/lwip/core/ipv4/ip4.c +++ b/components/lwip/core/ipv4/ip4.c @@ -59,11 +59,6 @@ #include -#ifdef MEMLEAK_DEBUG -static const char mem_debug_file[] ICACHE_RODATA_ATTR STORE_ATTR = __FILE__; -#endif - - /** Set this to 0 in the rare case of wanting to call an extra function to * generate the IP checksum (in contrast to calculating it on-the-fly). */ #ifndef LWIP_INLINE_IP_CHKSUM @@ -150,7 +145,7 @@ ip4_route_src(const ip4_addr_t *dest, const ip4_addr_t *src) struct netif * ip4_route(const ip4_addr_t *dest) { -#ifdef LWIP_ESP8266 +#if ESP_LWIP struct netif *non_default_netif = NULL; #endif struct netif *netif; @@ -183,7 +178,7 @@ ip4_route(const ip4_addr_t *dest) } } -#ifdef LWIP_ESP8266 +#if ESP_LWIP if (non_default_netif && !ip4_addr_isbroadcast(dest, non_default_netif)){ return non_default_netif; } diff --git a/components/lwip/core/ipv4/ip4_addr.c b/components/lwip/core/ipv4/ip4_addr.c index 3053cf087..0501b84e5 100755 --- a/components/lwip/core/ipv4/ip4_addr.c +++ b/components/lwip/core/ipv4/ip4_addr.c @@ -45,17 +45,8 @@ /* used by IP_ADDR_ANY and IP_ADDR_BROADCAST in ip_addr.h */ -#ifdef LWIP_ESP8266 -//TO_DO -//const ip_addr_t ip_addr_any ICACHE_RODATA_ATTR STORE_ATTR = IPADDR4_INIT(IPADDR_ANY); -//const ip_addr_t ip_addr_broadcast ICACHE_RODATA_ATTR STORE_ATTR = IPADDR4_INIT(IPADDR_BROADCAST); const ip_addr_t ip_addr_any = IPADDR4_INIT(IPADDR_ANY); const ip_addr_t ip_addr_broadcast = IPADDR4_INIT(IPADDR_BROADCAST); -#else -const ip_addr_t ip_addr_any = IPADDR4_INIT(IPADDR_ANY); -const ip_addr_t ip_addr_broadcast = IPADDR4_INIT(IPADDR_BROADCAST); -#endif - /** * Determine if an address is a broadcast address on a network interface @@ -170,7 +161,7 @@ ip4addr_aton(const char *cp, ip4_addr_t *addr) u32_t parts[4]; u32_t *pp = parts; -#ifdef LWIP_ESP8266 +#if ESP_LWIP //#if 0 char ch; unsigned long cutoff; @@ -199,8 +190,7 @@ ip4addr_aton(const char *cp, ip4_addr_t *addr) } } -#ifdef LWIP_ESP8266 -//#if 0 +#if ESP_IP4_ATON cutoff =(unsigned long)0xffffffff / (unsigned long)base; cutlim =(unsigned long)0xffffffff % (unsigned long)base; for (;;) { diff --git a/components/lwip/core/ipv4/ip_frag.c b/components/lwip/core/ipv4/ip_frag.c index 1e6b053e6..a64743350 100755 --- a/components/lwip/core/ipv4/ip_frag.c +++ b/components/lwip/core/ipv4/ip_frag.c @@ -51,11 +51,6 @@ #include -#ifdef MEMLEAK_DEBUG -static const char mem_debug_file[] ICACHE_RODATA_ATTR STORE_ATTR = __FILE__; -#endif - - #if IP_REASSEMBLY /** * The IP reassembly code currently has the following limitations: diff --git a/components/lwip/core/ipv6/icmp6.c b/components/lwip/core/ipv6/icmp6.c index 0a17da33e..013983bde 100755 --- a/components/lwip/core/ipv6/icmp6.c +++ b/components/lwip/core/ipv6/icmp6.c @@ -56,10 +56,6 @@ #include -#ifdef MEMLEAK_DEBUG -static const char mem_debug_file[] ICACHE_RODATA_ATTR STORE_ATTR = __FILE__; -#endif - #ifndef LWIP_ICMP6_DATASIZE #define LWIP_ICMP6_DATASIZE 8 #endif diff --git a/components/lwip/core/ipv6/ip6.c b/components/lwip/core/ipv6/ip6.c index 056d33355..380bc290c 100755 --- a/components/lwip/core/ipv6/ip6.c +++ b/components/lwip/core/ipv6/ip6.c @@ -59,10 +59,6 @@ #include "lwip/debug.h" #include "lwip/stats.h" -#ifdef MEMLEAK_DEBUG -static const char mem_debug_file[] ICACHE_RODATA_ATTR STORE_ATTR = __FILE__; -#endif - /** * Finds the appropriate network interface for a given IPv6 address. It tries to select * a netif following a sequence of heuristics: diff --git a/components/lwip/core/ipv6/ip6_frag.c b/components/lwip/core/ipv6/ip6_frag.c index 0792c2e1b..c9e13cd20 100755 --- a/components/lwip/core/ipv6/ip6_frag.c +++ b/components/lwip/core/ipv6/ip6_frag.c @@ -52,11 +52,6 @@ #include -#ifdef MEMLEAK_DEBUG -static const char mem_debug_file[] ICACHE_RODATA_ATTR STORE_ATTR = __FILE__; -#endif - - #if LWIP_IPV6 && LWIP_IPV6_REASS /* don't build if not configured for use in lwipopts.h */ diff --git a/components/lwip/core/ipv6/mld6.c b/components/lwip/core/ipv6/mld6.c index 6a2d55c54..489c5063a 100755 --- a/components/lwip/core/ipv6/mld6.c +++ b/components/lwip/core/ipv6/mld6.c @@ -59,11 +59,6 @@ #include -#ifdef MEMLEAK_DEBUG -static const char mem_debug_file[] ICACHE_RODATA_ATTR STORE_ATTR = __FILE__; -#endif - - /* * MLD constants */ diff --git a/components/lwip/core/ipv6/nd6.c b/components/lwip/core/ipv6/nd6.c index 39e7bfed0..36f8f78c3 100755 --- a/components/lwip/core/ipv6/nd6.c +++ b/components/lwip/core/ipv6/nd6.c @@ -60,11 +60,6 @@ #include -#ifdef MEMLEAK_DEBUG -static const char mem_debug_file[] ICACHE_RODATA_ATTR STORE_ATTR = __FILE__; -#endif - - /* Router tables. */ struct nd6_neighbor_cache_entry neighbor_cache[LWIP_ND6_NUM_NEIGHBORS]; struct nd6_destination_cache_entry destination_cache[LWIP_ND6_NUM_DESTINATIONS]; diff --git a/components/lwip/core/mem.c b/components/lwip/core/mem.c index 42df6daeb..9ca9e3c4f 100755 --- a/components/lwip/core/mem.c +++ b/components/lwip/core/mem.c @@ -65,10 +65,6 @@ #include -#ifdef MEMLEAK_DEBUG -static const char mem_debug_file[] ICACHE_RODATA_ATTR STORE_ATTR = __FILE__; -#endif - #if MEM_USE_POOLS #if MEMP_MEM_MALLOC diff --git a/components/lwip/core/memp.c b/components/lwip/core/memp.c index a5169abc8..789553365 100755 --- a/components/lwip/core/memp.c +++ b/components/lwip/core/memp.c @@ -70,10 +70,6 @@ #include -#ifdef MEMLEAK_DEBUG -static const char mem_debug_file[] ICACHE_RODATA_ATTR STORE_ATTR = __FILE__; -#endif - #define LWIP_MEMPOOL(name,num,size,desc) LWIP_MEMPOOL_DECLARE(name,num,size,desc) #include "lwip/priv/memp_std.h" diff --git a/components/lwip/core/netif.c b/components/lwip/core/netif.c index 33e030412..5c308a957 100755 --- a/components/lwip/core/netif.c +++ b/components/lwip/core/netif.c @@ -81,10 +81,6 @@ #define NETIF_LINK_CALLBACK(n) #endif /* LWIP_NETIF_LINK_CALLBACK */ -#ifdef MEMLEAK_DEBUG -static const char mem_debug_file[] ICACHE_RODATA_ATTR STORE_ATTR = __FILE__; -#endif - struct netif *netif_list; struct netif *netif_default; @@ -220,7 +216,7 @@ netif_add(struct netif *netif, /* netif not under DHCP control by default */ netif->dhcp = NULL; -#ifdef LWIP_ESP8266 +#if ESP_DHCP netif->dhcps_pcb = NULL; #endif @@ -233,8 +229,7 @@ netif_add(struct netif *netif, #endif /* LWIP_AUTOIP */ #if LWIP_IPV6_AUTOCONFIG -#ifdef LWIP_ESP8266 -//#if 0 +#if ESP_IPV6_AUTOCONFIG netif->ip6_autoconfig_enabled = 1; #else /* IPv6 address autoconfiguration not enabled by default */ @@ -973,7 +968,7 @@ netif_create_ip6_linklocal_address(struct netif *netif, u8_t from_mac_48bit) } } -#ifdef LWIP_ESP8266 +#if ESP_LWIP ip6_addr_set( ip_2_ip6(&netif->link_local_addr), ip_2_ip6(&netif->ip6_addr[0]) ); #endif @@ -1028,7 +1023,7 @@ netif_add_ip6_address(struct netif *netif, const ip6_addr_t *ip6addr, s8_t *chos } -#ifdef LWIP_ESP8266 +#if ESP_LWIP void netif_create_ip4_linklocal_address(struct netif * netif) { diff --git a/components/lwip/core/pbuf.c b/components/lwip/core/pbuf.c index 044d765cd..29e24ef2b 100755 --- a/components/lwip/core/pbuf.c +++ b/components/lwip/core/pbuf.c @@ -78,13 +78,8 @@ #include -#ifdef MEMLEAK_DEBUG -static const char mem_debug_file[] ICACHE_RODATA_ATTR STORE_ATTR = __FILE__; -#endif - -#ifdef LWIP_ESP8266 +#if ESP_LWIP #include "esp_wifi_internal.h" -#define EP_OFFSET 0 #endif #define SIZEOF_STRUCT_PBUF LWIP_MEM_ALIGN_SIZE(sizeof(struct pbuf)) @@ -208,12 +203,7 @@ struct pbuf * pbuf_alloc(pbuf_layer layer, u16_t length, pbuf_type type) { struct pbuf *p, *q, *r; - -#ifdef LWIP_ESP8266 - u16_t offset = 0; -#else - u16_t offset; -#endif + u16_t offset = 0; s32_t rem_len; /* remaining length */ LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_alloc(length=%"U16_F")\n", length)); @@ -224,48 +214,16 @@ pbuf_alloc(pbuf_layer layer, u16_t length, pbuf_type type) /* add room for transport (often TCP) layer header */ offset = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN; -#ifdef LWIP_ESP8266 //TO_DO - offset += EP_OFFSET; -#endif - break; case PBUF_IP: /* add room for IP layer header */ offset = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN; -#ifdef LWIP_ESP8266 //TO_DO - offset += EP_OFFSET; -#endif - break; case PBUF_LINK: /* add room for link layer header */ offset = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN; -#ifdef LWIP_ESP8266 //TO_DO - /* - * 1. LINK_HLEN 14Byte will be remove in WLAN layer - * 2. IEEE80211_HDR_MAX_LEN needs 40 bytes. - * 3. encryption needs exra 4 bytes ahead of actual data payload, and require - * DAddr and SAddr to be 4-byte aligned. - * 4. TRANSPORT and IP are all 20, 4 bytes aligned, nice... - * 5. LCC add 6 bytes more, We don't consider WAPI yet... - * 6. define LWIP_MEM_ALIGN to be 4 Byte aligned, pbuf struct is 16B, Only thing may be - * matter is ether_hdr is not 4B aligned. - * - * So, we need extra (40 + 4 - 14) = 30 and it's happen to be 4-Byte aligned - * - * 1. lwip - * | empty 30B | eth_hdr (14B) | payload ...| - * total: 44B ahead payload - * 2. net80211 - * | max 80211 hdr, 32B | ccmp/tkip iv (8B) | sec rsv(4B) | payload ...| - * total: 40B ahead sec_rsv and 44B ahead payload - * - */ - offset += EP_OFFSET; //remove LINK hdr in wlan -#endif - break; case PBUF_RAW_TX: /* add room for encapsulating link layer headers (e.g. 802.11) */ @@ -274,10 +232,6 @@ pbuf_alloc(pbuf_layer layer, u16_t length, pbuf_type type) case PBUF_RAW: offset = 0; -#ifdef LWIP_ESP8266 //TO_DO - offset += EP_OFFSET; -#endif - break; default: LWIP_ASSERT("pbuf_alloc: bad pbuf layer", 0); @@ -396,9 +350,10 @@ pbuf_alloc(pbuf_layer layer, u16_t length, pbuf_type type) /* set flags */ p->flags = 0; -#ifdef LWIP_ESP8266 +#if ESP_LWIP p->eb = NULL; #endif + LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_alloc(length=%"U16_F") == %p\n", length, (void *)p)); return p; } @@ -764,7 +719,7 @@ pbuf_free(struct pbuf *p) /* is this a ROM or RAM referencing pbuf? */ } else if (type == PBUF_ROM || type == PBUF_REF) { -#ifdef LWIP_ESP8266 +#if ESP_LWIP if (type == PBUF_REF && p->eb != NULL ) esp_wifi_internal_free_rx_buffer(p->eb); #endif diff --git a/components/lwip/core/raw.c b/components/lwip/core/raw.c index 72a58d381..82ce4e3a7 100755 --- a/components/lwip/core/raw.c +++ b/components/lwip/core/raw.c @@ -54,10 +54,6 @@ #include -#ifdef MEMLEAK_DEBUG -static const char mem_debug_file[] ICACHE_RODATA_ATTR STORE_ATTR = __FILE__; -#endif - /** The list of RAW PCBs */ static struct raw_pcb *raw_pcbs; diff --git a/components/lwip/core/stats.c b/components/lwip/core/stats.c index 77ac3c675..b47ab0b7f 100755 --- a/components/lwip/core/stats.c +++ b/components/lwip/core/stats.c @@ -47,10 +47,6 @@ #include -#ifdef MEMLEAK_DEBUG -static const char mem_debug_file[] ICACHE_RODATA_ATTR STORE_ATTR = __FILE__; -#endif - struct stats_ lwip_stats; #if defined(LWIP_DEBUG) || LWIP_STATS_DISPLAY diff --git a/components/lwip/core/tcp.c b/components/lwip/core/tcp.c index e7ea56103..87ddf5f1a 100755 --- a/components/lwip/core/tcp.c +++ b/components/lwip/core/tcp.c @@ -57,10 +57,6 @@ #include -#ifdef MEMLEAK_DEBUG -static const char mem_debug_file[] ICACHE_RODATA_ATTR STORE_ATTR = __FILE__; -#endif - #ifndef TCP_LOCAL_PORT_RANGE_START /* From http://www.iana.org/assignments/port-numbers: "The Dynamic and/or Private Ports are those from 49152 through 65535" */ @@ -77,14 +73,7 @@ static const char mem_debug_file[] ICACHE_RODATA_ATTR STORE_ATTR = __FILE__; #define TCP_KEEP_INTVL(pcb) TCP_KEEPINTVL_DEFAULT #endif /* LWIP_TCP_KEEPALIVE */ -#ifdef LWIP_ESP8266 -//TO_DO -//char tcp_state_str[12]; -//const char tcp_state_str_rodata[][12] ICACHE_RODATA_ATTR STORE_ATTR = { const char * const tcp_state_str[] = { -#else -const char * const tcp_state_str[] = { -#endif "CLOSED", "LISTEN", "SYN_SENT", @@ -100,27 +89,14 @@ const char * const tcp_state_str[] = { /* last local TCP port */ -#ifdef LWIP_ESP8266 static s16_t tcp_port = TCP_LOCAL_PORT_RANGE_START; -#else -static u16_t tcp_port = TCP_LOCAL_PORT_RANGE_START; -#endif /* Incremented every coarse grained timer shot (typically every 500 ms). */ u32_t tcp_ticks; -#ifdef LWIP_ESP8266 -//TO_DO -//const u8_t tcp_backoff[13] ICACHE_RODATA_ATTR STORE_ATTR ={ 1, 2, 3, 4, 5, 6, 7, 7, 7, 7, 7, 7, 7}; -//const u8_t tcp_persist_backoff[7] ICACHE_RODATA_ATTR STORE_ATTR = { 3, 6, 12, 24, 48, 96, 120 }; - -const u8_t tcp_backoff[13] = { 1, 2, 3, 4, 5, 6, 7, 7, 7, 7, 7, 7, 7}; -const u8_t tcp_persist_backoff[7] = { 3, 6, 12, 24, 48, 96, 120 }; -#else const u8_t tcp_backoff[13] = { 1, 2, 3, 4, 5, 6, 7, 7, 7, 7, 7, 7, 7}; /* Times per slowtmr hits */ const u8_t tcp_persist_backoff[7] = { 3, 6, 12, 24, 48, 96, 120 }; -#endif /* The TCP PCB lists. */ @@ -136,19 +112,9 @@ struct tcp_pcb *tcp_active_pcbs; struct tcp_pcb *tcp_tw_pcbs; /** An array with all (non-temporary) PCB lists, mainly used for smaller code size */ -#ifdef LWIP_ESP8266 -//TO_DO -//struct tcp_pcb ** const tcp_pcb_lists[] ICACHE_RODATA_ATTR STORE_ATTR = {&tcp_listen_pcbs.pcbs, &tcp_bound_pcbs, - // &tcp_active_pcbs, &tcp_tw_pcbs}; struct tcp_pcb ** const tcp_pcb_lists[] = {&tcp_listen_pcbs.pcbs, &tcp_bound_pcbs, &tcp_active_pcbs, &tcp_tw_pcbs}; -#else -struct tcp_pcb ** const tcp_pcb_lists[] = {&tcp_listen_pcbs.pcbs, &tcp_bound_pcbs, - &tcp_active_pcbs, &tcp_tw_pcbs}; -#endif - - u8_t tcp_active_pcbs_changed; /** Timer counter to handle calling slow-timer from tcp_tmr() */ @@ -720,7 +686,7 @@ tcp_new_port(void) again: -#ifdef LWIP_ESP8266 +#if ESP_RANDOM_TCP_PORT tcp_port = system_get_time(); if (tcp_port < 0) tcp_port = LWIP_RAND() - tcp_port; @@ -915,13 +881,7 @@ tcp_slowtmr_start: /* If snd_wnd is zero, use persist timer to send 1 byte probes * instead of using the standard retransmission mechanism. */ -#ifdef LWIP_ESP8266 -//NEED TO DO - //u8_t backoff_cnt = system_get_data_of_array_8(tcp_persist_backoff, pcb->persist_backoff-1); u8_t backoff_cnt = tcp_persist_backoff[pcb->persist_backoff-1]; -#else - u8_t backoff_cnt = tcp_persist_backoff[pcb->persist_backoff-1]; -#endif if (pcb->persist_cnt < backoff_cnt) { pcb->persist_cnt++; @@ -949,15 +909,7 @@ tcp_slowtmr_start: /* Double retransmission time-out unless we are trying to * connect to somebody (i.e., we are in SYN_SENT). */ if (pcb->state != SYN_SENT) { - -#ifdef LWIP_ESP8266 -//TO_DO -// pcb->rto = ((pcb->sa >> 3) + pcb->sv) << system_get_data_of_array_8(tcp_backoff, pcb->nrtx); pcb->rto = ((pcb->sa >> 3) + pcb->sv) << tcp_backoff[pcb->nrtx]; -#else - pcb->rto = ((pcb->sa >> 3) + pcb->sv) << tcp_backoff[pcb->nrtx]; -#endif - } /* Reset the retransmission timer. */ @@ -1436,7 +1388,7 @@ tcp_kill_timewait(void) } } -#ifdef LWIP_ESP8266 +#if ESP_LWIP /** * Kills the oldest connection that is in FIN_WAIT_2 state. * Called from tcp_alloc() if no more connections are available. @@ -1502,7 +1454,7 @@ tcp_alloc(u8_t prio) struct tcp_pcb *pcb; u32_t iss; -#ifdef LWIP_ESP8266 +#if ESP_LWIP /*Kills the oldest connection that is in TIME_WAIT state.*/ u8_t time_wait_num = 0; for(pcb = tcp_tw_pcbs; pcb != NULL; pcb = pcb->next) { @@ -2015,14 +1967,7 @@ void tcp_netif_ipv4_addr_changed(const ip4_addr_t* old_addr, const ip4_addr_t* n const char* tcp_debug_state_str(enum tcp_state s) { -#ifdef LWIP_ESP8266 -//TO_DO - //system_get_string_from_flash(tcp_state_str_rodata[s], tcp_state_str, 12); - //return tcp_state_str; return tcp_state_str[s]; -#else - return tcp_state_str[s]; -#endif } #if TCP_DEBUG || TCP_INPUT_DEBUG || TCP_OUTPUT_DEBUG diff --git a/components/lwip/core/tcp_in.c b/components/lwip/core/tcp_in.c index 90ebf1723..f3284233e 100755 --- a/components/lwip/core/tcp_in.c +++ b/components/lwip/core/tcp_in.c @@ -60,11 +60,6 @@ #include "lwip/nd6.h" #endif /* LWIP_ND6_TCP_REACHABILITY_HINTS */ -#ifdef MEMLEAK_DEBUG -static const char mem_debug_file[] ICACHE_RODATA_ATTR STORE_ATTR = __FILE__; -#endif - - /** Initial CWND calculation as defined RFC 2581 */ #define LWIP_TCP_CALC_INITIAL_CWND(mss) LWIP_MIN((4U * (mss)), LWIP_MAX((2U * (mss)), 4380U)); /** Initial slow start threshold value: we use the full window */ @@ -329,20 +324,6 @@ tcp_input(struct pbuf *p, struct netif *inp) if (pcb != NULL) { - -#ifdef LWIP_ESP8266 -//No Need Any more -/* - extern char RxNodeNum(void); - if(RxNodeNum() <= 2) - { -extern void pbuf_free_ooseq(void); - pbuf_free_ooseq(); - } -*/ -#endif - - /* The incoming segment belongs to a connection. */ #if TCP_INPUT_DEBUG tcp_debug_print_state(pcb->state); diff --git a/components/lwip/core/tcp_out.c b/components/lwip/core/tcp_out.c index 5f10befae..f189623f5 100755 --- a/components/lwip/core/tcp_out.c +++ b/components/lwip/core/tcp_out.c @@ -59,10 +59,6 @@ #include -#ifdef MEMLEAK_DEBUG -static const char mem_debug_file[] ICACHE_RODATA_ATTR STORE_ATTR = __FILE__; -#endif - /* Define some copy-macros for checksum-on-copy so that the code looks nicer by preventing too many ifdef's. */ #if TCP_CHECKSUM_ON_COPY diff --git a/components/lwip/core/timers.c b/components/lwip/core/timers.c index 0a361474e..ef47b2e18 100755 --- a/components/lwip/core/timers.c +++ b/components/lwip/core/timers.c @@ -62,11 +62,6 @@ #include "lwip/sys.h" #include "lwip/pbuf.h" -#ifdef MEMLEAK_DEBUG -static const char mem_debug_file[] ICACHE_RODATA_ATTR STORE_ATTR = __FILE__; -#endif - - /** The one and only timeout list */ static struct sys_timeo *next_timeout; #if NO_SYS @@ -162,7 +157,7 @@ dhcp_timer_coarse(void *arg) LWIP_DEBUGF(TIMERS_DEBUG, ("tcpip: dhcp_coarse_tmr()\n")); dhcp_coarse_tmr(); -#ifdef LWIP_ESP8266 +#if ESP_DHCP extern void dhcps_coarse_tmr(void); dhcps_coarse_tmr(); #endif @@ -294,12 +289,6 @@ void sys_timeouts_init(void) #endif /* LWIP_ARP */ #if LWIP_DHCP -#ifdef LWIP_ESP8266 - // DHCP_MAXRTX = 0; -#endif - - - sys_timeout(DHCP_COARSE_TIMER_MSECS, dhcp_timer_coarse, NULL); sys_timeout(DHCP_FINE_TIMER_MSECS, dhcp_timer_fine, NULL); #endif /* LWIP_DHCP */ @@ -346,7 +335,7 @@ void sys_timeout_debug(u32_t msecs, sys_timeout_handler handler, void *arg, const char* handler_name) #else /* LWIP_DEBUG_TIMERNAMES */ -#ifdef LWIP_ESP8266 +#if ESP_LIGHT_SLEEP u32_t LwipTimOutLim = 0; // For light sleep. time out. limit is 3000ms #endif @@ -379,7 +368,7 @@ sys_timeout(u32_t msecs, sys_timeout_handler handler, void *arg) timeout->h = handler; timeout->arg = arg; -#ifdef LWIP_ESP8266 +#if ESP_LIGHT_SLEEP if(msecs < LwipTimOutLim) msecs = LwipTimOutLim; #endif diff --git a/components/lwip/core/udp.c b/components/lwip/core/udp.c index e44ab7e73..37ae2c179 100755 --- a/components/lwip/core/udp.c +++ b/components/lwip/core/udp.c @@ -67,11 +67,6 @@ #include -#ifdef MEMLEAK_DEBUG -static const char mem_debug_file[] ICACHE_RODATA_ATTR STORE_ATTR = __FILE__; -#endif - - #ifndef UDP_LOCAL_PORT_RANGE_START /* From http://www.iana.org/assignments/port-numbers: "The Dynamic and/or Private Ports are those from 49152 through 65535" */ diff --git a/components/lwip/include/lwip/lwip/api.h b/components/lwip/include/lwip/lwip/api.h index 985eb76d4..5b6a21ecf 100755 --- a/components/lwip/include/lwip/lwip/api.h +++ b/components/lwip/include/lwip/lwip/api.h @@ -185,10 +185,6 @@ struct netconn { /** sem that is used to synchronously execute functions in the core context */ sys_sem_t op_completed; -#ifdef LWIP_ESP8266 - sys_sem_t snd_op_completed; //only for snd semphore -#endif - #endif /** mbox where received packets are stored until they are fetched diff --git a/components/lwip/include/lwip/lwip/dhcp.h b/components/lwip/include/lwip/lwip/dhcp.h index 2d8926eca..76ce1543f 100755 --- a/components/lwip/include/lwip/lwip/dhcp.h +++ b/components/lwip/include/lwip/lwip/dhcp.h @@ -249,7 +249,7 @@ void dhcp_fine_tmr(void); #define DHCP_OPTION_NTP 42 #define DHCP_OPTION_END 255 -#ifdef LWIP_ESP8266 +#if ESP_LWIP /**add options for support more router by liuHan**/ #define DHCP_OPTION_DOMAIN_NAME 15 #define DHCP_OPTION_PRD 31 diff --git a/components/lwip/include/lwip/lwip/dns.h b/components/lwip/include/lwip/lwip/dns.h index 1ceed0d88..5ef12e56c 100755 --- a/components/lwip/include/lwip/lwip/dns.h +++ b/components/lwip/include/lwip/lwip/dns.h @@ -36,7 +36,7 @@ #include "lwip/opt.h" -#ifdef LWIP_ESP8266 +#if ESP_DNS #include "lwip/err.h" #endif diff --git a/components/lwip/include/lwip/lwip/err.h b/components/lwip/include/lwip/lwip/err.h index 26fb91db9..a766ee186 100755 --- a/components/lwip/include/lwip/lwip/err.h +++ b/components/lwip/include/lwip/lwip/err.h @@ -60,7 +60,7 @@ typedef s8_t err_t; #define ERR_USE -8 /* Address in use. */ -#ifdef LWIP_ESP8266 +#if ESP_LWIP #define ERR_ALREADY -9 /* Already connected. */ #define ERR_ISCONN -10 /* Conn already established.*/ #define ERR_IS_FATAL(e) ((e) < ERR_ISCONN) diff --git a/components/lwip/include/lwip/lwip/mem.h b/components/lwip/include/lwip/lwip/mem.h index ca76f6632..a90d07256 100755 --- a/components/lwip/include/lwip/lwip/mem.h +++ b/components/lwip/include/lwip/lwip/mem.h @@ -51,8 +51,6 @@ typedef size_t mem_size_t; * allow these defines to be overridden. */ -#ifndef MEMLEAK_DEBUG - #ifndef mem_free #define mem_free free #endif @@ -63,41 +61,6 @@ typedef size_t mem_size_t; #define mem_calloc calloc #endif -/* DYC_NEED_TO_DO_LATER -#ifndef mem_realloc -#define mem_realloc -#endif -#ifndef mem_zalloc -#define mem_zalloc -#endif -*/ - -#else -/* -#ifndef mem_free -#define mem_free(s) \ - do{\ - const char *file = mem_debug_file;\ - vPortFree(s, file, __LINE__);\ - }while(0) -#endif -#ifndef mem_malloc -#define mem_malloc(s) ({const char *file = mem_debug_file; pvPortMalloc(s, file, __LINE__);}) -#endif -#ifndef mem_calloc -#define mem_calloc(s) ({const char *file = mem_debug_file; pvPortCalloc(s, file, __LINE__);}) -#endif -#ifndef mem_realloc -#define mem_realloc(p, s) ({const char *file = mem_debug_file; pvPortRealloc(p, s, file, __LINE__);}) -#endif -#ifndef mem_zalloc -#define mem_zalloc(s) ({const char *file = mem_debug_file; pvPortZalloc(s, file, __LINE__);}) -#endif -*/ -#endif - - - /* Since there is no C library allocation function to shrink memory without moving it, define this to nothing. */ #ifndef mem_trim diff --git a/components/lwip/include/lwip/lwip/netif.h b/components/lwip/include/lwip/lwip/netif.h index 99066a5a1..666f77eb9 100755 --- a/components/lwip/include/lwip/lwip/netif.h +++ b/components/lwip/include/lwip/lwip/netif.h @@ -177,7 +177,7 @@ typedef err_t (*netif_mld_mac_filter_fn)(struct netif *netif, #endif /* LWIP_IPV6 && LWIP_IPV6_MLD */ -#ifdef LWIP_ESP8266 +#if ESP_DHCP /*add DHCP event processing by LiuHan*/ typedef void (*dhcp_event_fn)(void); #endif @@ -190,7 +190,7 @@ struct netif { /** pointer to next in linked list */ struct netif *next; -#ifdef LWIP_ESP8266 +#if ESP_LWIP //ip_addr_t is changed by marco IPV4, IPV6 ip_addr_t link_local_addr; #endif @@ -248,7 +248,7 @@ struct netif { /** the DHCP client state information for this netif */ struct dhcp *dhcp; -#ifdef LWIP_ESP8266 +#if ESP_LWIP struct udp_pcb *dhcps_pcb; dhcp_event_fn dhcp_event; #endif diff --git a/components/lwip/include/lwip/lwip/opt.h b/components/lwip/include/lwip/lwip/opt.h index c286712e0..51d340e00 100755 --- a/components/lwip/include/lwip/lwip/opt.h +++ b/components/lwip/include/lwip/lwip/opt.h @@ -3008,8 +3008,8 @@ #define LWIP_PERF 0 #endif -#ifndef THREAD_SAFE_DEBUG -#define THREAD_SAFE_DEBUG 0 +#ifndef ESP_THREAD_SAFE_DEBUG +#define ESP_THREAD_SAFE_DEBUG 0 #endif #endif /* LWIP_HDR_OPT_H */ diff --git a/components/lwip/include/lwip/lwip/pbuf.h b/components/lwip/include/lwip/lwip/pbuf.h index aaf5e294a..1834c4e04 100755 --- a/components/lwip/include/lwip/lwip/pbuf.h +++ b/components/lwip/include/lwip/lwip/pbuf.h @@ -137,7 +137,7 @@ struct pbuf { */ u16_t ref; -#ifdef LWIP_ESP8266 +#if ESP_LWIP void *eb; #endif }; diff --git a/components/lwip/include/lwip/lwip/priv/api_msg.h b/components/lwip/include/lwip/lwip/priv/api_msg.h index 329fa0de3..02d191a53 100755 --- a/components/lwip/include/lwip/lwip/priv/api_msg.h +++ b/components/lwip/include/lwip/lwip/priv/api_msg.h @@ -187,7 +187,7 @@ struct dns_api_msg { #endif /* LWIP_DNS */ #if LWIP_NETCONN_SEM_PER_THREAD -#ifdef LWIP_ESP8266 +#if ESP_THREAD_SAFE #define LWIP_NETCONN_THREAD_SEM_GET() sys_thread_sem_get() #define LWIP_NETCONN_THREAD_SEM_ALLOC() sys_thread_sem_init() #define LWIP_NETCONN_THREAD_SEM_FREE() sys_thread_sem_deinit() @@ -222,10 +222,6 @@ struct dns_api_msg { #define TCPIP_APIMSG(m,f,e) do { (m)->function = f; (e) = tcpip_apimsg(m); } while(0) #define TCPIP_APIMSG_ACK(m) do { NETCONN_SET_SAFE_ERR((m)->conn, (m)->err); sys_sem_signal(LWIP_API_MSG_SEM(m)); } while(0) -#ifdef LWIP_ESP8266 -#define TCPIP_APIMSG_ACK_SND(m) do { NETCONN_SET_SAFE_ERR((m)->conn, (m)->err); sys_sem_signal(LWIP_API_MSG_SND_SEM(m)); } while(0) -#endif - #endif /* LWIP_TCPIP_CORE_LOCKING */ void lwip_netconn_do_newconn (void *m); diff --git a/components/lwip/include/lwip/lwip/sockets.h b/components/lwip/include/lwip/lwip/sockets.h index fc2b7e2d1..d9622ea03 100755 --- a/components/lwip/include/lwip/lwip/sockets.h +++ b/components/lwip/include/lwip/lwip/sockets.h @@ -509,7 +509,7 @@ int lwip_fcntl(int s, int cmd, int val); #if LWIP_COMPAT_SOCKETS #if LWIP_COMPAT_SOCKETS != 2 -#if LWIP_THREAD_SAFE +#if ESP_THREAD_SAFE int lwip_accept_r(int s, struct sockaddr *addr, socklen_t *addrlen); int lwip_bind_r(int s, const struct sockaddr *name, socklen_t namelen); @@ -594,7 +594,7 @@ int lwip_fcntl_r(int s, int cmd, int val); #define fcntl(s,cmd,val) lwip_fcntl(s,cmd,val) #define ioctl(s,cmd,argp) lwip_ioctl(s,cmd,argp) #endif /* LWIP_POSIX_SOCKETS_IO_NAMES */ -#endif /* LWIP_THREAD_SAFE */ +#endif /* ESP_THREAD_SAFE */ #endif /* LWIP_COMPAT_SOCKETS != 2 */ diff --git a/components/lwip/include/lwip/port/lwipopts.h b/components/lwip/include/lwip/port/lwipopts.h index 5667e2d20..75f349280 100755 --- a/components/lwip/include/lwip/port/lwipopts.h +++ b/components/lwip/include/lwip/port/lwipopts.h @@ -37,7 +37,6 @@ #include "sdkconfig.h" /* Enable all Espressif-only options */ -#define LWIP_ESP8266 /* ----------------------------------------------- @@ -510,14 +509,42 @@ extern unsigned char misc_prof_get_tcp_snd_buf(void); */ #define TCPIP_DEBUG LWIP_DBG_OFF +/* Enable all Espressif-only options */ + +#define ESP_LWIP 1 +#define ESP_PER_SOC_TCP_WND 1 +#define ESP_THREAD_SAFE 1 +#define ESP_THREAD_SAFE_DEBUG LWIP_DBG_OFF +#define ESP_DHCP 1 +#define ESP_DNS 1 +#define ESP_IPV6_AUTOCONFIG 1 +#define ESP_PERF 0 +#define ESP_RANDOM_TCP_PORT 1 +#define ESP_IP4_ATON 1 +#define ESP_LIGHT_SLEEP 1 + + +#if ESP_PER_SOC_TCP_WND +#define TCP_WND_DEFAULT (4*TCP_MSS) +#define TCP_SND_BUF_DEFAULT (2*TCP_MSS) +#define TCP_WND(pcb) (pcb->per_soc_tcp_wnd) +#define TCP_SND_BUF(pcb) (pcb->per_soc_tcp_snd_buf) +#else +#if ESP_PERF +extern unsigned char misc_prof_get_tcpw(void); +extern unsigned char misc_prof_get_tcp_snd_buf(void); +#define TCP_WND(pcb) (misc_prof_get_tcpw()*TCP_MSS) +#define TCP_SND_BUF(pcb) (misc_prof_get_tcp_snd_buf()*TCP_MSS) +#endif +#endif + /** * DHCP_DEBUG: Enable debugging in dhcp.c. */ #define DHCP_DEBUG LWIP_DBG_OFF #define LWIP_DEBUG 0 #define TCP_DEBUG LWIP_DBG_OFF -#define THREAD_SAFE_DEBUG LWIP_DBG_OFF -#define LWIP_THREAD_SAFE 1 +#define ESP_THREAD_SAFE_DEBUG LWIP_DBG_OFF #define CHECKSUM_CHECK_UDP 0 #define CHECKSUM_CHECK_IP 0 diff --git a/components/lwip/netif/etharp.c b/components/lwip/netif/etharp.c index 5891c5cfd..776e949f7 100755 --- a/components/lwip/netif/etharp.c +++ b/components/lwip/netif/etharp.c @@ -55,10 +55,6 @@ #include -#ifdef MEMLEAK_DEBUG -static const char mem_debug_file[] ICACHE_RODATA_ATTR STORE_ATTR = __FILE__; -#endif - #if LWIP_IPV4 && LWIP_ARP /* don't build if not configured for use in lwipopts.h */ /** Re-request a used ARP entry 1 minute before it would expire to prevent diff --git a/components/lwip/port/freertos/sys_arch.c b/components/lwip/port/freertos/sys_arch.c index 74a4a996a..15ba3011d 100755 --- a/components/lwip/port/freertos/sys_arch.c +++ b/components/lwip/port/freertos/sys_arch.c @@ -56,7 +56,7 @@ sys_mutex_new(sys_mutex_t *pxMutex) xReturn = ERR_OK; } - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("sys_mutex_new: m=%p\n", *pxMutex)); + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("sys_mutex_new: m=%p\n", *pxMutex)); return xReturn; } @@ -89,7 +89,7 @@ sys_mutex_unlock(sys_mutex_t *pxMutex) void sys_mutex_free(sys_mutex_t *pxMutex) { - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("sys_mutex_free: m=%p\n", *pxMutex)); + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("sys_mutex_free: m=%p\n", *pxMutex)); vQueueDelete(*pxMutex); } #endif @@ -192,20 +192,20 @@ sys_mbox_new(sys_mbox_t *mbox, int size) { *mbox = malloc(sizeof(struct sys_mbox_s)); if (*mbox == NULL){ - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("fail to new *mbox\n")); + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("fail to new *mbox\n")); return ERR_MEM; } (*mbox)->os_mbox = xQueueCreate(size, sizeof(void *)); if ((*mbox)->os_mbox == NULL) { - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("fail to new *mbox->os_mbox\n")); + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("fail to new *mbox->os_mbox\n")); free(*mbox); return ERR_MEM; } if (sys_mutex_new(&((*mbox)->lock)) != ERR_OK){ - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("fail to new *mbox->lock\n")); + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("fail to new *mbox->lock\n")); vQueueDelete((*mbox)->os_mbox); free(*mbox); return ERR_MEM; @@ -213,7 +213,7 @@ sys_mbox_new(sys_mbox_t *mbox, int size) (*mbox)->alive = true; - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("new *mbox ok mbox=%p os_mbox=%p mbox_lock=%p\n", *mbox, (*mbox)->os_mbox, (*mbox)->lock)); + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("new *mbox ok mbox=%p os_mbox=%p mbox_lock=%p\n", *mbox, (*mbox)->os_mbox, (*mbox)->lock)); return ERR_OK; } @@ -234,7 +234,7 @@ sys_mbox_trypost(sys_mbox_t *mbox, void *msg) if (xQueueSend((*mbox)->os_mbox, &msg, (portTickType)0) == pdPASS) { xReturn = ERR_OK; } else { - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("trypost mbox=%p fail\n", (*mbox)->os_mbox)); + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("trypost mbox=%p fail\n", (*mbox)->os_mbox)); xReturn = ERR_MEM; } @@ -271,7 +271,7 @@ sys_arch_mbox_fetch(sys_mbox_t *mbox, void **msg, u32_t timeout) if (*mbox == NULL){ *msg = NULL; - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("sys_arch_mbox_fetch: null mbox\n")); + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("sys_arch_mbox_fetch: null mbox\n")); return -1; } @@ -294,14 +294,14 @@ sys_arch_mbox_fetch(sys_mbox_t *mbox, void **msg, u32_t timeout) } else { // block forever for a message. while (1){ - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("sys_arch_mbox_fetch: fetch mbox=%p os_mbox=%p lock=%p\n", mbox, (*mbox)->os_mbox, (*mbox)->lock)); + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("sys_arch_mbox_fetch: fetch mbox=%p os_mbox=%p lock=%p\n", mbox, (*mbox)->os_mbox, (*mbox)->lock)); if (pdTRUE == xQueueReceive((*mbox)->os_mbox, &(*msg), portMAX_DELAY)){ - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("sys_arch_mbox_fetch:mbox rx msg=%p\n", (*msg))); + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("sys_arch_mbox_fetch:mbox rx msg=%p\n", (*msg))); break; } if ((*mbox)->alive == false){ - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("sys_arch_mbox_fetch:mbox not alive\n")); + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("sys_arch_mbox_fetch:mbox not alive\n")); *msg = NULL; break; } @@ -356,24 +356,24 @@ sys_mbox_free(sys_mbox_t *mbox) uint16_t count = 0; bool post_null = true; - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("sys_mbox_free: set alive false\n")); + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("sys_mbox_free: set alive false\n")); (*mbox)->alive = false; while ( count++ < MAX_POLL_CNT ){ //ESP32_WORKAROUND - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("sys_mbox_free:try lock=%d\n", count)); + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("sys_mbox_free:try lock=%d\n", count)); if (!sys_mutex_trylock( &(*mbox)->lock )){ - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("sys_mbox_free:get lock ok %d\n", count)); + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("sys_mbox_free:get lock ok %d\n", count)); sys_mutex_unlock( &(*mbox)->lock ); break; } if (post_null){ - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("sys_mbox_free: post null to mbox\n")); + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("sys_mbox_free: post null to mbox\n")); if (sys_mbox_trypost( mbox, NULL) != ERR_OK){ - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("sys_mbox_free: post null mbox fail\n")); + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("sys_mbox_free: post null mbox fail\n")); } else { post_null = false; - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("sys_mbox_free: post null mbox ok\n")); + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("sys_mbox_free: post null mbox ok\n")); } } @@ -383,7 +383,7 @@ sys_mbox_free(sys_mbox_t *mbox) sys_delay_ms(PER_POLL_DELAY); } - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("sys_mbox_free:free mbox\n")); + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("sys_mbox_free:free mbox\n")); if (uxQueueMessagesWaiting((*mbox)->os_mbox)) { xQueueReset((*mbox)->os_mbox); @@ -491,7 +491,7 @@ sys_sem_t* sys_thread_sem_get(void) if (!sem){ sem = sys_thread_sem_init(); } - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("sem_get s=%p\n", sem)); + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("sem_get s=%p\n", sem)); return sem; } @@ -500,12 +500,12 @@ static void sys_thread_tls_free(int index, void* data) sys_sem_t *sem = (sys_sem_t*)(data); if (sem && *sem){ - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("sem del, i=%d sem=%p\n", index, *sem)); + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("sem del, i=%d sem=%p\n", index, *sem)); vSemaphoreDelete(*sem); } if (sem){ - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("sem pointer del, i=%d sem_p=%p\n", index, sem)); + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("sem pointer del, i=%d sem_p=%p\n", index, sem)); free(sem); } } @@ -526,7 +526,7 @@ sys_sem_t* sys_thread_sem_init(void) return 0; } - LWIP_DEBUGF(THREAD_SAFE_DEBUG, ("sem init sem_p=%p sem=%p cb=%p\n", sem, *sem, sys_thread_tls_free)); + LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("sem init sem_p=%p sem=%p cb=%p\n", sem, *sem, sys_thread_tls_free)); vTaskSetThreadLocalStoragePointerAndDelCallback(xTaskGetCurrentTaskHandle(), SYS_TLS_INDEX, sem, (TlsDeleteCallbackFunction_t)sys_thread_tls_free); return sem; diff --git a/components/lwip/port/netif/wlanif.c b/components/lwip/port/netif/wlanif.c index 0b4fe70ba..548bb7f97 100755 --- a/components/lwip/port/netif/wlanif.c +++ b/components/lwip/port/netif/wlanif.c @@ -56,16 +56,8 @@ #define IFNAME0 'e' #define IFNAME1 'n' -#ifdef LWIP_ESP8266 -//TO_DO -//char *hostname; -//bool default_hostname = 1; - static char hostname[16]; -#else -static char hostname[16]; -#endif -#ifdef PERF +#if ESP_PERF uint32_t g_rx_alloc_pbuf_fail_cnt = 0; #endif @@ -95,7 +87,7 @@ low_level_init(struct netif *netif) /* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */ netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP; -#ifdef LWIP_ESP8266 +#if ESP_LWIP #if LWIP_IGMP @@ -133,7 +125,7 @@ low_level_output(struct netif *netif, struct pbuf *p) return ERR_IF; } -#ifdef LWIP_ESP8266 +#if ESP_LWIP q = p; u16_t pbuf_x_len = 0; pbuf_x_len = q->len; @@ -172,7 +164,7 @@ low_level_output(struct netif *netif, struct pbuf *p) * @param netif the lwip network interface structure for this ethernetif */ void -#ifdef LWIP_ESP8266 +#if ESP_LWIP wlanif_input(struct netif *netif, void *buffer, u16_t len, void* eb) #else wlanif_input(struct netif *netif, void *buffer, uint16 len) @@ -180,17 +172,17 @@ wlanif_input(struct netif *netif, void *buffer, uint16 len) { struct pbuf *p; -#ifdef LWIP_ESP8266 +#if ESP_LWIP if(buffer== NULL) goto _exit; if(netif == NULL) goto _exit; #endif -#ifdef LWIP_ESP8266 +#if ESP_LWIP p = pbuf_alloc(PBUF_RAW, len, PBUF_REF); if (p == NULL){ -#ifdef PERF +#if ESP_PERF g_rx_alloc_pbuf_fail_cnt++; #endif return; @@ -236,7 +228,7 @@ wlanif_init(struct netif *netif) #if LWIP_NETIF_HOSTNAME /* Initialize interface hostname */ -#ifdef LWIP_ESP8266 +#if ESP_LWIP //TO_DO /* if ((struct netif *)wifi_get_netif(STATION_IF) == netif) { From 3371083c1611fb7a5b0f389739599146af51047c Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Thu, 27 Oct 2016 16:07:47 +0800 Subject: [PATCH 076/149] Add checks for tasks woken up on other CPUs where needed, make xYieldPending and xPendingReadyList per-processor, add configurable ISR stack size to Kconfig, in general fix the entire wake-up-task-on-other-cpu-by-interrupt implementation --- components/esp32/crosscore_int.c | 90 +++++++++---------- components/esp32/include/esp_crosscore_int.h | 27 ++++++ components/freertos/Kconfig | 9 ++ .../include/freertos/FreeRTOSConfig.h | 2 +- components/freertos/tasks.c | 70 ++++++++------- 5 files changed, 117 insertions(+), 81 deletions(-) diff --git a/components/esp32/crosscore_int.c b/components/esp32/crosscore_int.c index a98b13583..56fe6fe9a 100644 --- a/components/esp32/crosscore_int.c +++ b/components/esp32/crosscore_int.c @@ -44,64 +44,60 @@ ToDo: There is a small chance the CPU already has yielded when this ISR is servi the ISR will cause it to switch _away_ from it. portYIELD_FROM_ISR will probably just schedule the task again, but have to check that. */ static void esp_crosscore_isr(void *arg) { - volatile uint32_t myReasonVal; + volatile uint32_t myReasonVal; #if 0 - //A pointer to the correct reason array item is passed to this ISR. - volatile uint32_t *myReason=arg; + //A pointer to the correct reason array item is passed to this ISR. + volatile uint32_t *myReason=arg; #else - //Does not work yet, the interrupt code needs work to understand two separate interrupt and argument - //tables... - volatile uint32_t *myReason=&reason[xPortGetCoreID()]; + //Does not work yet, the interrupt code needs work to understand two separate interrupt and argument + //tables... + volatile uint32_t *myReason=&reason[xPortGetCoreID()]; #endif - //Clear the interrupt first. - if (xPortGetCoreID()==0) { - WRITE_PERI_REG(DPORT_CPU_INTR_FROM_CPU_0_REG, 0); - } else { - WRITE_PERI_REG(DPORT_CPU_INTR_FROM_CPU_1_REG, 0); - } - //Grab the reason and clear it. - portENTER_CRITICAL(&reasonSpinlock); - myReasonVal=*myReason; - *myReason=0; - portEXIT_CRITICAL(&reasonSpinlock); + //Clear the interrupt first. + if (xPortGetCoreID()==0) { + WRITE_PERI_REG(DPORT_CPU_INTR_FROM_CPU_0_REG, 0); + } else { + WRITE_PERI_REG(DPORT_CPU_INTR_FROM_CPU_1_REG, 0); + } + //Grab the reason and clear it. + portENTER_CRITICAL(&reasonSpinlock); + myReasonVal=*myReason; + *myReason=0; + portEXIT_CRITICAL(&reasonSpinlock); - //Check what we need to do. - if (myReasonVal&REASON_YIELD) { - portYIELD_FROM_ISR(); - } - - ets_printf("recv yield\n"); + //Check what we need to do. + if (myReasonVal&REASON_YIELD) { + portYIELD_FROM_ISR(); + } } //Initialize the crosscore interrupt on this core. Call this once //on each active core. void esp_crosscore_int_init() { - portENTER_CRITICAL(&reasonSpinlock); - ets_printf("init cpu %d\n", xPortGetCoreID()); - reason[xPortGetCoreID()]=0; - portEXIT_CRITICAL(&reasonSpinlock); - ESP_INTR_DISABLE(ETS_FROM_CPU_INUM); - if (xPortGetCoreID()==0) { - intr_matrix_set(xPortGetCoreID(), ETS_FROM_CPU_INTR0_SOURCE, ETS_FROM_CPU_INUM); - } else { - intr_matrix_set(xPortGetCoreID(), ETS_FROM_CPU_INTR1_SOURCE, ETS_FROM_CPU_INUM); - } - xt_set_interrupt_handler(ETS_FROM_CPU_INUM, esp_crosscore_isr, (void*)&reason[xPortGetCoreID()]); - ESP_INTR_ENABLE(ETS_FROM_CPU_INUM); + portENTER_CRITICAL(&reasonSpinlock); + reason[xPortGetCoreID()]=0; + portEXIT_CRITICAL(&reasonSpinlock); + ESP_INTR_DISABLE(ETS_FROM_CPU_INUM); + if (xPortGetCoreID()==0) { + intr_matrix_set(xPortGetCoreID(), ETS_FROM_CPU_INTR0_SOURCE, ETS_FROM_CPU_INUM); + } else { + intr_matrix_set(xPortGetCoreID(), ETS_FROM_CPU_INTR1_SOURCE, ETS_FROM_CPU_INUM); + } + xt_set_interrupt_handler(ETS_FROM_CPU_INUM, esp_crosscore_isr, (void*)&reason[xPortGetCoreID()]); + ESP_INTR_ENABLE(ETS_FROM_CPU_INUM); } void esp_crosscore_int_send_yield(int coreId) { - ets_printf("send yield\n"); - assert(coreIduxPriority < uxPriority ) + No mux here, uxPriority is mostly atomic and there's not really any harm if this check misfires. + */ + if( tskCAN_RUN_HERE( xCoreID ) && pxCurrentTCB[ xPortGetCoreID() ]->uxPriority < uxPriority ) { taskYIELD_IF_USING_PREEMPTION(); } + else if( xCoreID != xPortGetCoreID() ) { + taskYIELD_OTHER_CORE(xCoreID, uxPriority); + } else { mtCOVERAGE_TEST_MARKER(); @@ -1452,7 +1450,7 @@ BaseType_t i; if( listIS_CONTAINED_WITHIN( &xSuspendedTaskList, &( pxTCB->xGenericListItem ) ) != pdFALSE ) { /* Has the task already been resumed from within an ISR? */ - if( listIS_CONTAINED_WITHIN( &xPendingReadyList, &( pxTCB->xEventListItem ) ) == pdFALSE ) + if( listIS_CONTAINED_WITHIN( &xPendingReadyList[ xPortGetCoreID() ], &( pxTCB->xEventListItem ) ) == pdFALSE ) { /* Is it in the suspended list because it is in the Suspended state, or because is is blocked with no timeout? */ @@ -1544,7 +1542,6 @@ BaseType_t i; #if ( ( INCLUDE_xTaskResumeFromISR == 1 ) && ( INCLUDE_vTaskSuspend == 1 ) ) -/* ToDo: Make this multicore-compatible. */ BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume ) { BaseType_t xYieldRequired = pdFALSE; @@ -1567,14 +1564,14 @@ BaseType_t i; ( void ) uxListRemove( &( pxTCB->xGenericListItem ) ); prvAddTaskToReadyList( pxTCB ); - if ( pxTCB->xCoreID == xPortGetCoreID() ) - { - taskYIELD_OTHER_CORE( pxTCB->xCoreID, pxTCB->uxPriority); - } - else if( pxTCB->uxPriority >= pxCurrentTCB[ xPortGetCoreID() ]->uxPriority ) + if( tskCAN_RUN_HERE( pxTCB->xCoreID ) && pxTCB->uxPriority >= pxCurrentTCB[ xPortGetCoreID() ]->uxPriority ) { xYieldRequired = pdTRUE; } + else if ( pxTCB->xCoreID != xPortGetCoreID() ) + { + taskYIELD_OTHER_CORE( pxTCB->xCoreID, pxTCB->uxPriority); + } else { mtCOVERAGE_TEST_MARKER(); @@ -1585,7 +1582,7 @@ BaseType_t i; /* The delayed or ready lists cannot be accessed so the task is held in the pending ready list until the scheduler is unsuspended. */ - vListInsertEnd( &( xPendingReadyList ), &( pxTCB->xEventListItem ) ); + vListInsertEnd( &( xPendingReadyList[ xPortGetCoreID() ] ), &( pxTCB->xEventListItem ) ); } } else @@ -1770,9 +1767,9 @@ BaseType_t xAlreadyYielded = pdFALSE; { /* Move any readied tasks from the pending list into the appropriate ready list. */ - while( listLIST_IS_EMPTY( &xPendingReadyList ) == pdFALSE ) + while( listLIST_IS_EMPTY( &xPendingReadyList[ xPortGetCoreID() ] ) == pdFALSE ) { - pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &xPendingReadyList ) ); + pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &xPendingReadyList[ xPortGetCoreID() ] ) ); ( void ) uxListRemove( &( pxTCB->xEventListItem ) ); ( void ) uxListRemove( &( pxTCB->xGenericListItem ) ); prvAddTaskToReadyList( pxTCB ); @@ -1785,10 +1782,6 @@ BaseType_t xAlreadyYielded = pdFALSE; xYieldPending[xPortGetCoreID()] = pdTRUE; break; } - else if ( pxTCB->xCoreID != xPortGetCoreID() ) - { - taskYIELD_OTHER_CORE( pxTCB->xCoreID, pxTCB->uxPriority ); - } else { mtCOVERAGE_TEST_MARKER(); @@ -2658,15 +2651,20 @@ BaseType_t xReturn; /* The delayed and ready lists cannot be accessed, so hold this task pending until the scheduler is resumed. */ taskENTER_CRITICAL(&xTaskQueueMutex); - vListInsertEnd( &( xPendingReadyList ), &( pxUnblockedTCB->xEventListItem ) ); + vListInsertEnd( &( xPendingReadyList[ xPortGetCoreID() ] ), &( pxUnblockedTCB->xEventListItem ) ); taskEXIT_CRITICAL(&xTaskQueueMutex); } if ( tskCAN_RUN_HERE(pxUnblockedTCB->xCoreID) && pxUnblockedTCB->uxPriority >= pxCurrentTCB[ xPortGetCoreID() ]->uxPriority ) { - /* We can schedule the awoken task on this CPU. */ - xYieldPending[xPortGetCoreID()] = pdTRUE; + /* Return true if the task removed from the event list has a higher + priority than the calling task. This allows the calling task to know if + it should force a context switch now. */ xReturn = pdTRUE; + + /* Mark that a yield is pending in case the user is not using the + "xHigherPriorityTaskWoken" parameter to an ISR safe FreeRTOS function. */ + xYieldPending[ xPortGetCoreID() ] = pdTRUE; } else if ( pxUnblockedTCB->xCoreID != xPortGetCoreID() ) { @@ -2724,9 +2722,15 @@ BaseType_t xReturn; if ( tskCAN_RUN_HERE(pxUnblockedTCB->xCoreID) && pxUnblockedTCB->uxPriority >= pxCurrentTCB[ xPortGetCoreID() ]->uxPriority ) { - /* We can schedule the awoken task on this CPU. */ - xYieldPending[xPortGetCoreID()] = pdTRUE; + /* Return true if the task removed from the event list has + a higher priority than the calling task. This allows + the calling task to know if it should force a context + switch now. */ xReturn = pdTRUE; + + /* Mark that a yield is pending in case the user is not using the + "xHigherPriorityTaskWoken" parameter to an ISR safe FreeRTOS function. */ + xYieldPending[ xPortGetCoreID() ] = pdTRUE; } else if ( pxUnblockedTCB->xCoreID != xPortGetCoreID() ) { @@ -2967,7 +2971,7 @@ static portTASK_FUNCTION( prvIdleTask, pvParameters ) eSleepModeStatus eReturn = eStandardSleep; taskENTER_CRITICAL(&xTaskQueueMutex); - if( listCURRENT_LIST_LENGTH( &xPendingReadyList ) != 0 ) + if( listCURRENT_LIST_LENGTH( &xPendingReadyList[ xPortGetCoreID() ] ) != 0 ) { /* A task was made ready while the scheduler was suspended. */ eReturn = eAbortSleep; @@ -3210,7 +3214,7 @@ UBaseType_t uxPriority; vListInitialise( &xDelayedTaskList1 ); vListInitialise( &xDelayedTaskList2 ); - vListInitialise( &xPendingReadyList ); + vListInitialise( &xPendingReadyList[ xPortGetCoreID() ] ); #if ( INCLUDE_vTaskDelete == 1 ) { @@ -4576,7 +4580,7 @@ TickType_t uxReturn; { /* The delayed and ready lists cannot be accessed, so hold this task pending until the scheduler is resumed. */ - vListInsertEnd( &( xPendingReadyList ), &( pxTCB->xEventListItem ) ); + vListInsertEnd( &( xPendingReadyList[ xPortGetCoreID() ] ), &( pxTCB->xEventListItem ) ); } if( tskCAN_RUN_HERE(pxTCB->xCoreID) && pxTCB->uxPriority > pxCurrentTCB[ xPortGetCoreID() ]->uxPriority ) @@ -4644,7 +4648,7 @@ TickType_t uxReturn; { /* The delayed and ready lists cannot be accessed, so hold this task pending until the scheduler is resumed. */ - vListInsertEnd( &( xPendingReadyList ), &( pxTCB->xEventListItem ) ); + vListInsertEnd( &( xPendingReadyList[ xPortGetCoreID() ] ), &( pxTCB->xEventListItem ) ); } if( tskCAN_RUN_HERE(pxTCB->xCoreID) && pxTCB->uxPriority > pxCurrentTCB[ xPortGetCoreID() ]->uxPriority ) From 68f39c1ed90bc948ad46af74869d26f5bef9806c Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Thu, 27 Oct 2016 16:50:28 +0800 Subject: [PATCH 077/149] Only init crosscore when FreeRTOS runs in multicore mode, add warnings that cross_int calls are private. --- components/esp32/cpu_start.c | 2 ++ components/esp32/crosscore_int.c | 4 ++-- components/esp32/include/esp_crosscore_int.h | 7 +++++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/components/esp32/cpu_start.c b/components/esp32/cpu_start.c index 0c94f39e3..bfc751d45 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -147,7 +147,9 @@ void start_cpu0_default(void) uart_div_modify(0, (APB_CLK_FREQ << 4) / 115200); ets_setup_syscalls(); do_global_ctors(); +#if !CONFIG_FREERTOS_UNICORE esp_crosscore_int_init(); +#endif esp_ipc_init(); spi_flash_init(); xTaskCreatePinnedToCore(&main_task, "main", diff --git a/components/esp32/crosscore_int.c b/components/esp32/crosscore_int.c index 56fe6fe9a..78287d405 100644 --- a/components/esp32/crosscore_int.c +++ b/components/esp32/crosscore_int.c @@ -49,8 +49,8 @@ static void esp_crosscore_isr(void *arg) { //A pointer to the correct reason array item is passed to this ISR. volatile uint32_t *myReason=arg; #else - //Does not work yet, the interrupt code needs work to understand two separate interrupt and argument - //tables... + //The previous line does not work yet, the interrupt code needs work to understand two separate interrupt and argument + //tables... this is a valid but slightly less optimal replacement. volatile uint32_t *myReason=&reason[xPortGetCoreID()]; #endif //Clear the interrupt first. diff --git a/components/esp32/include/esp_crosscore_int.h b/components/esp32/include/esp_crosscore_int.h index ca5f3901e..0e4b2b838 100644 --- a/components/esp32/include/esp_crosscore_int.h +++ b/components/esp32/include/esp_crosscore_int.h @@ -19,6 +19,10 @@ * Initialize the crosscore interrupt system for this CPU. * This needs to be called once on every CPU that is used * by FreeRTOS. + * + * If multicore FreeRTOS support is enabled, this will be + * called automatically by the startup code and should not + * be called manually. */ void esp_crosscore_int_init(); @@ -28,6 +32,9 @@ void esp_crosscore_int_init(); * currently running task in favour of a higher-priority task * that presumably just woke up. * + * This is used internally by FreeRTOS in multicore mode + * and should not be called by the user. + * * @param coreID Core that should do the yielding */ void esp_crosscore_int_send_yield(int coreId); From 401f6e47134417708a4654cac150f15e0c70819a Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Tue, 25 Oct 2016 21:02:39 +0800 Subject: [PATCH 078/149] vfs: initial version of virtual filesystem API --- components/vfs/README.rst | 2 + components/vfs/component.mk | 1 + components/vfs/include/esp_vfs.h | 157 +++++++++++++++++ components/vfs/vfs.c | 285 +++++++++++++++++++++++++++++++ 4 files changed, 445 insertions(+) create mode 100644 components/vfs/README.rst create mode 100755 components/vfs/component.mk create mode 100644 components/vfs/include/esp_vfs.h create mode 100644 components/vfs/vfs.c diff --git a/components/vfs/README.rst b/components/vfs/README.rst new file mode 100644 index 000000000..b32e22ccf --- /dev/null +++ b/components/vfs/README.rst @@ -0,0 +1,2 @@ +Virtual filesystem (VFS) is a driver which can perform operations on file-like objects. This can be a real filesystem (FAT, SPIFFS, etc.), or a device driver which exposes file-like interface. + \ No newline at end of file diff --git a/components/vfs/component.mk b/components/vfs/component.mk new file mode 100755 index 000000000..fccf88db8 --- /dev/null +++ b/components/vfs/component.mk @@ -0,0 +1 @@ +include $(IDF_PATH)/make/component_common.mk diff --git a/components/vfs/include/esp_vfs.h b/components/vfs/include/esp_vfs.h new file mode 100644 index 000000000..0d1c3d147 --- /dev/null +++ b/components/vfs/include/esp_vfs.h @@ -0,0 +1,157 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef __ESP_VFS_H__ +#define __ESP_VFS_H__ + +#include +#include +#include "esp_err.h" +#include +#include +#include +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Maximum length of path prefix (not including zero terminator) + */ +#define ESP_VFS_PATH_MAX 15 + +/** + * Default value of flags member in esp_vfs_t structure. + */ +#define ESP_VFS_FLAG_DEFAULT 0 + +/** + * Flag which indicates that FS needs extra context pointer in syscalls. + */ +#define ESP_VFS_FLAG_CONTEXT_PTR 1 + +/** + * @brief VFS definition structure + * + * This structure should be filled with pointers to corresponding + * FS driver functions. + * + * If the FS implementation has an option to use certain offset for + * all file descriptors, this value should be passed into fd_offset + * field. Otherwise VFS component will translate all FDs to start + * at zero offset. + * + * Some FS implementations expect some state (e.g. pointer to some structure) + * to be passed in as a first argument. For these implementations, + * populate the members of this structure which have _p suffix, set + * flags member to ESP_VFS_FLAG_CONTEXT_PTR and provide the context pointer + * to esp_vfs_register function. + * If the implementation doesn't use this extra argument, populate the + * members without _p suffix and set flags memeber to ESP_VFS_FLAG_DEFAULT. + * + * If the FS driver doesn't provide some of the functions, set corresponding + * members to NULL. + */ +typedef struct +{ + int fd_offset; + int flags; + union { + size_t (*write_p)(void* p, int fd, const void * data, size_t size); + size_t (*write)(int fd, const void * data, size_t size); + }; + union { + _off_t (*lseek_p)(void* p, int fd, _off_t size, int mode); + _off_t (*lseek)(int fd, _off_t size, int mode); + }; + union { + ssize_t (*read_p)(void* ctx, int fd, void * dst, size_t size); + ssize_t (*read)(int fd, void * dst, size_t size); + }; + union { + int (*open_p)(void* ctx, const char * path, int flags, int mode); + int (*open)(const char * path, int flags, int mode); + }; + union { + int (*close_p)(void* ctx, int fd); + int (*close)(int fd); + }; + union { + int (*fstat_p)(void* ctx, int fd, struct stat * st); + int (*fstat)(int fd, struct stat * st); + }; + union { + int (*stat_p)(void* ctx, const char * path, struct stat * st); + int (*stat)(const char * path, struct stat * st); + }; + union { + int (*link_p)(void* ctx, const char* n1, const char* n2); + int (*link)(const char* n1, const char* n2); + }; + union { + int (*unlink_p)(void* ctx, const char *path); + int (*unlink)(const char *path); + }; + union { + int (*rename_p)(void* ctx, const char *src, const char *dst); + int (*rename)(const char *src, const char *dst); + }; +} esp_vfs_t; + + +/** + * Register a virtual filesystem for given path prefix. + * + * @param base_path file path prefix associated with the filesystem. + * Must be a zero-terminated C string, up to ESP_VFS_PATH_MAX + * characters long, and at least 2 characters long. + * Name must start with a "/" and must not end with "/". + * For example, "/data" or "/dev/spi" are valid. + * These VFSes would then be called to handle file paths such as + * "/data/myfile.txt" or "/dev/spi/0". + * @param vfs Pointer to esp_vfs_t, a structure which maps syscalls to + * the filesystem driver functions. VFS component doesn't + * assume ownership of this pointer. + * @param ctx If vfs->flags has ESP_VFS_FLAG_CONTEXT_PTR set, a pointer + * which should be passed to VFS functions. Otherwise, NULL. + * + * @return ESP_OK if successful, ESP_ERR_NO_MEM if too many VFSes are + * registered. + */ +esp_err_t esp_vfs_register(const char* base_path, const esp_vfs_t* vfs, void* ctx); + + +/** + * These functions are to be used in newlib syscall table. They will be called by + * newlib when it needs to use any of the syscalls. + */ + +ssize_t esp_vfs_write(struct _reent *r, int fd, const void * data, size_t size); +_off_t esp_vfs_lseek(struct _reent *r, int fd, _off_t size, int mode); +ssize_t esp_vfs_read(struct _reent *r, int fd, void * dst, size_t size); +int esp_vfs_open(struct _reent *r, const char * path, int flags, int mode); +int esp_vfs_close(struct _reent *r, int fd); +int esp_vfs_fstat(struct _reent *r, int fd, struct stat * st); +int esp_vfs_stat(struct _reent *r, const char * path, struct stat * st); +int esp_vfs_link(struct _reent *r, const char* n1, const char* n2); +int esp_vfs_unlink(struct _reent *r, const char *path); +int esp_vfs_rename(struct _reent *r, const char *src, const char *dst); + + + +#ifdef __cplusplus +} // extern "C" +#endif + + +#endif //__ESP_VFS_H__ diff --git a/components/vfs/vfs.c b/components/vfs/vfs.c new file mode 100644 index 000000000..afc7149ba --- /dev/null +++ b/components/vfs/vfs.c @@ -0,0 +1,285 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include "esp_vfs.h" +#include "esp_log.h" + +/* + * File descriptors visible by the applications are composed of two parts. + * Lower CONFIG_MAX_FD_BITS bits are used for the actual file descriptor. + * Next (16 - CONFIG_MAX_FD_BITS - 1) bits are used to identify the VFS this + * descriptor corresponds to. + * Highest bit is zero. + * We can only use 16 bits because newlib stores file descriptor as short int. + */ + +#ifndef CONFIG_MAX_FD_BITS +#define CONFIG_MAX_FD_BITS 12 +#endif + +#define MAX_VFS_ID_BITS (16 - CONFIG_MAX_FD_BITS - 1) +// mask of actual file descriptor (e.g. 0x00000fff) +#define VFS_FD_MASK ((1 << CONFIG_MAX_FD_BITS) - 1) +// max number of VFS entries +#define VFS_MAX_COUNT ((1 << MAX_VFS_ID_BITS) - 1) +// mask of VFS id (e.g. 0x00007000) +#define VFS_INDEX_MASK (VFS_MAX_COUNT << CONFIG_MAX_FD_BITS) +#define VFS_INDEX_S CONFIG_MAX_FD_BITS + +typedef struct vfs_entry_ { + esp_vfs_t vfs; // contains pointers to VFS functions + char path_prefix[ESP_VFS_PATH_MAX]; // path prefix mapped to this VFS + size_t path_prefix_len; // micro-optimization to avoid doing extra strlen + void* ctx; // optional pointer which can be passed to VFS + int offset; // index of this structure in s_vfs array +} vfs_entry_t; + +static vfs_entry_t* s_vfs[VFS_MAX_COUNT] = { 0 }; +static size_t s_vfs_count = 0; + +esp_err_t esp_vfs_register(const char* base_path, const esp_vfs_t* vfs, void* ctx) +{ + if (s_vfs_count >= VFS_MAX_COUNT) { + return ESP_ERR_NO_MEM; + } + size_t len = strlen(base_path); + if (len < 2 || len > ESP_VFS_PATH_MAX) { + return ESP_ERR_INVALID_ARG; + } + if (base_path[0] != '/' || base_path[len - 1] == '/') { + return ESP_ERR_INVALID_ARG; + } + vfs_entry_t *entry = (vfs_entry_t*) malloc(sizeof(vfs_entry_t)); + if (entry == NULL) { + return ESP_ERR_NO_MEM; + } + strcpy(entry->path_prefix, base_path); // we have already verified argument length + memcpy(&entry->vfs, vfs, sizeof(esp_vfs_t)); + entry->path_prefix_len = len; + entry->ctx = ctx; + entry->offset = s_vfs_count; + s_vfs[s_vfs_count] = entry; + ++s_vfs_count; + return ESP_OK; +} + +static const vfs_entry_t* get_vfs_for_fd(int fd) +{ + int index = ((fd & VFS_INDEX_MASK) >> VFS_INDEX_S); + if (index >= s_vfs_count) { + return NULL; + } + return s_vfs[index]; +} + +static int translate_fd(const vfs_entry_t* vfs, int fd) +{ + return (fd & VFS_FD_MASK) + vfs->vfs.fd_offset; +} + +static const char* translate_path(const vfs_entry_t* vfs, const char* src_path) +{ + assert(strncmp(src_path, vfs->path_prefix, vfs->path_prefix_len) == 0); + return src_path + vfs->path_prefix_len; +} + +static const vfs_entry_t* get_vfs_for_path(const char* path) +{ + size_t len = strlen(path); + for (size_t i = 0; i < s_vfs_count; ++i) { + const vfs_entry_t* vfs = s_vfs[i]; + if (len < vfs->path_prefix_len + 1) { // +1 is for the trailing slash after base path + continue; + } + if (memcmp(path, vfs->path_prefix, vfs->path_prefix_len) != 0) { // match prefix + continue; + } + if (path[vfs->path_prefix_len] != '/') { // don't match "/data" prefix for "/data1/foo.txt" + continue; + } + return vfs; + } + return NULL; +} + +/* + * Using huge multi-line macros is never nice, but in this case + * the only alternative is to repeat this chunk of code (with different function names) + * for each syscall being implemented. Given that this define is contained within a single + * file, this looks like a good tradeoff. + * + * First we check if syscall is implemented by VFS (corresponding member is not NULL), + * then call the right flavor of the method (e.g. open or open_p) depending on + * ESP_VFS_FLAG_CONTEXT_PTR flag. If ESP_VFS_FLAG_CONTEXT_PTR is set, context is passed + * in as first argument and _p variant is used for the call. + * It is enough to check just one of them for NULL, as both variants are part of a union. + */ +#define CHECK_AND_CALL(ret, r, pvfs, func, ...) \ + if (pvfs->vfs.func == NULL) { \ + __errno_r(r) = ENOSYS; \ + return -1; \ + } \ + if (pvfs->vfs.flags & ESP_VFS_FLAG_CONTEXT_PTR) { \ + ret = (*pvfs->vfs.func ## _p)(pvfs->ctx, __VA_ARGS__); \ + } else { \ + ret = (*pvfs->vfs.func)(__VA_ARGS__);\ + } + + +int esp_vfs_open(struct _reent *r, const char * path, int flags, int mode) +{ + const vfs_entry_t* vfs = get_vfs_for_path(path); + if (vfs == NULL) { + __errno_r(r) = ENOENT; + return -1; + } + const char* path_within_vfs = translate_path(vfs, path); + int ret; + CHECK_AND_CALL(ret, r, vfs, open, path_within_vfs, flags, mode); + return ret - vfs->vfs.fd_offset + (vfs->offset << VFS_INDEX_S); +} + +ssize_t esp_vfs_write(struct _reent *r, int fd, const void * data, size_t size) +{ + const vfs_entry_t* vfs = get_vfs_for_fd(fd); + if (vfs == NULL) { + __errno_r(r) = EBADF; + return -1; + } + int local_fd = translate_fd(vfs, fd); + int ret; + CHECK_AND_CALL(ret, r, vfs, write, local_fd, data, size); + return ret; +} + +_off_t esp_vfs_lseek(struct _reent *r, int fd, _off_t size, int mode) +{ + const vfs_entry_t* vfs = get_vfs_for_fd(fd); + if (vfs == NULL) { + __errno_r(r) = EBADF; + return -1; + } + int local_fd = translate_fd(vfs, fd); + int ret; + CHECK_AND_CALL(ret, r, vfs, lseek, local_fd, size, mode); + return ret; +} + +ssize_t esp_vfs_read(struct _reent *r, int fd, void * dst, size_t size) +{ + const vfs_entry_t* vfs = get_vfs_for_fd(fd); + if (vfs == NULL) { + __errno_r(r) = EBADF; + return -1; + } + int local_fd = translate_fd(vfs, fd); + int ret; + CHECK_AND_CALL(ret, r, vfs, read, local_fd, dst, size); + return ret; +} + + +int esp_vfs_close(struct _reent *r, int fd) +{ + const vfs_entry_t* vfs = get_vfs_for_fd(fd); + if (vfs == NULL) { + __errno_r(r) = EBADF; + return -1; + } + int local_fd = translate_fd(vfs, fd); + int ret; + CHECK_AND_CALL(ret, r, vfs, close, local_fd); + return ret; +} + +int esp_vfs_fstat(struct _reent *r, int fd, struct stat * st) +{ + const vfs_entry_t* vfs = get_vfs_for_fd(fd); + if (vfs == NULL) { + __errno_r(r) = EBADF; + return -1; + } + int local_fd = translate_fd(vfs, fd); + int ret; + CHECK_AND_CALL(ret, r, vfs, fstat, local_fd, st); + return ret; +} + +int esp_vfs_stat(struct _reent *r, const char * path, struct stat * st) +{ + const vfs_entry_t* vfs = get_vfs_for_path(path); + if (vfs == NULL) { + __errno_r(r) = ENOENT; + return -1; + } + const char* path_within_vfs = translate_path(vfs, path); + int ret; + CHECK_AND_CALL(ret, r, vfs, stat, path_within_vfs, st); + return ret; +} + +int esp_vfs_link(struct _reent *r, const char* n1, const char* n2) +{ + const vfs_entry_t* vfs = get_vfs_for_path(n1); + if (vfs == NULL) { + __errno_r(r) = ENOENT; + return -1; + } + const vfs_entry_t* vfs2 = get_vfs_for_path(n2); + if (vfs != vfs2) { + __errno_r(r) = EXDEV; + return -1; + } + const char* path1_within_vfs = translate_path(vfs, n1); + const char* path2_within_vfs = translate_path(vfs, n2); + int ret; + CHECK_AND_CALL(ret, r, vfs, link, path1_within_vfs, path2_within_vfs); + return ret; +} + +int esp_vfs_unlink(struct _reent *r, const char *path) +{ + const vfs_entry_t* vfs = get_vfs_for_path(path); + if (vfs == NULL) { + __errno_r(r) = ENOENT; + return -1; + } + const char* path_within_vfs = translate_path(vfs, path); + int ret; + CHECK_AND_CALL(ret, r, vfs, unlink, path_within_vfs); + return ret; +} + +int esp_vfs_rename(struct _reent *r, const char *src, const char *dst) +{ + const vfs_entry_t* vfs = get_vfs_for_path(src); + if (vfs == NULL) { + __errno_r(r) = ENOENT; + return -1; + } + const vfs_entry_t* vfs_dst = get_vfs_for_path(dst); + if (vfs != vfs_dst) { + __errno_r(r) = EXDEV; + return -1; + } + const char* src_within_vfs = translate_path(vfs, src); + const char* dst_within_vfs = translate_path(vfs, dst); + int ret; + CHECK_AND_CALL(ret, r, vfs, rename, src_within_vfs, dst_within_vfs); + return ret; +} From 587360363c121d266b66271b10a60831b6ff9d40 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Tue, 25 Oct 2016 22:01:50 +0800 Subject: [PATCH 079/149] vfs: add UART device This should be integrated with UART driver once everything is merged --- components/vfs/include/esp_vfs_dev.h | 28 ++++++++ components/vfs/vfs_uart.c | 95 ++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 components/vfs/include/esp_vfs_dev.h create mode 100644 components/vfs/vfs_uart.c diff --git a/components/vfs/include/esp_vfs_dev.h b/components/vfs/include/esp_vfs_dev.h new file mode 100644 index 000000000..6eb63d852 --- /dev/null +++ b/components/vfs/include/esp_vfs_dev.h @@ -0,0 +1,28 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef __ESP_VFS_H__ +#define __ESP_VFS_H__ + +#include "esp_vfs.h" + +/** + * @brief add /dev/uart virtual filesystem driver + * + * This function is called from startup code to enable serial output + */ +void esp_vfs_dev_uart_register(); + + +#endif //__ESP_VFS_H__ diff --git a/components/vfs/vfs_uart.c b/components/vfs/vfs_uart.c new file mode 100644 index 000000000..179c5e258 --- /dev/null +++ b/components/vfs/vfs_uart.c @@ -0,0 +1,95 @@ +#include +#include "esp_vfs.h" +#include "esp_attr.h" +#include "sys/errno.h" +#include "sys/lock.h" +#include "soc/uart_struct.h" + +static uart_dev_t* s_uarts[3] = {&UART0, &UART1, &UART2}; + +static int IRAM_ATTR uart_open(const char * path, int flags, int mode) +{ + // this is fairly primitive, we should check if file is opened read only, + // and error out if write is requested + if (strcmp(path, "/0") == 0) { + return 0; + } else if (strcmp(path, "/1") == 0) { + return 1; + } else if (strcmp(path, "/2") == 0) { + return 2; + } + errno = ENOENT; + return -1; +} + +static void IRAM_ATTR uart_tx_char(uart_dev_t* uart, int c) +{ + while (uart->status.txfifo_cnt >= 127) { + ; + } + uart->fifo.rw_byte = c; +} + + +static size_t IRAM_ATTR uart_write(int fd, const void * data, size_t size) +{ + assert(fd >=0 && fd < 3); + const char *data_c = (const char *)data; + uart_dev_t* uart = s_uarts[fd]; + static _lock_t stdout_lock; /* lazily initialised */ + /* Even though newlib does stream locking on stdout, we need + a dedicated stdout UART lock... + + This is because each task has its own _reent structure with + unique FILEs for stdin/stdout/stderr, so these are + per-thread (lazily initialised by __sinit the first time a + stdio function is used, see findfp.c:235. + + It seems like overkill to allocate a FILE-per-task and lock + a thread-local stream, but I see no easy way to fix this + (pre-__sinit_, tasks have "fake" FILEs ie __sf_fake_stdout + which aren't fully valid.) + */ + _lock_acquire_recursive(&stdout_lock); + for (size_t i = 0; i < size; i++) { +#if CONFIG_NEWLIB_STDOUT_ADDCR + if (data_c[i]=='\n') { + uart_tx_char(uart, '\r'); + } +#endif + uart_tx_char(uart, data_c[i]); + } + _lock_release_recursive(&stdout_lock); + return size; +} + +static int IRAM_ATTR uart_fstat(int fd, struct stat * st) +{ + assert(fd >=0 && fd < 3); + st->st_mode = S_IFCHR; + return 0; +} + +static int IRAM_ATTR uart_close(int fd) +{ + assert(fd >=0 && fd < 3); + return 0; +} + +void esp_vfs_dev_uart_register() +{ + esp_vfs_t vfs = { + .fd_offset = 0, + .flags = ESP_VFS_FLAG_DEFAULT, + .write = &uart_write, + .open = &uart_open, + .fstat = &uart_fstat, + .close = &uart_close, + .read = NULL, // TODO: implement reading from UART + .stat = NULL, + .link = NULL, + .unlink = NULL, + .rename = NULL + }; + ESP_ERROR_CHECK(esp_vfs_register("/dev/uart", &vfs, NULL)); +} From b3b8334d546fbcdb57629fc07b1cbaa67b65958e Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Tue, 25 Oct 2016 22:12:07 +0800 Subject: [PATCH 080/149] vfs and newlib: small fixes - spaces->tabs in tasks.c - update vfs_uart.c to use per-UART locks - add license to vfs_uart.c - allocate separate streams for stdout, stdin, stderr, so that they can be independently reassigned - fix build system test failure --- components/esp32/cpu_start.c | 4 +- components/esp32/include/soc/cpu.h | 3 + components/newlib/component.mk | 7 +- .../{esp32/syscalls.c => newlib/locks.c} | 258 +----------------- .../newlib/platform_include/esp_newlib.h | 27 ++ components/newlib/syscall_table.c | 91 ++++++ components/newlib/syscalls.c | 105 +++++++ components/newlib/time.c | 35 +++ 8 files changed, 269 insertions(+), 261 deletions(-) rename components/{esp32/syscalls.c => newlib/locks.c} (54%) create mode 100644 components/newlib/platform_include/esp_newlib.h create mode 100644 components/newlib/syscall_table.c create mode 100644 components/newlib/syscalls.c create mode 100644 components/newlib/time.c diff --git a/components/esp32/cpu_start.c b/components/esp32/cpu_start.c index a649764ab..cbac54d7c 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -42,7 +42,7 @@ #include "esp_spi_flash.h" #include "esp_ipc.h" #include "esp_log.h" - +#include "esp_newlib.h" #include "esp_brownout.h" #include "esp_int_wdt.h" #include "esp_task_wdt.h" @@ -160,7 +160,7 @@ void start_cpu0_default(void) #if CONFIG_TASK_WDT esp_task_wdt_init(); #endif - ets_setup_syscalls(); + esp_setup_syscalls(); do_global_ctors(); esp_ipc_init(); spi_flash_init(); diff --git a/components/esp32/include/soc/cpu.h b/components/esp32/include/soc/cpu.h index c74ba317c..ee2907493 100644 --- a/components/esp32/include/soc/cpu.h +++ b/components/esp32/include/soc/cpu.h @@ -15,6 +15,9 @@ #ifndef _SOC_CPU_H #define _SOC_CPU_H +#include +#include +#include #include "xtensa/corebits.h" /* C macros for xtensa special register read/write/exchange */ diff --git a/components/newlib/component.mk b/components/newlib/component.mk index 7c8c74deb..3731b5ef0 100644 --- a/components/newlib/component.mk +++ b/components/newlib/component.mk @@ -1,8 +1,5 @@ -COMPONENT_ADD_LDFLAGS := $(abspath lib/libc.a) $(abspath lib/libm.a) +COMPONENT_ADD_LDFLAGS := $(abspath lib/libc.a) $(abspath lib/libm.a) -lnewlib - -define COMPONENT_BUILDRECIPE - #Nothing to do; this does not generate a library. -endef +COMPONENT_ADD_INCLUDEDIRS := include platform_include include $(IDF_PATH)/make/component_common.mk diff --git a/components/esp32/syscalls.c b/components/newlib/locks.c similarity index 54% rename from components/esp32/syscalls.c rename to components/newlib/locks.c index 052605ee3..21b974a1f 100644 --- a/components/esp32/syscalls.c +++ b/components/newlib/locks.c @@ -3,7 +3,7 @@ // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at - +// // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software @@ -11,181 +11,17 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -#include -#include -#include -#include -#include -#include -#include + +#include #include +#include #include "esp_attr.h" -#include "rom/libc_stubs.h" -#include "rom/uart.h" #include "soc/cpu.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "freertos/portmacro.h" #include "freertos/task.h" -void abort() { - do - { - __asm__ ("break 0,0"); - *((int*) 0) = 0; - } while(true); -} - -void* _malloc_r(struct _reent *r, size_t size) { - return pvPortMalloc(size); -} - -void _free_r(struct _reent *r, void* ptr) { - return vPortFree(ptr); -} - -void* _realloc_r(struct _reent *r, void* ptr, size_t size) { - void* new_chunk; - if (size == 0) { - if (ptr) { - vPortFree(ptr); - } - return NULL; - } - - new_chunk = pvPortMalloc(size); - if (new_chunk && ptr) { - memcpy(new_chunk, ptr, size); - vPortFree(ptr); - } - // realloc behaviour: don't free original chunk if alloc failed - return new_chunk; -} - -void* _calloc_r(struct _reent *r, size_t count, size_t size) { - void* result = pvPortMalloc(count * size); - if (result) - { - memset(result, 0, count * size); - } - return result; -} - -int _system_r(struct _reent *r, const char *str) { - abort(); - return 0; -} - -int _rename_r(struct _reent *r, const char *src, const char *dst) { - abort(); - return 0; -} - -clock_t _times_r(struct _reent *r, struct tms *ptms) { - abort(); - return 0; -} - -// TODO: read time from RTC -int _gettimeofday_r(struct _reent *r, struct timeval *tv, void *tz) { - abort(); - return 0; -} - -void _raise_r(struct _reent *r) { - abort(); -} - -int _unlink_r(struct _reent *r, const char *path) { - abort(); - return 0; -} - -int _link_r(struct _reent *r, const char* n1, const char* n2) { - abort(); - return 0; -} - -int _stat_r(struct _reent *r, const char * path, struct stat * st) { - return 0; -} - -int _fstat_r(struct _reent *r, int fd, struct stat * st) { - st->st_mode = S_IFCHR; - return 0; -} - -void* _sbrk_r(struct _reent *r, ptrdiff_t sz) { - abort(); - return 0; -} - -int _getpid_r(struct _reent *r) { - abort(); - return 0; -} - -int _kill_r(struct _reent *r, int pid, int sig) { - abort(); - return 0; -} - -void _exit_r(struct _reent *r, int e) { - abort(); -} - -int _close_r(struct _reent *r, int fd) { - return 0; -} - -int _open_r(struct _reent *r, const char * path, int flags, int mode) { - return 0; -} - -void _exit(int __status) { - abort(); -} - -ssize_t _write_r(struct _reent *r, int fd, const void * data, size_t size) { - const char *data_c = (const char *)data; - if (fd == STDOUT_FILENO) { - static _lock_t stdout_lock; /* lazily initialised */ - /* Even though newlib does stream locking on stdout, we need - a dedicated stdout UART lock... - - This is because each task has its own _reent structure with - unique FILEs for stdin/stdout/stderr, so these are - per-thread (lazily initialised by __sinit the first time a - stdio function is used, see findfp.c:235. - - It seems like overkill to allocate a FILE-per-task and lock - a thread-local stream, but I see no easy way to fix this - (pre-__sinit_, tasks have "fake" FILEs ie __sf_fake_stdout - which aren't fully valid.) - */ - _lock_acquire_recursive(&stdout_lock); - for (size_t i = 0; i < size; i++) { -#if CONFIG_NEWLIB_STDOUT_ADDCR - if (data_c[i]=='\n') { - uart_tx_one_char('\r'); - } -#endif - uart_tx_one_char(data_c[i]); - } - _lock_release_recursive(&stdout_lock); - } - return size; -} - -_off_t _lseek_r(struct _reent *r, int fd, _off_t size, int mode) { - return 0; -} - -// TODO: implement reading from UART -ssize_t _read_r(struct _reent *r, int fd, void * dst, size_t size) { - return 0; -} - /* Notes on our newlib lock implementation: * * - Use FreeRTOS mutex semaphores as locks. @@ -369,89 +205,3 @@ void IRAM_ATTR _lock_release(_lock_t *lock) { void IRAM_ATTR _lock_release_recursive(_lock_t *lock) { lock_release_generic(lock, queueQUEUE_TYPE_RECURSIVE_MUTEX); } - -// This function is not part on newlib API, it is defined in libc/stdio/local.h -// It is called as part of _reclaim_reent via a pointer in __cleanup member -// of struct _reent. -// This function doesn't call _fclose_r for _stdin, _stdout, _stderr members -// of struct reent. Not doing so causes a memory leak each time a task is -// terminated. We replace __cleanup member with _extra_cleanup_r (below) to work -// around this. -extern void _cleanup_r(struct _reent* r); - -void _extra_cleanup_r(struct _reent* r) -{ - _cleanup_r(r); - _fclose_r(r, r->_stdout); - _fclose_r(r, r->_stderr); - _fclose_r(r, r->_stdin); -} - -static struct _reent s_reent; - -/* - General ToDo that the Xtensa newlib support code did but we do not: Close every open fd a running task had when the task - is killed. Do we want that too? - JD -*/ - -extern int _printf_float(struct _reent *rptr, - void *pdata, - FILE * fp, - int (*pfunc) (struct _reent *, FILE *, _CONST char *, size_t len), - va_list * ap); - - -extern int _scanf_float(struct _reent *rptr, - void *pdata, - FILE *fp, - va_list *ap); - - -static struct syscall_stub_table s_stub_table = { - .__getreent = &__getreent, - ._malloc_r = &_malloc_r, - ._free_r = &_free_r, - ._realloc_r = &_realloc_r, - ._calloc_r = &_calloc_r, - ._abort = &abort, - ._system_r = &_system_r, - ._rename_r = &_rename_r, - ._times_r = &_times_r, - ._gettimeofday_r = &_gettimeofday_r, - ._raise_r = &_raise_r, - ._unlink_r = &_unlink_r, - ._link_r = &_link_r, - ._stat_r = &_stat_r, - ._fstat_r = &_fstat_r, - ._sbrk_r = &_sbrk_r, - ._getpid_r = &_getpid_r, - ._kill_r = &_kill_r, - ._exit_r = &_exit_r, - ._close_r = &_close_r, - ._open_r = &_open_r, - ._write_r = (int (*)(struct _reent *r, int, const void *, int)) &_write_r, - ._lseek_r = (int (*)(struct _reent *r, int, int, int)) &_lseek_r, - ._read_r = (int (*)(struct _reent *r, int, void *, int)) &_read_r, - ._lock_init = &_lock_init, - ._lock_init_recursive = &_lock_init_recursive, - ._lock_close = &_lock_close, - ._lock_close_recursive = &_lock_close, - ._lock_acquire = &_lock_acquire, - ._lock_acquire_recursive = &_lock_acquire_recursive, - ._lock_try_acquire = &_lock_try_acquire, - ._lock_try_acquire_recursive = &_lock_try_acquire_recursive, - ._lock_release = &_lock_release, - ._lock_release_recursive = &_lock_release_recursive, - ._printf_float = &_printf_float, - ._scanf_float = &_scanf_float, -}; - -void ets_setup_syscalls() { - syscall_table_ptr_pro = &s_stub_table; - syscall_table_ptr_app = &s_stub_table; - _GLOBAL_REENT = &s_reent; - environ = malloc(sizeof(char*)); - environ[0] = NULL; -} - - diff --git a/components/newlib/platform_include/esp_newlib.h b/components/newlib/platform_include/esp_newlib.h new file mode 100644 index 000000000..74d0cc5e5 --- /dev/null +++ b/components/newlib/platform_include/esp_newlib.h @@ -0,0 +1,27 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef __ESP_NEWLIB_H__ +#define __ESP_NEWLIB_H__ + +/** + * Function which sets up syscall table used by newlib functions in ROM. + * + * Called from the startup code, not intended to be called from application + * code. + */ +void esp_setup_syscalls(); + + +#endif //__ESP_NEWLIB_H__ diff --git a/components/newlib/syscall_table.c b/components/newlib/syscall_table.c new file mode 100644 index 000000000..b6414af55 --- /dev/null +++ b/components/newlib/syscall_table.c @@ -0,0 +1,91 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "rom/libc_stubs.h" +#include "esp_vfs.h" + +static struct _reent s_reent; + +extern int _printf_float(struct _reent *rptr, + void *pdata, + FILE * fp, + int (*pfunc) (struct _reent *, FILE *, _CONST char *, size_t len), + va_list * ap); + + +extern int _scanf_float(struct _reent *rptr, + void *pdata, + FILE *fp, + va_list *ap); + + +static struct syscall_stub_table s_stub_table = { + .__getreent = &__getreent, + ._malloc_r = &_malloc_r, + ._free_r = &_free_r, + ._realloc_r = &_realloc_r, + ._calloc_r = &_calloc_r, + ._abort = &abort, + ._system_r = &_system_r, + ._rename_r = &esp_vfs_rename, + ._times_r = &_times_r, + ._gettimeofday_r = &_gettimeofday_r, + ._raise_r = (void (*)(struct _reent *r)) &_raise_r, + ._unlink_r = &esp_vfs_unlink, + ._link_r = &esp_vfs_link, + ._stat_r = &esp_vfs_stat, + ._fstat_r = &esp_vfs_fstat, + ._sbrk_r = &_sbrk_r, + ._getpid_r = &_getpid_r, + ._kill_r = &_kill_r, + ._exit_r = NULL, // never called in ROM + ._close_r = &esp_vfs_close, + ._open_r = &esp_vfs_open, + ._write_r = (int (*)(struct _reent *r, int, const void *, int)) &esp_vfs_write, + ._lseek_r = (int (*)(struct _reent *r, int, int, int)) &esp_vfs_lseek, + ._read_r = (int (*)(struct _reent *r, int, void *, int)) &esp_vfs_read, + ._lock_init = &_lock_init, + ._lock_init_recursive = &_lock_init_recursive, + ._lock_close = &_lock_close, + ._lock_close_recursive = &_lock_close, + ._lock_acquire = &_lock_acquire, + ._lock_acquire_recursive = &_lock_acquire_recursive, + ._lock_try_acquire = &_lock_try_acquire, + ._lock_try_acquire_recursive = &_lock_try_acquire_recursive, + ._lock_release = &_lock_release, + ._lock_release_recursive = &_lock_release_recursive, + ._printf_float = &_printf_float, + ._scanf_float = &_scanf_float, +}; + +void esp_setup_syscalls() +{ + syscall_table_ptr_pro = &s_stub_table; + syscall_table_ptr_app = &s_stub_table; + _GLOBAL_REENT = &s_reent; + environ = malloc(sizeof(char*)); + environ[0] = NULL; +} + + diff --git a/components/newlib/syscalls.c b/components/newlib/syscalls.c new file mode 100644 index 000000000..4d70a39a8 --- /dev/null +++ b/components/newlib/syscalls.c @@ -0,0 +1,105 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include +#include +#include "esp_attr.h" +#include "freertos/FreeRTOS.h" + +void IRAM_ATTR abort() +{ + do + { + __asm__ ("break 0,0"); + *((int*) 0) = 0; + } while(true); +} + +void* IRAM_ATTR _malloc_r(struct _reent *r, size_t size) +{ + return pvPortMalloc(size); +} + +void IRAM_ATTR _free_r(struct _reent *r, void* ptr) +{ + return vPortFree(ptr); +} + +void* IRAM_ATTR _realloc_r(struct _reent *r, void* ptr, size_t size) +{ + void* new_chunk; + if (size == 0) { + if (ptr) { + vPortFree(ptr); + } + return NULL; + } + + new_chunk = pvPortMalloc(size); + if (new_chunk && ptr) { + memcpy(new_chunk, ptr, size); + vPortFree(ptr); + } + // realloc behaviour: don't free original chunk if alloc failed + return new_chunk; +} + +void* IRAM_ATTR _calloc_r(struct _reent *r, size_t count, size_t size) +{ + void* result = pvPortMalloc(count * size); + if (result) + { + memset(result, 0, count * size); + } + return result; +} + +int _system_r(struct _reent *r, const char *str) +{ + __errno_r(r) = ENOSYS; + return -1; +} + +void _raise_r(struct _reent *r) +{ + abort(); +} + +void* _sbrk_r(struct _reent *r, ptrdiff_t sz) +{ + abort(); +} + +int _getpid_r(struct _reent *r) +{ + __errno_r(r) = ENOSYS; + return -1; +} + +int _kill_r(struct _reent *r, int pid, int sig) +{ + __errno_r(r) = ENOSYS; + return -1; +} + +void _exit(int __status) +{ + abort(); +} + diff --git a/components/newlib/time.c b/components/newlib/time.c new file mode 100644 index 000000000..cb2efb4e1 --- /dev/null +++ b/components/newlib/time.c @@ -0,0 +1,35 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include +#include "esp_attr.h" + + +clock_t _times_r(struct _reent *r, struct tms *ptms) +{ + __errno_r(r) = ENOSYS; + return (clock_t) -1; +} + +// TODO: read time from RTC +int _gettimeofday_r(struct _reent *r, struct timeval *tv, void *tz) +{ + __errno_r(r) = ENOSYS; + return (clock_t) -1; +} From 0c130ecf19a8c1fe9803dba75148028965898f8b Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Tue, 25 Oct 2016 22:16:08 +0800 Subject: [PATCH 081/149] vfs: code review fixes - fix typo in readme - remove unneeded extern declaration - fix header guard macro - tabs->spaces in syscalls.c (+1 squashed commit) - fix minor typos --- components/esp32/cpu_start.c | 6 +++ components/freertos/tasks.c | 8 ++-- .../newlib/platform_include/esp_newlib.h | 10 +++++ components/newlib/reent_init.c | 45 +++++++++++++++++++ 4 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 components/newlib/reent_init.c diff --git a/components/esp32/cpu_start.c b/components/esp32/cpu_start.c index cbac54d7c..769bd3eaf 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -42,6 +42,7 @@ #include "esp_spi_flash.h" #include "esp_ipc.h" #include "esp_log.h" +#include "esp_vfs_dev.h" #include "esp_newlib.h" #include "esp_brownout.h" #include "esp_int_wdt.h" @@ -161,6 +162,11 @@ void start_cpu0_default(void) esp_task_wdt_init(); #endif esp_setup_syscalls(); + esp_vfs_dev_uart_register(); + esp_reent_init(_GLOBAL_REENT); + _GLOBAL_REENT->_stdout = fopen("/dev/uart/0", "w"); // use fdopen here? + _GLOBAL_REENT->_stderr = _GLOBAL_REENT->_stdout; + _GLOBAL_REENT->_stdin = _GLOBAL_REENT->_stdout; do_global_ctors(); esp_ipc_init(); spi_flash_init(); diff --git a/components/freertos/tasks.c b/components/freertos/tasks.c index 9f004002c..6a7eb5034 100644 --- a/components/freertos/tasks.c +++ b/components/freertos/tasks.c @@ -77,6 +77,7 @@ task.h is included from an application file. */ #define MPU_WRAPPERS_INCLUDED_FROM_API_FILE #include "rom/ets_sys.h" +#include "esp_newlib.h" /* FreeRTOS includes. */ #include "FreeRTOS.h" @@ -962,8 +963,8 @@ UBaseType_t x; #if ( configUSE_NEWLIB_REENTRANT == 1 ) { - /* Initialise this task's Newlib reent structure. */ - _REENT_INIT_PTR( ( &( pxNewTCB->xNewLib_reent ) ) ); + /* Initialise this task's Newlib reent structure. */ + esp_reent_init(&pxNewTCB->xNewLib_reent); } #endif @@ -3643,8 +3644,6 @@ BaseType_t xTaskGetAffinity( TaskHandle_t xTask ) #if ( INCLUDE_vTaskDelete == 1 ) - // TODO: move this to newlib component and provide a header file - extern void _extra_cleanup_r(struct _reent* r); static void prvDeleteTCB( TCB_t *pxTCB ) { @@ -3657,7 +3656,6 @@ BaseType_t xTaskGetAffinity( TaskHandle_t xTask ) to the task to free any memory allocated at the application level. */ #if ( configUSE_NEWLIB_REENTRANT == 1 ) { - pxTCB->xNewLib_reent.__cleanup = &_extra_cleanup_r; _reclaim_reent( &( pxTCB->xNewLib_reent ) ); } #endif /* configUSE_NEWLIB_REENTRANT */ diff --git a/components/newlib/platform_include/esp_newlib.h b/components/newlib/platform_include/esp_newlib.h index 74d0cc5e5..468f2ae34 100644 --- a/components/newlib/platform_include/esp_newlib.h +++ b/components/newlib/platform_include/esp_newlib.h @@ -15,6 +15,16 @@ #ifndef __ESP_NEWLIB_H__ #define __ESP_NEWLIB_H__ +#include + +/** + * Replacement for newlib's _REENT_INIT_PTR and __sinit. + * + * Called from startup code and FreeRTOS, not intended to be called from + * application code. + */ +void esp_reent_init(struct _reent* r); + /** * Function which sets up syscall table used by newlib functions in ROM. * diff --git a/components/newlib/reent_init.c b/components/newlib/reent_init.c new file mode 100644 index 000000000..5c29e898c --- /dev/null +++ b/components/newlib/reent_init.c @@ -0,0 +1,45 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include "esp_attr.h" + +/* This function is not part on newlib API, it is defined in libc/stdio/local.h + * There is no nice way to get __cleanup member populated while avoiding __sinit, + * so extern declaration is used here. + */ +extern void _cleanup_r(struct _reent* r); + +/** + * This is the replacement for newlib's _REENT_INIT_PTR and __sinit. + * The problem with __sinit is that it allocates three FILE structures + * (stdin, stdout, stderr). Having individual standard streams for each task + * is a bit too much on a small embedded system. So we point streams + * to the streams of the global struct _reent, which are initialized in + * startup code. + */ +void IRAM_ATTR esp_reent_init(struct _reent* r) +{ + memset(r, 0, sizeof(*r)); + r->_stdout = _GLOBAL_REENT->_stdout; + r->_stderr = _GLOBAL_REENT->_stderr; + r->_stdin = _GLOBAL_REENT->_stdin; + r->__cleanup = &_cleanup_r; + r->__sdidinit = 1; + r->__sglue._next = NULL; + r->__sglue._niobs = 0; + r->__sglue._iobs = NULL; + r->_current_locale = "C"; +} From 7e201c55276649df496baed44a003fa9d958de60 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Wed, 26 Oct 2016 14:05:56 +0800 Subject: [PATCH 082/149] vfs and newlib: small fixes - spaces->tabs in tasks.c - update vfs_uart.c to use per-UART locks - add license to vfs_uart.c - allocate separate streams for stdout, stdin, stderr, so that they can be independently reassigned - fix build system test failure - use posix off_t instead of newlib internal _off_t --- components/esp32/cpu_start.c | 7 +++--- components/vfs/include/esp_vfs.h | 6 ++--- components/vfs/vfs.c | 2 +- components/vfs/vfs_uart.c | 38 ++++++++++++++++++-------------- make/test_build_system.sh | 4 ++-- 5 files changed, 32 insertions(+), 25 deletions(-) diff --git a/components/esp32/cpu_start.c b/components/esp32/cpu_start.c index 769bd3eaf..6998140af 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -164,9 +164,10 @@ void start_cpu0_default(void) esp_setup_syscalls(); esp_vfs_dev_uart_register(); esp_reent_init(_GLOBAL_REENT); - _GLOBAL_REENT->_stdout = fopen("/dev/uart/0", "w"); // use fdopen here? - _GLOBAL_REENT->_stderr = _GLOBAL_REENT->_stdout; - _GLOBAL_REENT->_stdin = _GLOBAL_REENT->_stdout; + const char* default_uart_dev = "/dev/uart/0"; + _GLOBAL_REENT->_stdout = fopen(default_uart_dev, "w"); + _GLOBAL_REENT->_stderr = fopen(default_uart_dev, "w"); + _GLOBAL_REENT->_stdin = fopen(default_uart_dev, "r"); do_global_ctors(); esp_ipc_init(); spi_flash_init(); diff --git a/components/vfs/include/esp_vfs.h b/components/vfs/include/esp_vfs.h index 0d1c3d147..2d9e52c5a 100644 --- a/components/vfs/include/esp_vfs.h +++ b/components/vfs/include/esp_vfs.h @@ -71,8 +71,8 @@ typedef struct size_t (*write)(int fd, const void * data, size_t size); }; union { - _off_t (*lseek_p)(void* p, int fd, _off_t size, int mode); - _off_t (*lseek)(int fd, _off_t size, int mode); + off_t (*lseek_p)(void* p, int fd, off_t size, int mode); + off_t (*lseek)(int fd, off_t size, int mode); }; union { ssize_t (*read_p)(void* ctx, int fd, void * dst, size_t size); @@ -137,7 +137,7 @@ esp_err_t esp_vfs_register(const char* base_path, const esp_vfs_t* vfs, void* ct */ ssize_t esp_vfs_write(struct _reent *r, int fd, const void * data, size_t size); -_off_t esp_vfs_lseek(struct _reent *r, int fd, _off_t size, int mode); +off_t esp_vfs_lseek(struct _reent *r, int fd, off_t size, int mode); ssize_t esp_vfs_read(struct _reent *r, int fd, void * dst, size_t size); int esp_vfs_open(struct _reent *r, const char * path, int flags, int mode); int esp_vfs_close(struct _reent *r, int fd); diff --git a/components/vfs/vfs.c b/components/vfs/vfs.c index afc7149ba..bf26968ff 100644 --- a/components/vfs/vfs.c +++ b/components/vfs/vfs.c @@ -167,7 +167,7 @@ ssize_t esp_vfs_write(struct _reent *r, int fd, const void * data, size_t size) return ret; } -_off_t esp_vfs_lseek(struct _reent *r, int fd, _off_t size, int mode) +off_t esp_vfs_lseek(struct _reent *r, int fd, off_t size, int mode) { const vfs_entry_t* vfs = get_vfs_for_fd(fd); if (vfs == NULL) { diff --git a/components/vfs/vfs_uart.c b/components/vfs/vfs_uart.c index 179c5e258..6b6fad8e4 100644 --- a/components/vfs/vfs_uart.c +++ b/components/vfs/vfs_uart.c @@ -1,3 +1,17 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include #include "esp_vfs.h" #include "esp_attr.h" @@ -6,6 +20,7 @@ #include "soc/uart_struct.h" static uart_dev_t* s_uarts[3] = {&UART0, &UART1, &UART2}; +static _lock_t s_uart_locks[3]; // per-UART locks, lazily initialized static int IRAM_ATTR uart_open(const char * path, int flags, int mode) { @@ -36,21 +51,12 @@ static size_t IRAM_ATTR uart_write(int fd, const void * data, size_t size) assert(fd >=0 && fd < 3); const char *data_c = (const char *)data; uart_dev_t* uart = s_uarts[fd]; - static _lock_t stdout_lock; /* lazily initialised */ - /* Even though newlib does stream locking on stdout, we need - a dedicated stdout UART lock... - - This is because each task has its own _reent structure with - unique FILEs for stdin/stdout/stderr, so these are - per-thread (lazily initialised by __sinit the first time a - stdio function is used, see findfp.c:235. - - It seems like overkill to allocate a FILE-per-task and lock - a thread-local stream, but I see no easy way to fix this - (pre-__sinit_, tasks have "fake" FILEs ie __sf_fake_stdout - which aren't fully valid.) - */ - _lock_acquire_recursive(&stdout_lock); + /* + * Even though newlib does stream locking on each individual stream, we need + * a dedicated UART lock if two streams (stdout and stderr) point to the + * same UART. + */ + _lock_acquire_recursive(&s_uart_locks[fd]); for (size_t i = 0; i < size; i++) { #if CONFIG_NEWLIB_STDOUT_ADDCR if (data_c[i]=='\n') { @@ -59,7 +65,7 @@ static size_t IRAM_ATTR uart_write(int fd, const void * data, size_t size) #endif uart_tx_char(uart, data_c[i]); } - _lock_release_recursive(&stdout_lock); + _lock_release_recursive(&s_uart_locks[fd]); return size; } diff --git a/make/test_build_system.sh b/make/test_build_system.sh index 7c8cbc1f1..d08ae6c8a 100755 --- a/make/test_build_system.sh +++ b/make/test_build_system.sh @@ -59,9 +59,9 @@ function run_tests() print_status "Updating component source file rebuilds component" # touch a file & do a build take_build_snapshot - touch ${IDF_PATH}/components/esp32/syscalls.c + touch ${IDF_PATH}/components/esp32/cpu_start.c make || failure "Failed to partial build" - assert_rebuilt ${APP_BINS} esp32/libesp32.a esp32/syscalls.o + assert_rebuilt ${APP_BINS} esp32/libesp32.a esp32/cpu_start.o assert_not_rebuilt lwip/liblwip.a freertos/libfreertos.a ${BOOTLOADER_BINS} partitions_singleapp.bin print_status "Bootloader source file rebuilds bootloader" From da56e7625503773dba62d58c93c18fbd879cce76 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Wed, 26 Oct 2016 16:47:26 +0800 Subject: [PATCH 083/149] vfs: add readme file --- components/vfs/README.rst | 125 +++++++++++++++++++++++++++++++++++++- components/vfs/vfs_uart.c | 1 + 2 files changed, 124 insertions(+), 2 deletions(-) diff --git a/components/vfs/README.rst b/components/vfs/README.rst index b32e22ccf..21b687e78 100644 --- a/components/vfs/README.rst +++ b/components/vfs/README.rst @@ -1,2 +1,123 @@ -Virtual filesystem (VFS) is a driver which can perform operations on file-like objects. This can be a real filesystem (FAT, SPIFFS, etc.), or a device driver which exposes file-like interface. - \ No newline at end of file +Virtual filesystem component +============================ + +Overview +-------- + +Virtual filesystem (VFS) component provides a unified interface for drivers which can perform operations on file-like objects. This can be a real filesystems (FAT, SPIFFS, etc.), or device drivers which exposes file-like interface. + +This component allows C library functions, such as fopen and fprintf, to work with FS drivers. At high level, each FS driver is associated with some path prefix. When one of C library functions needs to open a file, VFS component searches for the FS driver associated with the file's path, and forwards the call to that driver. VFS also forwards read, write, and other calls for the given file to the same FS driver. + +For example, one can register a FAT filesystem driver with ``/fat`` prefix, and call ``fopen("/fat/file.txt", "w")``. VFS component will the call ``open`` function of FAT driver and pass ``/file.txt`` argument to it (and appropriate mode flags). All subsequent calls to C library functions for the returned ``FILE*`` stream will also be forwarded to the FAT driver. + +FS registration +--------------- + +To register an FS driver, application needs to define in instance of esp_vfs_t structure and populate it with function pointers to FS APIs:: + + esp_vfs_t myfs = { + .fd_offset = 0, + .flags = ESP_VFS_FLAG_DEFAULT, + .write = &myfs_write, + .open = &myfs_open, + .fstat = &myfs_fstat, + .close = &myfs_close, + .read = &myfs_read, + .lseek = NULL, + .stat = NULL, + .link = NULL, + .unlink = NULL, + .rename = NULL + }; + + ESP_ERROR_CHECK(esp_vfs_register("/data", &myfs, NULL)); + +Depending on the way FS driver declares it's APIs, either ``read``, ``write``, etc., or ``read_p``, ``write_p``, etc. should be used. + +Case 1: API functions are declared without an extra context pointer (FS driver is a singleton):: + + size_t myfs_write(int fd, const void * data, size_t size); + + // In definition of esp_vfs_t: + .flags = ESP_VFS_FLAG_DEFAULT, + .write = &myfs_write, + // ... other member initialized + + // When registering FS, context pointer (third argument) is NULL: + ESP_ERROR_CHECK(esp_vfs_register("/data", &myfs, NULL)); + +Case 2: API functions are declared with an extra context pointer (FS driver supports multiple instances):: + + size_t myfs_write(myfs_t* fs, int fd, const void * data, size_t size); + + // In definition of esp_vfs_t: + .flags = ESP_VFS_FLAG_CONTEXT_PTR, + .write_p = &myfs_write, + // ... other member initialized + + // When registering FS, pass the FS context pointer into the third argument + // (hypothetical myfs_mount function is used for illustrative purposes) + myfs_t* myfs_inst1 = myfs_mount(partition1->offset, parition1->size); + ESP_ERROR_CHECK(esp_vfs_register("/data1", &myfs, myfs_inst1)); + + // Can register another instance: + myfs_t* myfs_inst2 = myfs_mount(partition2->offset, parition2->size); + ESP_ERROR_CHECK(esp_vfs_register("/data2", &myfs, myfs_inst2)); + +Paths +----- + +Each registered FS has a path prefix associated with it. This prefix may be considered a "mount point" of this partition. + +Registering mount points which have another mount point as a prefix is not supported and results in undefined behavior. For instance, the following is correct and supported: + +- FS 1 on /data/fs1 +- FS 2 on /data/fs2 + +This **will not work** as expected: + +- FS 1 on /data +- FS 2 on /data/fs2 + +When opening files, FS driver will only be given relative path to files. If the ``myfs`` driver is registered with ``/data`` as prefix, and application calls ``fopen("/data/config.json", ...)``, VFS component will call ``myfs_open("/config.json", ...)``. + +VFS doesn't impose a limit on total file path length, but it does limit FS path prefix to ``ESP_VFS_PATH_MAX`` characters. Individual FS drivers may have their own filename length limitations. + + +File descriptors +---------------- + +It is suggested that filesystem drivers should use small positive integers as file descriptors. VFS component assumes that ``CONFIG_MAX_FD_BITS`` bits (12 by default) are sufficient to represent a file descriptor. + +If filesystem is configured with an option to offset all file descriptors by a constant value, such value should be passed to ``fd_offset`` field of ``esp_vfs_t`` structure. VFS component will then remove this offset when working with FDs of that specific FS, bringing them into the range of small positive integers. + +While file descriptors returned by VFS component to newlib library are rarely seen by the application, the following details may be useful for debugging purposes. File descriptors returned by VFS component are composed of two parts: FS driver ID, and the actual file descriptor. Because newlib stores file descriptors as 16-bit integers, VFS component is also limited by 16 bits to store both parts. + +Lower ``CONFIG_MAX_FD_BITS`` bits are used to store zero-based file descriptor. If FS driver has a non-zero ``fd_offset`` field, this ``fd_offset`` is subtracted FDs obtained from the FS ``open`` call, and the result is stored in the lower bits of the FD. Higher bits are used to save the index of FS in the internal table of registered filesystems. + +When VFS component receives a call from newlib which has a file descriptor, this file descriptor is translated back to the FS-specific file descriptor. First, higher bits of FD are used to identify the FS. Then ``fd_offset`` field of the FS is added to the lower ``CONFIG_MAX_FD_BITS`` bits of the fd, and resulting FD is passed to the FS driver. + +:: + + FD as seen by newlib FD as seen by FS driver + +-----+ + +-------+---------------+ | | +------------------------+ + | FS id | Zero—based FD | +---------------> sum +----> | + +---+---+------+--------+ | | | +------------------------+ + | | | +--^--+ + | +--------------+ | + | | + | +-------------+ | + | | Table of | | + | | registered | | + | | filesystems | | + | +-------------+ +-------------+ | + +-------> entry +----> esp_vfs_t | | + index +-------------+ | structure | | + | | | | | + | | | + fd_offset +---+ + +-------------+ | | + +-------------+ + + + diff --git a/components/vfs/vfs_uart.c b/components/vfs/vfs_uart.c index 6b6fad8e4..bfccad896 100644 --- a/components/vfs/vfs_uart.c +++ b/components/vfs/vfs_uart.c @@ -92,6 +92,7 @@ void esp_vfs_dev_uart_register() .fstat = &uart_fstat, .close = &uart_close, .read = NULL, // TODO: implement reading from UART + .lseek = NULL, .stat = NULL, .link = NULL, .unlink = NULL, From 6f1d3ce4a7c782f8001574b96e37fc781f6f09c4 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Thu, 27 Oct 2016 11:47:41 +0800 Subject: [PATCH 084/149] vfs: code review fixes - fix typo in readme - remove unneeded extern declaration - fix header guard macro - tabs->spaces in syscalls.c - spaces->tabs in tasks.c --- components/esp32/cpu_start.c | 1 - components/freertos/tasks.c | 4 ++-- components/newlib/syscalls.c | 32 ++++++++++++++-------------- components/newlib/time.c | 2 +- components/vfs/README.rst | 19 +++++++++++------ components/vfs/include/esp_vfs_dev.h | 6 +++--- 6 files changed, 34 insertions(+), 30 deletions(-) diff --git a/components/esp32/cpu_start.c b/components/esp32/cpu_start.c index 6998140af..12189ccf6 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -60,7 +60,6 @@ static bool app_cpu_started = false; static void do_global_ctors(void); static void main_task(void* args); -extern void ets_setup_syscalls(void); extern void app_main(void); extern int _bss_start; diff --git a/components/freertos/tasks.c b/components/freertos/tasks.c index 6a7eb5034..64cc3a65d 100644 --- a/components/freertos/tasks.c +++ b/components/freertos/tasks.c @@ -963,8 +963,8 @@ UBaseType_t x; #if ( configUSE_NEWLIB_REENTRANT == 1 ) { - /* Initialise this task's Newlib reent structure. */ - esp_reent_init(&pxNewTCB->xNewLib_reent); + /* Initialise this task's Newlib reent structure. */ + esp_reent_init(&pxNewTCB->xNewLib_reent); } #endif diff --git a/components/newlib/syscalls.c b/components/newlib/syscalls.c index 4d70a39a8..3b2fbf62c 100644 --- a/components/newlib/syscalls.c +++ b/components/newlib/syscalls.c @@ -38,26 +38,26 @@ void* IRAM_ATTR _malloc_r(struct _reent *r, size_t size) void IRAM_ATTR _free_r(struct _reent *r, void* ptr) { - return vPortFree(ptr); + vPortFree(ptr); } void* IRAM_ATTR _realloc_r(struct _reent *r, void* ptr, size_t size) { - void* new_chunk; - if (size == 0) { - if (ptr) { - vPortFree(ptr); - } - return NULL; - } + void* new_chunk; + if (size == 0) { + if (ptr) { + vPortFree(ptr); + } + return NULL; + } - new_chunk = pvPortMalloc(size); - if (new_chunk && ptr) { - memcpy(new_chunk, ptr, size); - vPortFree(ptr); - } - // realloc behaviour: don't free original chunk if alloc failed - return new_chunk; + new_chunk = pvPortMalloc(size); + if (new_chunk && ptr) { + memcpy(new_chunk, ptr, size); + vPortFree(ptr); + } + // realloc behaviour: don't free original chunk if alloc failed + return new_chunk; } void* IRAM_ATTR _calloc_r(struct _reent *r, size_t count, size_t size) @@ -65,7 +65,7 @@ void* IRAM_ATTR _calloc_r(struct _reent *r, size_t count, size_t size) void* result = pvPortMalloc(count * size); if (result) { - memset(result, 0, count * size); + memset(result, 0, count * size); } return result; } diff --git a/components/newlib/time.c b/components/newlib/time.c index cb2efb4e1..021b29545 100644 --- a/components/newlib/time.c +++ b/components/newlib/time.c @@ -31,5 +31,5 @@ clock_t _times_r(struct _reent *r, struct tms *ptms) int _gettimeofday_r(struct _reent *r, struct timeval *tv, void *tz) { __errno_r(r) = ENOSYS; - return (clock_t) -1; + return -1; } diff --git a/components/vfs/README.rst b/components/vfs/README.rst index 21b687e78..c58161c90 100644 --- a/components/vfs/README.rst +++ b/components/vfs/README.rst @@ -8,7 +8,7 @@ Virtual filesystem (VFS) component provides a unified interface for drivers whic This component allows C library functions, such as fopen and fprintf, to work with FS drivers. At high level, each FS driver is associated with some path prefix. When one of C library functions needs to open a file, VFS component searches for the FS driver associated with the file's path, and forwards the call to that driver. VFS also forwards read, write, and other calls for the given file to the same FS driver. -For example, one can register a FAT filesystem driver with ``/fat`` prefix, and call ``fopen("/fat/file.txt", "w")``. VFS component will the call ``open`` function of FAT driver and pass ``/file.txt`` argument to it (and appropriate mode flags). All subsequent calls to C library functions for the returned ``FILE*`` stream will also be forwarded to the FAT driver. +For example, one can register a FAT filesystem driver with ``/fat`` prefix, and call ``fopen("/fat/file.txt", "w")``. VFS component will then call ``open`` function of FAT driver and pass ``/file.txt`` argument to it (and appropriate mode flags). All subsequent calls to C library functions for the returned ``FILE*`` stream will also be forwarded to the FAT driver. FS registration --------------- @@ -32,7 +32,7 @@ To register an FS driver, application needs to define in instance of esp_vfs_t s ESP_ERROR_CHECK(esp_vfs_register("/data", &myfs, NULL)); -Depending on the way FS driver declares it's APIs, either ``read``, ``write``, etc., or ``read_p``, ``write_p``, etc. should be used. +Depending on the way FS driver declares its APIs, either ``read``, ``write``, etc., or ``read_p``, ``write_p``, etc. should be used. Case 1: API functions are declared without an extra context pointer (FS driver is a singleton):: @@ -41,7 +41,7 @@ Case 1: API functions are declared without an extra context pointer (FS driver i // In definition of esp_vfs_t: .flags = ESP_VFS_FLAG_DEFAULT, .write = &myfs_write, - // ... other member initialized + // ... other members initialized // When registering FS, context pointer (third argument) is NULL: ESP_ERROR_CHECK(esp_vfs_register("/data", &myfs, NULL)); @@ -53,15 +53,15 @@ Case 2: API functions are declared with an extra context pointer (FS driver supp // In definition of esp_vfs_t: .flags = ESP_VFS_FLAG_CONTEXT_PTR, .write_p = &myfs_write, - // ... other member initialized + // ... other members initialized // When registering FS, pass the FS context pointer into the third argument // (hypothetical myfs_mount function is used for illustrative purposes) - myfs_t* myfs_inst1 = myfs_mount(partition1->offset, parition1->size); + myfs_t* myfs_inst1 = myfs_mount(partition1->offset, partition1->size); ESP_ERROR_CHECK(esp_vfs_register("/data1", &myfs, myfs_inst1)); // Can register another instance: - myfs_t* myfs_inst2 = myfs_mount(partition2->offset, parition2->size); + myfs_t* myfs_inst2 = myfs_mount(partition2->offset, partition2->size); ESP_ERROR_CHECK(esp_vfs_register("/data2", &myfs, myfs_inst2)); Paths @@ -79,7 +79,12 @@ This **will not work** as expected: - FS 1 on /data - FS 2 on /data/fs2 -When opening files, FS driver will only be given relative path to files. If the ``myfs`` driver is registered with ``/data`` as prefix, and application calls ``fopen("/data/config.json", ...)``, VFS component will call ``myfs_open("/config.json", ...)``. +When opening files, FS driver will only be given relative path to files. For example: + +- ``myfs`` driver is registered with ``/data`` as path prefix +- and application calls ``fopen("/data/config.json", ...)`` +- then VFS component will call ``myfs_open("/config.json", ...)``. +- ``myfs`` driver will open ``/config.json`` file VFS doesn't impose a limit on total file path length, but it does limit FS path prefix to ``ESP_VFS_PATH_MAX`` characters. Individual FS drivers may have their own filename length limitations. diff --git a/components/vfs/include/esp_vfs_dev.h b/components/vfs/include/esp_vfs_dev.h index 6eb63d852..bb2579ee0 100644 --- a/components/vfs/include/esp_vfs_dev.h +++ b/components/vfs/include/esp_vfs_dev.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef __ESP_VFS_H__ -#define __ESP_VFS_H__ +#ifndef __ESP_VFS_DEV_H__ +#define __ESP_VFS_DEV_H__ #include "esp_vfs.h" @@ -25,4 +25,4 @@ void esp_vfs_dev_uart_register(); -#endif //__ESP_VFS_H__ +#endif //__ESP_VFS_DEV_H__ From f6f23141b35dd3fbcf7fe4522766e732ea92df61 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Thu, 8 Sep 2016 19:18:03 +0800 Subject: [PATCH 085/149] components/spi_flash: add high level partition api header TW6701 --- components/spi_flash/include/esp_partition.h | 211 +++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 components/spi_flash/include/esp_partition.h diff --git a/components/spi_flash/include/esp_partition.h b/components/spi_flash/include/esp_partition.h new file mode 100644 index 000000000..fd1223da5 --- /dev/null +++ b/components/spi_flash/include/esp_partition.h @@ -0,0 +1,211 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef __ESP_PARTITION_H__ +#define __ESP_PARTITION_H__ + +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum esp_partition_type_t { + PT_APP_MASK = 0x0000, + PT_APP_FACTORY = PT_APP_MASK | 0x00, + PT_APP_OTA_MIN = PT_APP_MASK | 0x10, + PT_APP_OTA_0 = PT_APP_OTA_MIN + 0, + PT_APP_OTA_1 = PT_APP_OTA_MIN + 1, + PT_APP_OTA_2 = PT_APP_OTA_MIN + 2, + PT_APP_OTA_3 = PT_APP_OTA_MIN + 3, + PT_APP_OTA_4 = PT_APP_OTA_MIN + 4, + PT_APP_OTA_5 = PT_APP_OTA_MIN + 5, + PT_APP_OTA_6 = PT_APP_OTA_MIN + 6, + PT_APP_OTA_7 = PT_APP_OTA_MIN + 7, + PT_APP_OTA_8 = PT_APP_OTA_MIN + 8, + PT_APP_OTA_9 = PT_APP_OTA_MIN + 9, + PT_APP_OTA_10 = PT_APP_OTA_MIN + 10, + PT_APP_OTA_11 = PT_APP_OTA_MIN + 11, + PT_APP_OTA_12 = PT_APP_OTA_MIN + 12, + PT_APP_OTA_13 = PT_APP_OTA_MIN + 13, + PT_APP_OTA_14 = PT_APP_OTA_MIN + 14, + PT_APP_OTA_15 = PT_APP_OTA_MIN + 15, + PT_APP_OTA_MAX = PT_APP_MASK | 0x1f, + PT_APP_TEST = PT_APP_MASK | 0x20, + PT_APP_ANY = PT_APP_MASK | 0xff, + + PT_DATA_MASK = 0x0100, + PT_DATA_OTA = PT_DATA_MASK | 0x00, + PT_DATA_RF = PT_DATA_MASK | 0x01, + PT_DATA_WIFI = PT_DATA_MASK | 0x02, + PT_DATA_ANY = PT_DATA_MASK | 0xff, + + PT_FILESYSTEM_MASK = 0x0200, + PT_FILESYSTEM_ESPHTTPD = 0x0200, + PT_FILESYSTEM_FAT = 0x0201, + PT_FILESYSTEM_SPIFFS = 0x0202, + PT_FILESYSTEM_ANY = 0x20ff, + + PT_END = 0xffff +}; + +#define PT_APP_OTA(i) ((esp_partition_type_t)(PT_APP_OTA_MIN + ((i) & 0xf))) + + +typedef struct esp_partition_iterator_opaque_t* esp_partition_iterator_t; + +/** + * @brief Find partition based on one or more parameters + * + * @param type Partition type, one of esp_partition_type_t values + * To find all app partitions or all filesystem partitions, + * use PT_APP_ANY or PT_FILESYSTEM_ANY, respectively. + * @param label (optional) Partition label. Set this value if looking + * for partition with a specific name. Pass NULL otherwise. + * + * @return iterator which can be used to enumerate all the partitions found, + * or NULL if no partitions were found. + * Iterator obtained through this function has to be released + * using esp_partition_iterator_release when not used any more. + */ +esp_partition_iterator_t esp_partition_find(esp_partition_type_t type, const char* label); + + +/** + * @brief Move partition iterator to the next partition found + * + * Any pointers obtained using esp_partition_label function for this iterator + * will be invalid after this call. + * + * @param iterator Iterator obtained using esp_partition_find. Must be non-NULL. + * + * @return iterator pointing to the next partition found, or NULL if no more + * partitions were found. + * + */ +esp_partition_iterator_t esp_partition_next(esp_partition_iterator_t iterator); + +/** + * @brief Get partition type + * + * @param iterator Iterator obtained using esp_partition_find. Must be non-NULL. + * + * @return esp_partition_type_t value for partition pointed to by the iterator. + */ +esp_partition_type_t esp_partition_type(esp_partition_iterator_t iterator); + +/** + * @brief Get partition size + * + * @param iterator Iterator obtained using esp_partition_find. Must be non-NULL. + * + * @return partition size, in bytes + */ +uint32_t esp_partition_size(esp_partition_iterator_t iterator); + +/** + * @brief Get partition address + * + * @param iterator Iterator obtained using esp_partition_find. Must be non-NULL. + * + * @return flash address of partition start + */ +uint32_t esp_partition_address(esp_partition_iterator_t iterator); + +/** + * @brief Get partition label + * + * @param iterator Iterator obtained using esp_partition_find. Must be non-NULL. + * + * @return pointer to a zero-terminated string with partition label. + * The pointer is valid until the call to esp_partition_next or + * esp_partition_iterator_release for the given iterator. + */ +const char* esp_partition_label(esp_partition_iterator_t iterator); + +/** + * @brief Read data from the partition + * + * @param iterator Iterator obtained using esp_partition_find. Must be non-NULL. + * @param src_offset Address of the data to be read, relative to the + * beginning of the partition. + * @param dst Pointer to the buffer where data should be stored. + * Must be non-NULL and at least 'size' bytes long. + * @param size Size of data to be read, in bytes. + * + * @return ESP_OK, if data was read successfully; + * ESP_INVALID_ARG, if iterator or src are NULL; + * ESP_INVALID_SIZE, if read would go out of bounds of the partition; + * or one of error codes from lower-level flash driver. + */ +esp_err_t esp_partition_read(esp_partition_iterator_t iterator, + uint32_t src_offset, uint8_t* dst, uint32_t size); + +/** + * @brief Write data to the partition + * + * @param iterator Iterator obtained using esp_partition_find. Must be non-NULL. + * @param src Pointer to the source buffer. Must be non-NULL and + * at least 'size' bytes long. + * @param dst_offset Address where the data should be written, relative to the + * beginning of the partition. + * @param size Size of data to be written, in bytes. + * + * @note Prior to writing to flash memory, make sure it has been erased with + * esp_partition_erase_range call. + * + * @return ESP_OK, if data was written successfully; + * ESP_INVALID_ARG, if iterator or dst are NULL; + * ESP_INVALID_SIZE, if write would go out of bounds of the partition; + * or one of error codes from lower-level flash driver. + */ +esp_err_t esp_partition_write(esp_partition_iterator_t iterator, + const uint8_t* src, uint32_t dst_offset, uint32_t size); + +/** + * @brief Erase part of the partition + * + * @param iterator Iterator obtained using esp_partition_find. Must be non-NULL. + * @param start_addr Address where erase operation should start. Must be aligned + * to 4 kilobytes. + * @param size Size of the range which should be erased, in bytes. + * Must be divisible by 4 kilobytes. + * + * @return ESP_OK, if the range was erased successfully; + * ESP_INVALID_ARG, if iterator or dst are NULL; + * ESP_INVALID_SIZE, if erase would go out of bounds of the partition; + * or one of error codes from lower-level flash driver. + */ +esp_err_t esp_partition_erase_range(esp_partition_iterator_t iterator, + uint32_t start_addr, uint32_t size); + + +/** + * @brief Release partition iterator + * + * Any pointers obtained using esp_partition_label function will be invalid + * after this call. + * + * @param iterator Iterator obtained using esp_partition_find. Must be non-NULL. + * + */ +void esp_partition_iterator_release(esp_partition_iterator_t iterator); + + +#ifdef __cplusplus +} +#endif + + +#endif /* __ESP_PARTITION_H__ */ From 628bde2080a9b99023e3214a8d55aae5962d9eeb Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Wed, 19 Oct 2016 17:05:37 +0800 Subject: [PATCH 086/149] bootloader: move useful structures to esp32 component --- .../bootloader/src/main/bootloader_config.h | 80 +---------- .../bootloader/src/main/bootloader_start.c | 124 ++++++++---------- .../bootloader/src/main/flash_encrypt.c | 2 +- .../esp32/include/esp_flash_data_types.h | 102 ++++++++++++++ 4 files changed, 162 insertions(+), 146 deletions(-) create mode 100644 components/esp32/include/esp_flash_data_types.h diff --git a/components/bootloader/src/main/bootloader_config.h b/components/bootloader/src/main/bootloader_config.h index f99a1c94e..8a837693c 100644 --- a/components/bootloader/src/main/bootloader_config.h +++ b/components/bootloader/src/main/bootloader_config.h @@ -20,12 +20,13 @@ extern "C" { #endif + +#include "esp_flash_data_types.h" + #define BOOT_VERSION "V0.1" #define SPI_SEC_SIZE 0x1000 #define MEM_CACHE(offset) (uint8_t *)(0x3f400000 + (offset)) #define CACHE_READ_32(offset) ((uint32_t *)(0x3f400000 + (offset))) -#define PARTITION_ADD 0x4000 -#define PARTITION_MAGIC 0x50AA #define IROM_LOW 0x400D0000 #define IROM_HIGH 0x40400000 #define DROM_LOW 0x3F400000 @@ -35,73 +36,6 @@ extern "C" #define RTC_DATA_LOW 0x50000000 #define RTC_DATA_HIGH 0x50002000 -/*spi mode,saved in third byte in flash */ -enum { - SPI_MODE_QIO, - SPI_MODE_QOUT, - SPI_MODE_DIO, - SPI_MODE_DOUT, - SPI_MODE_FAST_READ, - SPI_MODE_SLOW_READ -}; -/* spi speed*/ -enum { - SPI_SPEED_40M, - SPI_SPEED_26M, - SPI_SPEED_20M, - SPI_SPEED_80M = 0xF -}; -/*supported flash sizes*/ -enum { - SPI_SIZE_1MB = 0, - SPI_SIZE_2MB, - SPI_SIZE_4MB, - SPI_SIZE_8MB, - SPI_SIZE_16MB, - SPI_SIZE_MAX -}; - - -struct flash_hdr { - char magic; - char blocks; - char spi_mode; /* flag of flash read mode in unpackage and usage in future */ - char spi_speed: 4; /* low bit */ - char spi_size: 4; - unsigned int entry_addr; - uint8_t encrypt_flag; /* encrypt flag */ - uint8_t secury_boot_flag; /* secury boot flag */ - char extra_header[14]; /* ESP32 additional header, unused by second bootloader */ -}; - -/* each header of flash bin block */ -struct block_hdr { - unsigned int load_addr; - unsigned int data_len; -}; - -/* OTA selection structure (two copies in the OTA data partition.) - - Size of 32 bytes is friendly to flash encryption */ -typedef struct { - uint32_t ota_seq; - uint8_t seq_label[24]; - uint32_t crc; /* CRC32 of ota_seq field only */ -} ota_select; - -typedef struct { - uint32_t offset; - uint32_t size; -} partition_pos_t; - -typedef struct { - uint16_t magic; - uint8_t type; /* partition Type */ - uint8_t subtype; /* part_subtype */ - partition_pos_t pos; - uint8_t label[16]; /* label for the partition */ - uint8_t reserved[4]; /* reserved */ -} partition_info_t; #define PART_TYPE_APP 0x00 #define PART_SUBTYPE_FACTORY 0x00 @@ -120,10 +54,10 @@ typedef struct { #define SPI_ERROR_LOG "spi flash error" typedef struct { - partition_pos_t ota_info; - partition_pos_t factory; - partition_pos_t test; - partition_pos_t ota[16]; + esp_partition_pos_t ota_info; + esp_partition_pos_t factory; + esp_partition_pos_t test; + esp_partition_pos_t ota[16]; uint32_t app_count; uint32_t selected_subtype; } bootloader_state_t; diff --git a/components/bootloader/src/main/bootloader_start.c b/components/bootloader/src/main/bootloader_start.c index e87f579f4..5b1e15207 100644 --- a/components/bootloader/src/main/bootloader_start.c +++ b/components/bootloader/src/main/bootloader_start.c @@ -3,7 +3,7 @@ // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at - +// // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software @@ -49,8 +49,8 @@ flash cache is down and the app CPU is in reset. We do have a stack, so we can d extern void Cache_Flush(int); void bootloader_main(); -void unpack_load_app(const partition_pos_t *app_node); -void print_flash_info(struct flash_hdr* pfhdr); +void unpack_load_app(const esp_partition_pos_t *app_node); +void print_flash_info(const esp_image_header_t* pfhdr); void IRAM_ATTR set_cache_and_start_app(uint32_t drom_addr, uint32_t drom_load_addr, uint32_t drom_size, @@ -58,7 +58,7 @@ void IRAM_ATTR set_cache_and_start_app(uint32_t drom_addr, uint32_t irom_load_addr, uint32_t irom_size, uint32_t entry_addr); -static void update_flash_config(struct flash_hdr* pfhdr); +static void update_flash_config(const esp_image_header_t* pfhdr); void IRAM_ATTR call_start_cpu0() @@ -154,7 +154,7 @@ void boot_cache_redirect( uint32_t pos, size_t size ) */ bool load_partition_table(bootloader_state_t* bs, uint32_t addr) { - partition_info_t partition; + esp_partition_info_t partition; uint32_t end = addr + 0x1000; int index = 0; char *partition_usage; @@ -168,7 +168,7 @@ bool load_partition_table(bootloader_state_t* bs, uint32_t addr) ESP_LOGD(TAG, "type=%x subtype=%x", partition.type, partition.subtype); partition_usage = "unknown"; - if (partition.magic == PARTITION_MAGIC) { /* valid partition definition */ + if (partition.magic == ESP_PARTITION_MAGIC) { /* valid partition definition */ switch(partition.type) { case PART_TYPE_APP: /* app partition */ switch(partition.subtype) { @@ -231,12 +231,12 @@ bool load_partition_table(bootloader_state_t* bs, uint32_t addr) return true; } -static uint32_t ota_select_crc(const ota_select *s) +static uint32_t ota_select_crc(const esp_ota_select_entry_t *s) { return crc32_le(UINT32_MAX, (uint8_t*)&s->ota_seq, 4); } -static bool ota_select_valid(const ota_select *s) +static bool ota_select_valid(const esp_ota_select_entry_t *s) { return s->ota_seq != UINT32_MAX && s->crc == ota_select_crc(s); } @@ -252,10 +252,10 @@ void bootloader_main() { ESP_LOGI(TAG, "Espressif ESP32 2nd stage bootloader v. %s", BOOT_VERSION); - struct flash_hdr fhdr; + esp_image_header_t fhdr; bootloader_state_t bs; SpiFlashOpResult spiRet1,spiRet2; - ota_select sa,sb; + esp_ota_select_entry_t sa,sb; memset(&bs, 0, sizeof(bs)); ESP_LOGI(TAG, "compile time " __TIME__ ); @@ -266,18 +266,18 @@ void bootloader_main() /*register first sector in drom0 page 0 */ boot_cache_redirect( 0, 0x5000 ); - memcpy((unsigned int *) &fhdr, MEM_CACHE(0x1000), sizeof(struct flash_hdr) ); + memcpy((unsigned int *) &fhdr, MEM_CACHE(0x1000), sizeof(esp_image_header_t) ); print_flash_info(&fhdr); update_flash_config(&fhdr); - if (!load_partition_table(&bs, PARTITION_ADD)) { + if (!load_partition_table(&bs, ESP_PARTITION_TABLE_ADDR)) { ESP_LOGE(TAG, "load partition table error!"); return; } - partition_pos_t load_part_pos; + esp_partition_pos_t load_part_pos; if (bs.ota_info.offset != 0) { // check if partition table has OTA info partition //ESP_LOGE("OTA info sector handling is not implemented"); @@ -293,14 +293,14 @@ void bootloader_main() sb.crc = ota_select_crc(&sb); Cache_Read_Disable(0); - spiRet1 = SPIEraseSector(bs.ota_info.offset/0x1000); - spiRet2 = SPIEraseSector(bs.ota_info.offset/0x1000+1); + spiRet1 = SPIEraseSector(bs.ota_info.offset/0x1000); + spiRet2 = SPIEraseSector(bs.ota_info.offset/0x1000+1); if (spiRet1 != SPI_FLASH_RESULT_OK || spiRet2 != SPI_FLASH_RESULT_OK ) { ESP_LOGE(TAG, SPI_ERROR_LOG); return; } - spiRet1 = SPIWrite(bs.ota_info.offset,(uint32_t *)&sa,sizeof(ota_select)); - spiRet2 = SPIWrite(bs.ota_info.offset + 0x1000,(uint32_t *)&sb,sizeof(ota_select)); + spiRet1 = SPIWrite(bs.ota_info.offset,(uint32_t *)&sa,sizeof(esp_ota_select_entry_t)); + spiRet2 = SPIWrite(bs.ota_info.offset + 0x1000,(uint32_t *)&sb,sizeof(esp_ota_select_entry_t)); if (spiRet1 != SPI_FLASH_RESULT_OK || spiRet2 != SPI_FLASH_RESULT_OK ) { ESP_LOGE(TAG, SPI_ERROR_LOG); return; @@ -329,7 +329,7 @@ void bootloader_main() } ESP_LOGI(TAG, "Loading app partition at offset %08x", load_part_pos); - if(fhdr.secury_boot_flag == 0x01) { + if(fhdr.secure_boot_flag == 0x01) { /* protect the 2nd_boot */ if(false == secure_boot()){ ESP_LOGE(TAG, "secure boot failed"); @@ -350,12 +350,12 @@ void bootloader_main() } -void unpack_load_app(const partition_pos_t* partition) +void unpack_load_app(const esp_partition_pos_t* partition) { boot_cache_redirect(partition->offset, partition->size); uint32_t pos = 0; - struct flash_hdr image_header; + esp_image_header_t image_header; memcpy(&image_header, MEM_CACHE(pos), sizeof(image_header)); pos += sizeof(image_header); @@ -379,7 +379,7 @@ void unpack_load_app(const partition_pos_t* partition) for (uint32_t section_index = 0; section_index < image_header.blocks; ++section_index) { - struct block_hdr section_header = {0}; + esp_image_section_header_t section_header = {0}; memcpy(§ion_header, MEM_CACHE(pos), sizeof(section_header)); pos += sizeof(section_header); @@ -485,23 +485,23 @@ void IRAM_ATTR set_cache_and_start_app( (*entry)(); } -static void update_flash_config(struct flash_hdr* pfhdr) +static void update_flash_config(const esp_image_header_t* pfhdr) { uint32_t size; switch(pfhdr->spi_size) { - case SPI_SIZE_1MB: + case ESP_IMAGE_FLASH_SIZE_1MB: size = 1; break; - case SPI_SIZE_2MB: + case ESP_IMAGE_FLASH_SIZE_2MB: size = 2; break; - case SPI_SIZE_4MB: + case ESP_IMAGE_FLASH_SIZE_4MB: size = 4; break; - case SPI_SIZE_8MB: + case ESP_IMAGE_FLASH_SIZE_8MB: size = 8; break; - case SPI_SIZE_16MB: + case ESP_IMAGE_FLASH_SIZE_16MB: size = 16; break; default: @@ -516,66 +516,53 @@ static void update_flash_config(struct flash_hdr* pfhdr) Cache_Read_Enable( 0 ); } -void print_flash_info(struct flash_hdr* pfhdr) +void print_flash_info(const esp_image_header_t* phdr) { #if (BOOT_LOG_LEVEL >= BOOT_LOG_LEVEL_NOTICE) - struct flash_hdr fhdr = *pfhdr; - - ESP_LOGD(TAG, "magic %02x", fhdr.magic ); - ESP_LOGD(TAG, "blocks %02x", fhdr.blocks ); - ESP_LOGD(TAG, "spi_mode %02x", fhdr.spi_mode ); - ESP_LOGD(TAG, "spi_speed %02x", fhdr.spi_speed ); - ESP_LOGD(TAG, "spi_size %02x", fhdr.spi_size ); + ESP_LOGD(TAG, "magic %02x", phdr->magic ); + ESP_LOGD(TAG, "blocks %02x", phdr->blocks ); + ESP_LOGD(TAG, "spi_mode %02x", phdr->spi_mode ); + ESP_LOGD(TAG, "spi_speed %02x", phdr->spi_speed ); + ESP_LOGD(TAG, "spi_size %02x", phdr->spi_size ); const char* str; - switch ( fhdr.spi_speed ) { - case SPI_SPEED_40M: + switch ( phdr->spi_speed ) { + case ESP_IMAGE_SPI_SPEED_40M: str = "40MHz"; break; - - case SPI_SPEED_26M: + case ESP_IMAGE_SPI_SPEED_26M: str = "26.7MHz"; break; - - case SPI_SPEED_20M: + case ESP_IMAGE_SPI_SPEED_20M: str = "20MHz"; break; - - case SPI_SPEED_80M: + case ESP_IMAGE_SPI_SPEED_80M: str = "80MHz"; break; - default: str = "20MHz"; break; } ESP_LOGI(TAG, "SPI Speed : %s", str ); - - - switch ( fhdr.spi_mode ) { - case SPI_MODE_QIO: + switch ( phdr->spi_mode ) { + case ESP_IMAGE_SPI_MODE_QIO: str = "QIO"; break; - - case SPI_MODE_QOUT: + case ESP_IMAGE_SPI_MODE_QOUT: str = "QOUT"; break; - - case SPI_MODE_DIO: + case ESP_IMAGE_SPI_MODE_DIO: str = "DIO"; break; - - case SPI_MODE_DOUT: + case ESP_IMAGE_SPI_MODE_DOUT: str = "DOUT"; break; - - case SPI_MODE_FAST_READ: + case ESP_IMAGE_SPI_MODE_FAST_READ: str = "FAST READ"; break; - - case SPI_MODE_SLOW_READ: + case ESP_IMAGE_SPI_MODE_SLOW_READ: str = "SLOW READ"; break; default: @@ -584,31 +571,24 @@ void print_flash_info(struct flash_hdr* pfhdr) } ESP_LOGI(TAG, "SPI Mode : %s", str ); - - - switch ( fhdr.spi_size ) { - case SPI_SIZE_1MB: + switch ( phdr->spi_size ) { + case ESP_IMAGE_FLASH_SIZE_1MB: str = "1MB"; break; - - case SPI_SIZE_2MB: + case ESP_IMAGE_FLASH_SIZE_2MB: str = "2MB"; break; - - case SPI_SIZE_4MB: + case ESP_IMAGE_FLASH_SIZE_4MB: str = "4MB"; break; - - case SPI_SIZE_8MB: + case ESP_IMAGE_FLASH_SIZE_8MB: str = "8MB"; break; - - case SPI_SIZE_16MB: + case ESP_IMAGE_FLASH_SIZE_16MB: str = "16MB"; break; - default: - str = "1MB"; + str = "2MB"; break; } ESP_LOGI(TAG, "SPI Flash Size : %s", str ); diff --git a/components/bootloader/src/main/flash_encrypt.c b/components/bootloader/src/main/flash_encrypt.c index 26e66aa03..2fb57a987 100644 --- a/components/bootloader/src/main/flash_encrypt.c +++ b/components/bootloader/src/main/flash_encrypt.c @@ -128,7 +128,7 @@ bool flash_encrypt(bootloader_state_t *bs) return false; } /* encrypt partition table */ - if (false == flash_encrypt_write(PARTITION_ADD, SPI_SEC_SIZE)) { + if (false == flash_encrypt_write(ESP_PARTITION_TABLE_ADDR, SPI_SEC_SIZE)) { ESP_LOGE(TAG, "encrypt partition table error"); return false; } diff --git a/components/esp32/include/esp_flash_data_types.h b/components/esp32/include/esp_flash_data_types.h new file mode 100644 index 000000000..b16ee59f5 --- /dev/null +++ b/components/esp32/include/esp_flash_data_types.h @@ -0,0 +1,102 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef __ESP_BIN_TYPES_H__ +#define __ESP_BIN_TYPES_H__ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define ESP_PARTITION_TABLE_ADDR 0x4000 +#define ESP_PARTITION_MAGIC 0x50AA + +/*spi mode,saved in third byte in flash */ +typedef enum { + ESP_IMAGE_SPI_MODE_QIO, + ESP_IMAGE_SPI_MODE_QOUT, + ESP_IMAGE_SPI_MODE_DIO, + ESP_IMAGE_SPI_MODE_DOUT, + ESP_IMAGE_SPI_MODE_FAST_READ, + ESP_IMAGE_SPI_MODE_SLOW_READ +} esp_image_spi_mode_t; + +/* spi speed*/ +enum { + ESP_IMAGE_SPI_SPEED_40M, + ESP_IMAGE_SPI_SPEED_26M, + ESP_IMAGE_SPI_SPEED_20M, + ESP_IMAGE_SPI_SPEED_80M = 0xF +} esp_image_spi_freq_t; + +/*supported flash sizes*/ +typedef enum { + ESP_IMAGE_FLASH_SIZE_1MB = 0, + ESP_IMAGE_FLASH_SIZE_2MB, + ESP_IMAGE_FLASH_SIZE_4MB, + ESP_IMAGE_FLASH_SIZE_8MB, + ESP_IMAGE_FLASH_SIZE_16MB, + ESP_IMAGE_FLASH_SIZE_MAX +} esp_image_flash_size_t; + +typedef struct { + char magic; + char blocks; + char spi_mode; /* flag of flash read mode in unpackage and usage in future */ + char spi_speed: 4; /* low bit */ + char spi_size: 4; + unsigned int entry_addr; + uint8_t encrypt_flag; /* encrypt flag */ + uint8_t secure_boot_flag; /* secure boot flag */ + char extra_header[14]; /* ESP32 additional header, unused by second bootloader */ +} esp_image_header_t; + +/* each header of flash bin block */ +typedef struct { + unsigned int load_addr; + unsigned int data_len; +} esp_image_section_header_t; + + +/* OTA selection structure (two copies in the OTA data partition.) + Size of 32 bytes is friendly to flash encryption */ +typedef struct { + uint32_t ota_seq; + uint8_t seq_label[24]; + uint32_t crc; /* CRC32 of ota_seq field only */ +} esp_ota_select_entry_t; + + +typedef struct { + uint32_t offset; + uint32_t size; +} esp_partition_pos_t; + +typedef struct { + uint16_t magic; + uint8_t type; /* partition Type */ + uint8_t subtype; /* part_subtype */ + esp_partition_pos_t pos; + uint8_t label[16]; /* label for the partition */ + uint8_t reserved[4]; /* reserved */ +} esp_partition_info_t; + + +#ifdef __cplusplus +} +#endif + +#endif //__ESP_BIN_TYPES_H__ From 54ca573ce465b4a12cad9cc804c6c80063dcab35 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Wed, 19 Oct 2016 17:08:05 +0800 Subject: [PATCH 087/149] spi_flash: move cache operations into separate file --- components/spi_flash/README.rst | 33 +++ .../{esp_spi_flash.c => cache_utils.c} | 201 ++++-------------- components/spi_flash/cache_utils.h | 44 ++++ components/spi_flash/flash_ops.c | 158 ++++++++++++++ 4 files changed, 274 insertions(+), 162 deletions(-) create mode 100644 components/spi_flash/README.rst rename components/spi_flash/{esp_spi_flash.c => cache_utils.c} (53%) create mode 100644 components/spi_flash/cache_utils.h create mode 100644 components/spi_flash/flash_ops.c diff --git a/components/spi_flash/README.rst b/components/spi_flash/README.rst new file mode 100644 index 000000000..22f98cf02 --- /dev/null +++ b/components/spi_flash/README.rst @@ -0,0 +1,33 @@ +Driver for SPI flash read/write/erase operations +================================================ + +Implementation notes +-------------------- + +In order to perform some flash operations, we need to make sure both CPUs +are not running any code from flash for the duration of the flash operation. +In a single-core setup this is easy: we disable interrupts/scheduler and do +the flash operation. In the dual-core setup this is slightly more complicated. +We need to make sure that the other CPU doesn't run any code from flash. + + +When SPI flash API is called on CPU A (can be PRO or APP), we start +spi_flash_op_block_func function on CPU B using esp_ipc_call API. This API +wakes up high priority task on CPU B and tells it to execute given function, +in this case spi_flash_op_block_func. This function disables cache on CPU B and +signals that cache is disabled by setting s_flash_op_can_start flag. +Then the task on CPU A disables cache as well, and proceeds to execute flash +operation. + +While flash operation is running, interrupts can still run on CPU B. +We assume that all interrupt code is placed into RAM. + +Once flash operation is complete, function on CPU A sets another flag, +s_flash_op_complete, to let the task on CPU B know that it can re-enable +cache and release the CPU. Then the function on CPU A re-enables the cache on +CPU A as well and returns control to the calling code. + +Additionally, all API functions are protected with a mutex (s_flash_op_mutex). + +In a single core environment (CONFIG_FREERTOS_UNICORE enabled), we simply +disable both caches, no inter-CPU communication takes place. diff --git a/components/spi_flash/esp_spi_flash.c b/components/spi_flash/cache_utils.c similarity index 53% rename from components/spi_flash/esp_spi_flash.c rename to components/spi_flash/cache_utils.c index d702f3b81..6ae47bdb3 100644 --- a/components/spi_flash/esp_spi_flash.c +++ b/components/spi_flash/cache_utils.c @@ -3,7 +3,7 @@ // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at - +// // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software @@ -30,39 +30,7 @@ #include "esp_spi_flash.h" #include "esp_log.h" -/* - Driver for SPI flash read/write/erase operations - In order to perform some flash operations, we need to make sure both CPUs - are not running any code from flash for the duration of the flash operation. - In a single-core setup this is easy: we disable interrupts/scheduler and do - the flash operation. In the dual-core setup this is slightly more complicated. - We need to make sure that the other CPU doesn't run any code from flash. - - - When SPI flash API is called on CPU A (can be PRO or APP), we start - spi_flash_op_block_func function on CPU B using esp_ipc_call API. This API - wakes up high priority task on CPU B and tells it to execute given function, - in this case spi_flash_op_block_func. This function disables cache on CPU B and - signals that cache is disabled by setting s_flash_op_can_start flag. - Then the task on CPU A disables cache as well, and proceeds to execute flash - operation. - - While flash operation is running, interrupts can still run on CPU B. - We assume that all interrupt code is placed into RAM. - - Once flash operation is complete, function on CPU A sets another flag, - s_flash_op_complete, to let the task on CPU B know that it can re-enable - cache and release the CPU. Then the function on CPU A re-enables the cache on - CPU A as well and returns control to the calling code. - - Additionally, all API functions are protected with a mutex (s_flash_op_mutex). - - In a single core environment (CONFIG_FREERTOS_UNICORE enabled), we simply - disable both caches, no inter-CPU communication takes place. -*/ - -static esp_err_t spi_flash_translate_rc(SpiFlashOpResult rc); static void IRAM_ATTR spi_flash_disable_cache(uint32_t cpuid, uint32_t* saved_state); static void IRAM_ATTR spi_flash_restore_cache(uint32_t cpuid, uint32_t saved_state); @@ -72,25 +40,23 @@ static uint32_t s_flash_op_cache_state[2]; static SemaphoreHandle_t s_flash_op_mutex; static bool s_flash_op_can_start = false; static bool s_flash_op_complete = false; -#endif //CONFIG_FREERTOS_UNICORE -#if CONFIG_SPI_FLASH_ENABLE_COUNTERS -static const char* TAG = "spi_flash"; -static spi_flash_counters_t s_flash_stats; +void spi_flash_init_lock() +{ + s_flash_op_mutex = xSemaphoreCreateMutex(); +} -#define COUNTER_START() uint32_t ts_begin = xthal_get_ccount() -#define COUNTER_STOP(counter) do{ s_flash_stats.counter.count++; s_flash_stats.counter.time += (xthal_get_ccount() - ts_begin) / (XT_CLOCK_FREQ / 1000000); } while(0) -#define COUNTER_ADD_BYTES(counter, size) do { s_flash_stats.counter.bytes += size; } while (0) -#else -#define COUNTER_START() -#define COUNTER_STOP(counter) -#define COUNTER_ADD_BYTES(counter, size) +void spi_flash_op_lock() +{ + xSemaphoreTake(s_flash_op_mutex, portMAX_DELAY); +} -#endif //CONFIG_SPI_FLASH_ENABLE_COUNTERS +void spi_flash_op_unlock() +{ + xSemaphoreGive(s_flash_op_mutex); +} -#ifndef CONFIG_FREERTOS_UNICORE - -static void IRAM_ATTR spi_flash_op_block_func(void* arg) +void IRAM_ATTR spi_flash_op_block_func(void* arg) { // Disable scheduler on this CPU vTaskSuspendAll(); @@ -108,19 +74,9 @@ static void IRAM_ATTR spi_flash_op_block_func(void* arg) xTaskResumeAll(); } -void spi_flash_init() +void IRAM_ATTR spi_flash_disable_interrupts_caches_and_other_cpu() { - s_flash_op_mutex = xSemaphoreCreateMutex(); - -#if CONFIG_SPI_FLASH_ENABLE_COUNTERS - spi_flash_reset_counters(); -#endif -} - -static void IRAM_ATTR spi_flash_disable_interrupts_caches_and_other_cpu() -{ - // Take the API lock - xSemaphoreTake(s_flash_op_mutex, portMAX_DELAY); + spi_flash_op_lock(); const uint32_t cpuid = xPortGetCoreID(); const uint32_t other_cpuid = (cpuid == 0) ? 1 : 0; @@ -152,7 +108,7 @@ static void IRAM_ATTR spi_flash_disable_interrupts_caches_and_other_cpu() spi_flash_disable_cache(cpuid, &s_flash_op_cache_state[cpuid]); } -static void IRAM_ATTR spi_flash_enable_interrupts_caches_and_other_cpu() +void IRAM_ATTR spi_flash_enable_interrupts_caches_and_other_cpu() { const uint32_t cpuid = xPortGetCoreID(); const uint32_t other_cpuid = (cpuid == 0) ? 1 : 0; @@ -173,98 +129,45 @@ static void IRAM_ATTR spi_flash_enable_interrupts_caches_and_other_cpu() xTaskResumeAll(); } // Release API lock - xSemaphoreGive(s_flash_op_mutex); + spi_flash_op_unlock(); } -#else // CONFIG_FREERTOS_UNICORE +#else // CONFIG_FREERTOS_UNICORE -void spi_flash_init() +void spi_flash_init_lock() { -#if CONFIG_SPI_FLASH_ENABLE_COUNTERS - spi_flash_reset_counters(); -#endif } -static void IRAM_ATTR spi_flash_disable_interrupts_caches_and_other_cpu() +void spi_flash_op_lock() { vTaskSuspendAll(); +} + +void spi_flash_op_unlock() +{ + xTaskResumeAll(); +} + + +void IRAM_ATTR spi_flash_disable_interrupts_caches_and_other_cpu() +{ + spi_flash_op_lock(); spi_flash_disable_cache(0, &s_flash_op_cache_state[0]); } -static void IRAM_ATTR spi_flash_enable_interrupts_caches_and_other_cpu() +void IRAM_ATTR spi_flash_enable_interrupts_caches_and_other_cpu() { spi_flash_restore_cache(0, s_flash_op_cache_state[0]); - xTaskResumeAll(); + spi_flash_op_unlock(); } #endif // CONFIG_FREERTOS_UNICORE - -SpiFlashOpResult IRAM_ATTR spi_flash_unlock() -{ - static bool unlocked = false; - if (!unlocked) { - SpiFlashOpResult rc = SPIUnlock(); - if (rc != SPI_FLASH_RESULT_OK) { - return rc; - } - unlocked = true; - } - return SPI_FLASH_RESULT_OK; -} - -esp_err_t IRAM_ATTR spi_flash_erase_sector(uint16_t sec) -{ - COUNTER_START(); - spi_flash_disable_interrupts_caches_and_other_cpu(); - SpiFlashOpResult rc; - rc = spi_flash_unlock(); - if (rc == SPI_FLASH_RESULT_OK) { - rc = SPIEraseSector(sec); - } - spi_flash_enable_interrupts_caches_and_other_cpu(); - COUNTER_STOP(erase); - return spi_flash_translate_rc(rc); -} - -esp_err_t IRAM_ATTR spi_flash_write(uint32_t dest_addr, const uint32_t *src, uint32_t size) -{ - COUNTER_START(); - spi_flash_disable_interrupts_caches_and_other_cpu(); - SpiFlashOpResult rc; - rc = spi_flash_unlock(); - if (rc == SPI_FLASH_RESULT_OK) { - rc = SPIWrite(dest_addr, src, (int32_t) size); - COUNTER_ADD_BYTES(write, size); - } - spi_flash_enable_interrupts_caches_and_other_cpu(); - COUNTER_STOP(write); - return spi_flash_translate_rc(rc); -} - -esp_err_t IRAM_ATTR spi_flash_read(uint32_t src_addr, uint32_t *dest, uint32_t size) -{ - COUNTER_START(); - spi_flash_disable_interrupts_caches_and_other_cpu(); - SpiFlashOpResult rc = SPIRead(src_addr, dest, (int32_t) size); - COUNTER_ADD_BYTES(read, size); - spi_flash_enable_interrupts_caches_and_other_cpu(); - COUNTER_STOP(read); - return spi_flash_translate_rc(rc); -} - -static esp_err_t spi_flash_translate_rc(SpiFlashOpResult rc) -{ - switch (rc) { - case SPI_FLASH_RESULT_OK: - return ESP_OK; - case SPI_FLASH_RESULT_TIMEOUT: - return ESP_ERR_FLASH_OP_TIMEOUT; - case SPI_FLASH_RESULT_ERR: - default: - return ESP_ERR_FLASH_OP_FAIL; - } -} +/** + * The following two functions are replacements for Cache_Read_Disable and Cache_Read_Enable + * function in ROM. They are used to work around a bug where Cache_Read_Disable requires a call to + * Cache_Flush before Cache_Read_Enable, even if cached data was not modified. + */ static const uint32_t cache_mask = DPORT_APP_CACHE_MASK_OPSDRAM | DPORT_APP_CACHE_MASK_DROM0 | DPORT_APP_CACHE_MASK_DRAM1 | DPORT_APP_CACHE_MASK_IROM0 | @@ -300,29 +203,3 @@ static void IRAM_ATTR spi_flash_restore_cache(uint32_t cpuid, uint32_t saved_sta } } -#if CONFIG_SPI_FLASH_ENABLE_COUNTERS - -static inline void dump_counter(spi_flash_counter_t* counter, const char* name) -{ - ESP_LOGI(TAG, "%s count=%8d time=%8dms bytes=%8d\n", name, - counter->count, counter->time, counter->bytes); -} - -const spi_flash_counters_t* spi_flash_get_counters() -{ - return &s_flash_stats; -} - -void spi_flash_reset_counters() -{ - memset(&s_flash_stats, 0, sizeof(s_flash_stats)); -} - -void spi_flash_dump_counters() -{ - dump_counter(&s_flash_stats.read, "read "); - dump_counter(&s_flash_stats.write, "write"); - dump_counter(&s_flash_stats.erase, "erase"); -} - -#endif //CONFIG_SPI_FLASH_ENABLE_COUNTERS diff --git a/components/spi_flash/cache_utils.h b/components/spi_flash/cache_utils.h new file mode 100644 index 000000000..899a31c65 --- /dev/null +++ b/components/spi_flash/cache_utils.h @@ -0,0 +1,44 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ESP_SPI_FLASH_CACHE_UTILS_H +#define ESP_SPI_FLASH_CACHE_UTILS_H + +/** + * This header file contains declarations of cache manipulation functions + * used both in flash_ops.c and flash_mmap.c. + * + * These functions are considered internal and are not designed to be called from applications. + */ + +// Init mutex protecting access to spi_flash_* APIs +void spi_flash_init_lock(); + +// Take mutex protecting access to spi_flash_* APIs +void spi_flash_op_lock(); + +// Release said mutex +void spi_flash_op_unlock(); + +// Suspend the scheduler on both CPUs, disable cache. +// Contrary to its name this doesn't do anything with interrupts, yet. +// Interrupt disabling capability will be added once we implement +// interrupt allocation API. +void spi_flash_disable_interrupts_caches_and_other_cpu(); + +// Enable cache, enable interrupts (to be added in future), resume scheduler +void spi_flash_enable_interrupts_caches_and_other_cpu(); + + +#endif //ESP_SPI_FLASH_CACHE_UTILS_H diff --git a/components/spi_flash/flash_ops.c b/components/spi_flash/flash_ops.c new file mode 100644 index 000000000..99e8ef77f --- /dev/null +++ b/components/spi_flash/flash_ops.c @@ -0,0 +1,158 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include "sdkconfig.h" +#include "esp_ipc.h" +#include "esp_attr.h" +#include "esp_spi_flash.h" +#include "esp_log.h" +#include "cache_utils.h" + +#if CONFIG_SPI_FLASH_ENABLE_COUNTERS +static const char* TAG = "spi_flash"; +static spi_flash_counters_t s_flash_stats; + +#define COUNTER_START() uint32_t ts_begin = xthal_get_ccount() +#define COUNTER_STOP(counter) \ + do{ \ + s_flash_stats.counter.count++; \ + s_flash_stats.counter.time += (xthal_get_ccount() - ts_begin) / (XT_CLOCK_FREQ / 1000000); \\ + } while(0) + +#define COUNTER_ADD_BYTES(counter, size) \ + do { \ + s_flash_stats.counter.bytes += size; \ + } while (0) + +#else +#define COUNTER_START() +#define COUNTER_STOP(counter) +#define COUNTER_ADD_BYTES(counter, size) + +#endif //CONFIG_SPI_FLASH_ENABLE_COUNTERS + +static esp_err_t spi_flash_translate_rc(SpiFlashOpResult rc); + +void spi_flash_init() +{ + spi_flash_init_lock(); +#if CONFIG_SPI_FLASH_ENABLE_COUNTERS + spi_flash_reset_counters(); +#endif +} + +SpiFlashOpResult IRAM_ATTR spi_flash_unlock() +{ + static bool unlocked = false; + if (!unlocked) { + SpiFlashOpResult rc = SPIUnlock(); + if (rc != SPI_FLASH_RESULT_OK) { + return rc; + } + unlocked = true; + } + return SPI_FLASH_RESULT_OK; +} + +esp_err_t IRAM_ATTR spi_flash_erase_sector(uint16_t sec) +{ + COUNTER_START(); + spi_flash_disable_interrupts_caches_and_other_cpu(); + SpiFlashOpResult rc; + rc = spi_flash_unlock(); + if (rc == SPI_FLASH_RESULT_OK) { + rc = SPIEraseSector(sec); + } + spi_flash_enable_interrupts_caches_and_other_cpu(); + COUNTER_STOP(erase); + return spi_flash_translate_rc(rc); +} + +esp_err_t IRAM_ATTR spi_flash_write(uint32_t dest_addr, const uint32_t *src, uint32_t size) +{ + COUNTER_START(); + spi_flash_disable_interrupts_caches_and_other_cpu(); + SpiFlashOpResult rc; + rc = spi_flash_unlock(); + if (rc == SPI_FLASH_RESULT_OK) { + rc = SPIWrite(dest_addr, src, (int32_t) size); + COUNTER_ADD_BYTES(write, size); + } + spi_flash_enable_interrupts_caches_and_other_cpu(); + COUNTER_STOP(write); + return spi_flash_translate_rc(rc); +} + +esp_err_t IRAM_ATTR spi_flash_read(uint32_t src_addr, uint32_t *dest, uint32_t size) +{ + COUNTER_START(); + spi_flash_disable_interrupts_caches_and_other_cpu(); + SpiFlashOpResult rc = SPIRead(src_addr, dest, (int32_t) size); + COUNTER_ADD_BYTES(read, size); + spi_flash_enable_interrupts_caches_and_other_cpu(); + COUNTER_STOP(read); + return spi_flash_translate_rc(rc); +} + +static esp_err_t spi_flash_translate_rc(SpiFlashOpResult rc) +{ + switch (rc) { + case SPI_FLASH_RESULT_OK: + return ESP_OK; + case SPI_FLASH_RESULT_TIMEOUT: + return ESP_ERR_FLASH_OP_TIMEOUT; + case SPI_FLASH_RESULT_ERR: + default: + return ESP_ERR_FLASH_OP_FAIL; + } +} + +#if CONFIG_SPI_FLASH_ENABLE_COUNTERS + +static inline void dump_counter(spi_flash_counter_t* counter, const char* name) +{ + ESP_LOGI(TAG, "%s count=%8d time=%8dms bytes=%8d\n", name, + counter->count, counter->time, counter->bytes); +} + +const spi_flash_counters_t* spi_flash_get_counters() +{ + return &s_flash_stats; +} + +void spi_flash_reset_counters() +{ + memset(&s_flash_stats, 0, sizeof(s_flash_stats)); +} + +void spi_flash_dump_counters() +{ + dump_counter(&s_flash_stats.read, "read "); + dump_counter(&s_flash_stats.write, "write"); + dump_counter(&s_flash_stats.erase, "erase"); +} + +#endif //CONFIG_SPI_FLASH_ENABLE_COUNTERS From 42068c3b36cb4b5898ea0f9b353057dd85af33ab Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Wed, 19 Oct 2016 17:17:24 +0800 Subject: [PATCH 088/149] spi_flash: implement mmap/munmap --- components/esp32/include/esp_err.h | 2 + components/esp32/include/soc/dport_reg.h | 5 + components/spi_flash/flash_mmap.c | 211 +++++++++++++++++++ components/spi_flash/include/esp_spi_flash.h | 57 +++++ 4 files changed, 275 insertions(+) create mode 100644 components/spi_flash/flash_mmap.c diff --git a/components/esp32/include/esp_err.h b/components/esp32/include/esp_err.h index 4f013f91a..af9d2ec33 100644 --- a/components/esp32/include/esp_err.h +++ b/components/esp32/include/esp_err.h @@ -31,6 +31,8 @@ typedef int32_t esp_err_t; #define ESP_ERR_NO_MEM 0x101 #define ESP_ERR_INVALID_ARG 0x102 #define ESP_ERR_INVALID_STATE 0x103 +#define ESP_ERR_INVALID_SIZE 0x104 +#define ESP_ERR_NOT_FOUND 0x105 /** * Macro which can be used to check the error code, diff --git a/components/esp32/include/soc/dport_reg.h b/components/esp32/include/soc/dport_reg.h index d65d9edbc..0c43c0874 100644 --- a/components/esp32/include/soc/dport_reg.h +++ b/components/esp32/include/soc/dport_reg.h @@ -3830,6 +3830,11 @@ #define DPORT_DATE_S 0 #define DPORT_DPORT_DATE_VERSION 0x1605190 +/* Flash MMU table for PRO CPU */ +#define DPORT_PRO_FLASH_MMU_TABLE ((volatile uint32_t*) 0x3FF10000) + +/* Flash MMU table for APP CPU */ +#define DPORT_APP_FLASH_MMU_TABLE ((volatile uint32_t*) 0x3FF12000) diff --git a/components/spi_flash/flash_mmap.c b/components/spi_flash/flash_mmap.c new file mode 100644 index 000000000..8636a2605 --- /dev/null +++ b/components/spi_flash/flash_mmap.c @@ -0,0 +1,211 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include "sdkconfig.h" +#include "esp_ipc.h" +#include "esp_attr.h" +#include "esp_spi_flash.h" +#include "esp_log.h" +#include "cache_utils.h" + +#ifndef NDEBUG +// Enable built-in checks in queue.h in debug builds +#define INVARIANTS +#endif +#include "rom/queue.h" + +#define REGIONS_COUNT 4 +#define PAGES_PER_REGION 64 +#define FLASH_PAGE_SIZE 0x10000 +#define INVALID_ENTRY_VAL 0x100 +#define VADDR0_START_ADDR 0x3F400000 +#define VADDR1_START_ADDR 0x40000000 +#define VADDR1_FIRST_USABLE_ADDR 0x400D0000 +#define PRO_IRAM0_FIRST_USABLE_PAGE ((VADDR1_FIRST_USABLE_ADDR - VADDR1_START_ADDR) / FLASH_PAGE_SIZE + 64) + + +typedef struct mmap_entry_{ + uint32_t handle; + int page; + int count; + LIST_ENTRY(mmap_entry_) entries; +} mmap_entry_t; + + +static LIST_HEAD(mmap_entries_head, mmap_entry_) s_mmap_entries_head = + LIST_HEAD_INITIALIZER(s_mmap_entries_head); +static uint8_t s_mmap_page_refcnt[REGIONS_COUNT * PAGES_PER_REGION] = {0}; +static uint32_t s_mmap_last_handle = 0; + + +static void IRAM_ATTR spi_flash_mmap_init() +{ + for (int i = 0; i < REGIONS_COUNT * PAGES_PER_REGION; ++i) { + uint32_t entry_pro = DPORT_PRO_FLASH_MMU_TABLE[i]; + uint32_t entry_app = DPORT_APP_FLASH_MMU_TABLE[i]; + if (entry_pro != entry_app) { + // clean up entries used by boot loader + entry_pro = 0; + DPORT_PRO_FLASH_MMU_TABLE[i] = 0; + } + if ((entry_pro & 0x100) == 0 && (i == 0 || i == PRO_IRAM0_FIRST_USABLE_PAGE || entry_pro != 0)) { + s_mmap_page_refcnt[i] = 1; + } + } +} + +esp_err_t IRAM_ATTR spi_flash_mmap(uint32_t src_addr, size_t size, spi_flash_mmap_memory_t memory, + const void** out_ptr, spi_flash_mmap_handle_t* out_handle) +{ + esp_err_t ret; + mmap_entry_t* new_entry = (mmap_entry_t*) malloc(sizeof(mmap_entry_t)); + if (new_entry == 0) { + return ESP_ERR_NO_MEM; + } + if (src_addr & 0xffff) { + return ESP_ERR_INVALID_ARG; + } + spi_flash_disable_interrupts_caches_and_other_cpu(); + if (s_mmap_page_refcnt[0] == 0) { + spi_flash_mmap_init(); + } + // figure out the memory region where we should look for pages + int region_begin; // first page to check + int region_size; // number of pages to check + uint32_t region_addr; // base address of memory region + if (memory == SPI_FLASH_MMAP_DATA) { + // Vaddr0 + region_begin = 0; + region_size = 64; + region_addr = VADDR0_START_ADDR; + } else { + // only part of VAddr1 is usable, so adjust for that + region_begin = VADDR1_FIRST_USABLE_ADDR; + region_size = 3 * 64 - region_begin; + region_addr = VADDR1_FIRST_USABLE_ADDR; + } + // region which should be mapped + int phys_page = src_addr / FLASH_PAGE_SIZE; + int page_count = (size + FLASH_PAGE_SIZE - 1) / FLASH_PAGE_SIZE; + // The following part searches for a range of MMU entries which can be used. + // Algorithm is essentially naïve strstr algorithm, except that unused MMU + // entries are treated as wildcards. + int start; + int end = region_begin + region_size - page_count; + for (start = region_begin; start < end; ++start) { + int page = phys_page; + int pos; + for (pos = start; pos < start + page_count; ++pos, ++page) { + int table_val = (int) DPORT_PRO_FLASH_MMU_TABLE[pos]; + uint8_t refcnt = s_mmap_page_refcnt[pos]; + if (refcnt != 0 && table_val != page) { + break; + } + } + // whole mapping range matched, bail out + if (pos - start == page_count) { + break; + } + } + // checked all the region(s) and haven't found anything? + if (start == end) { + *out_handle = 0; + *out_ptr = NULL; + ret = ESP_ERR_NO_MEM; + } else { + // set up mapping using pages [start, start + page_count) + uint32_t entry_val = (uint32_t) phys_page; + for (int i = start; i != start + page_count; ++i, ++entry_val) { + // sanity check: we won't reconfigure entries with non-zero reference count + assert(s_mmap_page_refcnt[i] == 0 || + (DPORT_PRO_FLASH_MMU_TABLE[i] == entry_val && + DPORT_APP_FLASH_MMU_TABLE[i] == entry_val)); + if (s_mmap_page_refcnt[i] == 0) { + DPORT_PRO_FLASH_MMU_TABLE[i] = entry_val; + DPORT_APP_FLASH_MMU_TABLE[i] = entry_val; + } + ++s_mmap_page_refcnt[i]; + } + + LIST_INSERT_HEAD(&s_mmap_entries_head, new_entry, entries); + new_entry->page = start; + new_entry->count = page_count; + new_entry->handle = ++s_mmap_last_handle; + *out_handle = new_entry->handle; + *out_ptr = (void*) (region_addr + start * FLASH_PAGE_SIZE); + ret = ESP_OK; + } + spi_flash_enable_interrupts_caches_and_other_cpu(); + if (*out_ptr == NULL) { + free(new_entry); + } + return ret; +} + +void IRAM_ATTR spi_flash_munmap(spi_flash_mmap_handle_t handle) +{ + spi_flash_disable_interrupts_caches_and_other_cpu(); + mmap_entry_t* it; + // look for handle in linked list + for (it = LIST_FIRST(&s_mmap_entries_head); it != NULL; it = LIST_NEXT(it, entries)) { + if (it->handle == handle) { + // for each page, decrement reference counter + // if reference count is zero, disable MMU table entry to + // facilitate debugging of use-after-free conditions + for (int i = it->page; i < it->page + it->count; ++i) { + assert(s_mmap_page_refcnt[i] > 0); + if (--s_mmap_page_refcnt[i] == 0) { + DPORT_PRO_FLASH_MMU_TABLE[i] = INVALID_ENTRY_VAL; + DPORT_APP_FLASH_MMU_TABLE[i] = INVALID_ENTRY_VAL; + } + } + LIST_REMOVE(it, entries); + break; + } + } + spi_flash_enable_interrupts_caches_and_other_cpu(); + if (it == NULL) { + assert(0 && "invalid handle, or handle already unmapped"); + } + free(it); +} + +void spi_flash_mmap_dump() +{ + if (s_mmap_page_refcnt[0] == 0) { + spi_flash_mmap_init(); + } + mmap_entry_t* it; + for (it = LIST_FIRST(&s_mmap_entries_head); it != NULL; it = LIST_NEXT(it, entries)) { + printf("handle=%d page=%d count=%d\n", it->handle, it->page, it->count); + } + for (int i = 0; i < REGIONS_COUNT * PAGES_PER_REGION; ++i) { + if (s_mmap_page_refcnt[i] != 0) { + printf("page %d: refcnt=%d paddr=%d\n", + i, (int) s_mmap_page_refcnt[i], DPORT_PRO_FLASH_MMU_TABLE[i]); + } + } +} diff --git a/components/spi_flash/include/esp_spi_flash.h b/components/spi_flash/include/esp_spi_flash.h index 6d635880e..597e41857 100644 --- a/components/spi_flash/include/esp_spi_flash.h +++ b/components/spi_flash/include/esp_spi_flash.h @@ -70,6 +70,63 @@ esp_err_t spi_flash_write(uint32_t des_addr, const uint32_t *src_addr, uint32_t esp_err_t spi_flash_read(uint32_t src_addr, uint32_t *des_addr, uint32_t size); +/** + * @brief Enumeration which specifies memory space requested in an mmap call + */ +typedef enum { + SPI_FLASH_MMAP_DATA, /**< map to data memory (Vaddr0), allows byte-aligned access, 4 MB total */ + SPI_FLASH_MMAP_INST, /**< map to instruction memory (Vaddr1-3), allows only 4-byte-aligned access, 11 MB total */ +} spi_flash_mmap_memory_t; + +/** + * @brief Opaque handle for memory region obtained from spi_flash_mmap. + */ +typedef uint32_t spi_flash_mmap_handle_t; + +/** + * @brief Map region of flash memory into data or instruction address space + * + * This function allocates sufficient number of 64k MMU pages and configures + * them to map request region of flash memory into data address space or into + * instruction address space. It may reuse MMU pages which already provide + * required mapping. As with any allocator, there is possibility of fragmentation + * of address space if mmap/munmap are heavily used. To troubleshoot issues with + * page allocation, use spi_flash_mmap_dump function. + * + * @param src_addr Physical address in flash where requested region starts. + * This address *must* be aligned to 64kB boundary. + * @param size Size of region which has to be mapped. This size will be rounded + * up to a 64k boundary. + * @param memory Memory space where the region should be mapped + * @param out_ptr Output, pointer to the mapped memory region + * @param out_handle Output, handle which should be used for spi_flash_munmap call + * + * @return ESP_OK on success, ESP_ERR_NO_MEM if pages can not be allocated + */ +esp_err_t spi_flash_mmap(uint32_t src_addr, size_t size, spi_flash_mmap_memory_t memory, + const void** out_ptr, spi_flash_mmap_handle_t* out_handle); + +/** + * @brief Release region previously obtained using spi_flash_mmap + * + * @note Calling this function will not necessarily unmap memory region. + * Region will only be unmapped when there are no other handles which + * reference this region. In case of partially overlapping regions + * it is possible that memory will be unmapped partially. + * + * @param handle Handle obtained from spi_flash_mmap + */ +void spi_flash_munmap(spi_flash_mmap_handle_t handle); + +/** + * @brief Display information about mapped regions + * + * This function lists handles obtained using spi_flash_mmap, along with range + * of pages allocated to each handle. It also lists all non-zero entries of + * MMU table and corresponding reference counts. + */ +void spi_flash_mmap_dump(); + #if CONFIG_SPI_FLASH_ENABLE_COUNTERS /** From 999e6d4e8f9035f8de54410a8e037ec7ada3e917 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Wed, 19 Oct 2016 17:19:02 +0800 Subject: [PATCH 089/149] freertos: move panic handler data to DRAM Fixes https://ezredmine.espressif.com/issues/7817 --- components/esp32/ld/esp32.common.ld | 1 + 1 file changed, 1 insertion(+) diff --git a/components/esp32/ld/esp32.common.ld b/components/esp32/ld/esp32.common.ld index a3c636784..2226e9882 100644 --- a/components/esp32/ld/esp32.common.ld +++ b/components/esp32/ld/esp32.common.ld @@ -92,6 +92,7 @@ SECTIONS KEEP(*(.gnu.linkonce.s2.*)) KEEP(*(.jcr)) *(.dram1 .dram1.*) + *libfreertos.a:panic.o(.rodata .rodata.*) _data_end = ABSOLUTE(.); . = ALIGN(4); _heap_start = ABSOLUTE(.); From 079d9ea018729c95a9aea0ac759f40849cb13f5e Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Wed, 19 Oct 2016 18:04:25 +0800 Subject: [PATCH 090/149] spi_flash: implement partition API --- components/spi_flash/include/esp_partition.h | 156 ++++++++----- components/spi_flash/partition.c | 221 +++++++++++++++++++ 2 files changed, 326 insertions(+), 51 deletions(-) create mode 100644 components/spi_flash/partition.c diff --git a/components/spi_flash/include/esp_partition.h b/components/spi_flash/include/esp_partition.h index fd1223da5..6bdba149f 100644 --- a/components/spi_flash/include/esp_partition.h +++ b/components/spi_flash/include/esp_partition.h @@ -15,62 +15,73 @@ #ifndef __ESP_PARTITION_H__ #define __ESP_PARTITION_H__ +#include +#include #include "esp_err.h" #ifdef __cplusplus extern "C" { #endif -enum esp_partition_type_t { - PT_APP_MASK = 0x0000, - PT_APP_FACTORY = PT_APP_MASK | 0x00, - PT_APP_OTA_MIN = PT_APP_MASK | 0x10, - PT_APP_OTA_0 = PT_APP_OTA_MIN + 0, - PT_APP_OTA_1 = PT_APP_OTA_MIN + 1, - PT_APP_OTA_2 = PT_APP_OTA_MIN + 2, - PT_APP_OTA_3 = PT_APP_OTA_MIN + 3, - PT_APP_OTA_4 = PT_APP_OTA_MIN + 4, - PT_APP_OTA_5 = PT_APP_OTA_MIN + 5, - PT_APP_OTA_6 = PT_APP_OTA_MIN + 6, - PT_APP_OTA_7 = PT_APP_OTA_MIN + 7, - PT_APP_OTA_8 = PT_APP_OTA_MIN + 8, - PT_APP_OTA_9 = PT_APP_OTA_MIN + 9, - PT_APP_OTA_10 = PT_APP_OTA_MIN + 10, - PT_APP_OTA_11 = PT_APP_OTA_MIN + 11, - PT_APP_OTA_12 = PT_APP_OTA_MIN + 12, - PT_APP_OTA_13 = PT_APP_OTA_MIN + 13, - PT_APP_OTA_14 = PT_APP_OTA_MIN + 14, - PT_APP_OTA_15 = PT_APP_OTA_MIN + 15, - PT_APP_OTA_MAX = PT_APP_MASK | 0x1f, - PT_APP_TEST = PT_APP_MASK | 0x20, - PT_APP_ANY = PT_APP_MASK | 0xff, +typedef enum { + ESP_PARTITION_APP_MASK = 0x0000, + ESP_PARTITION_APP_FACTORY = ESP_PARTITION_APP_MASK | 0x00, + ESP_PARTITION_APP_OTA_MIN = ESP_PARTITION_APP_MASK | 0x10, + ESP_PARTITION_APP_OTA_0 = ESP_PARTITION_APP_OTA_MIN + 0, + ESP_PARTITION_APP_OTA_1 = ESP_PARTITION_APP_OTA_MIN + 1, + ESP_PARTITION_APP_OTA_2 = ESP_PARTITION_APP_OTA_MIN + 2, + ESP_PARTITION_APP_OTA_3 = ESP_PARTITION_APP_OTA_MIN + 3, + ESP_PARTITION_APP_OTA_4 = ESP_PARTITION_APP_OTA_MIN + 4, + ESP_PARTITION_APP_OTA_5 = ESP_PARTITION_APP_OTA_MIN + 5, + ESP_PARTITION_APP_OTA_6 = ESP_PARTITION_APP_OTA_MIN + 6, + ESP_PARTITION_APP_OTA_7 = ESP_PARTITION_APP_OTA_MIN + 7, + ESP_PARTITION_APP_OTA_8 = ESP_PARTITION_APP_OTA_MIN + 8, + ESP_PARTITION_APP_OTA_9 = ESP_PARTITION_APP_OTA_MIN + 9, + ESP_PARTITION_APP_OTA_10 = ESP_PARTITION_APP_OTA_MIN + 10, + ESP_PARTITION_APP_OTA_11 = ESP_PARTITION_APP_OTA_MIN + 11, + ESP_PARTITION_APP_OTA_12 = ESP_PARTITION_APP_OTA_MIN + 12, + ESP_PARTITION_APP_OTA_13 = ESP_PARTITION_APP_OTA_MIN + 13, + ESP_PARTITION_APP_OTA_14 = ESP_PARTITION_APP_OTA_MIN + 14, + ESP_PARTITION_APP_OTA_15 = ESP_PARTITION_APP_OTA_MIN + 15, + ESP_PARTITION_APP_OTA_MAX = ESP_PARTITION_APP_MASK | 0x1f, + ESP_PARTITION_APP_TEST = ESP_PARTITION_APP_MASK | 0x20, + ESP_PARTITION_APP_ANY = ESP_PARTITION_APP_MASK | 0xff, - PT_DATA_MASK = 0x0100, - PT_DATA_OTA = PT_DATA_MASK | 0x00, - PT_DATA_RF = PT_DATA_MASK | 0x01, - PT_DATA_WIFI = PT_DATA_MASK | 0x02, - PT_DATA_ANY = PT_DATA_MASK | 0xff, + ESP_PARTITION_DATA_MASK = 0x0100, + ESP_PARTITION_DATA_OTA = ESP_PARTITION_DATA_MASK | 0x00, + ESP_PARTITION_DATA_RF = ESP_PARTITION_DATA_MASK | 0x01, + ESP_PARTITION_DATA_WIFI = ESP_PARTITION_DATA_MASK | 0x02, + ESP_PARTITION_DATA_ANY = ESP_PARTITION_DATA_MASK | 0xff, - PT_FILESYSTEM_MASK = 0x0200, - PT_FILESYSTEM_ESPHTTPD = 0x0200, - PT_FILESYSTEM_FAT = 0x0201, - PT_FILESYSTEM_SPIFFS = 0x0202, - PT_FILESYSTEM_ANY = 0x20ff, + ESP_PARTITION_FILESYSTEM_MASK = 0x0200, + ESP_PARTITION_FILESYSTEM_ESPHTTPD = 0x0200, + ESP_PARTITION_FILESYSTEM_FAT = 0x0201, + ESP_PARTITION_FILESYSTEM_SPIFFS = 0x0202, + ESP_PARTITION_FILESYSTEM_ANY = 0x20ff, - PT_END = 0xffff -}; + ESP_PARTITION_END = 0xffff +} esp_partition_type_t; -#define PT_APP_OTA(i) ((esp_partition_type_t)(PT_APP_OTA_MIN + ((i) & 0xf))) +#define ESP_PARTITION_APP_OTA(i) ((esp_partition_type_t)(ESP_PARTITION_APP_OTA_MIN + ((i) & 0xf))) -typedef struct esp_partition_iterator_opaque_t* esp_partition_iterator_t; +typedef struct esp_partition_iterator_opaque_* esp_partition_iterator_t; + +typedef struct { + esp_partition_type_t type; + uint32_t address; + uint32_t size; + char label[17]; + bool encrypted; +} esp_partition_t; /** * @brief Find partition based on one or more parameters * * @param type Partition type, one of esp_partition_type_t values * To find all app partitions or all filesystem partitions, - * use PT_APP_ANY or PT_FILESYSTEM_ANY, respectively. + * use ESP_PARTITION_APP_ANY or ESP_PARTITION_FILESYSTEM_ANY, + * respectively. * @param label (optional) Partition label. Set this value if looking * for partition with a specific name. Pass NULL otherwise. * @@ -81,24 +92,47 @@ typedef struct esp_partition_iterator_opaque_t* esp_partition_iterator_t; */ esp_partition_iterator_t esp_partition_find(esp_partition_type_t type, const char* label); +/** + * @brief Find first partition based on one or more parameters + * + * @param type Partition type, one of esp_partition_type_t values + * To find all app partitions or all filesystem partitions, + * use ESP_PARTITION_APP_ANY or ESP_PARTITION_FILESYSTEM_ANY, + * respectively. + * @param label (optional) Partition label. Set this value if looking + * for partition with a specific name. Pass NULL otherwise. + * + * @return pointer to esp_partition_t structure, or NULL if no parition is found. + * This pointer is valid for the lifetime of the application. + */ +const esp_partition_t* esp_partition_find_first(esp_partition_type_t type, const char* label); + +/** + * @brief Get esp_partition_t structure for given partition + * + * @param iterator Iterator obtained using esp_partition_find. Must be non-NULL. + * + * @return pointer to esp_partition_t structure. This pointer is valid for the lifetime + * of the application. + */ +const esp_partition_t* esp_partition_get(esp_partition_iterator_t iterator); /** * @brief Move partition iterator to the next partition found * - * Any pointers obtained using esp_partition_label function for this iterator - * will be invalid after this call. + * Any copies of the iterator will be invalid after this call. * * @param iterator Iterator obtained using esp_partition_find. Must be non-NULL. * - * @return iterator pointing to the next partition found, or NULL if no more - * partitions were found. - * + * @return NULL if no partition was found, valid esp_partition_iterator_t otherwise. */ esp_partition_iterator_t esp_partition_next(esp_partition_iterator_t iterator); /** * @brief Get partition type * + * @note This is a helper function built around esp_partition_get. + * * @param iterator Iterator obtained using esp_partition_find. Must be non-NULL. * * @return esp_partition_type_t value for partition pointed to by the iterator. @@ -108,6 +142,8 @@ esp_partition_type_t esp_partition_type(esp_partition_iterator_t iterator); /** * @brief Get partition size * + * @note This is a helper function built around esp_partition_get. + * * @param iterator Iterator obtained using esp_partition_find. Must be non-NULL. * * @return partition size, in bytes @@ -117,6 +153,8 @@ uint32_t esp_partition_size(esp_partition_iterator_t iterator); /** * @brief Get partition address * + * @note This is a helper function built around esp_partition_get. + * * @param iterator Iterator obtained using esp_partition_find. Must be non-NULL. * * @return flash address of partition start @@ -126,11 +164,12 @@ uint32_t esp_partition_address(esp_partition_iterator_t iterator); /** * @brief Get partition label * + * @note This is a helper function built around esp_partition_get. + * * @param iterator Iterator obtained using esp_partition_find. Must be non-NULL. * * @return pointer to a zero-terminated string with partition label. - * The pointer is valid until the call to esp_partition_next or - * esp_partition_iterator_release for the given iterator. + * The pointer is valid for the lifetime of the application. */ const char* esp_partition_label(esp_partition_iterator_t iterator); @@ -145,8 +184,8 @@ const char* esp_partition_label(esp_partition_iterator_t iterator); * @param size Size of data to be read, in bytes. * * @return ESP_OK, if data was read successfully; - * ESP_INVALID_ARG, if iterator or src are NULL; - * ESP_INVALID_SIZE, if read would go out of bounds of the partition; + * ESP_ERR_INVALID_ARG, if iterator or src are NULL; + * ESP_ERR_INVALID_SIZE, if read would go out of bounds of the partition; * or one of error codes from lower-level flash driver. */ esp_err_t esp_partition_read(esp_partition_iterator_t iterator, @@ -166,8 +205,8 @@ esp_err_t esp_partition_read(esp_partition_iterator_t iterator, * esp_partition_erase_range call. * * @return ESP_OK, if data was written successfully; - * ESP_INVALID_ARG, if iterator or dst are NULL; - * ESP_INVALID_SIZE, if write would go out of bounds of the partition; + * ESP_ERR_INVALID_ARG, if iterator or dst are NULL; + * ESP_ERR_INVALID_SIZE, if write would go out of bounds of the partition; * or one of error codes from lower-level flash driver. */ esp_err_t esp_partition_write(esp_partition_iterator_t iterator, @@ -183,13 +222,28 @@ esp_err_t esp_partition_write(esp_partition_iterator_t iterator, * Must be divisible by 4 kilobytes. * * @return ESP_OK, if the range was erased successfully; - * ESP_INVALID_ARG, if iterator or dst are NULL; - * ESP_INVALID_SIZE, if erase would go out of bounds of the partition; + * ESP_ERR_INVALID_ARG, if iterator or dst are NULL; + * ESP_ERR_INVALID_SIZE, if erase would go out of bounds of the partition; * or one of error codes from lower-level flash driver. */ esp_err_t esp_partition_erase_range(esp_partition_iterator_t iterator, uint32_t start_addr, uint32_t size); +/** + * @brief Configure MMU to map partition into data memory + * + * @param iterator Iterator obtained using esp_partition_find. Must be non-NULL. + * + * @param offset Offset from the beginning of partition where mapping should start. + * Must be aligned to 64k. + * + * @param size Size of the area to be mapped. + * + * @return pointer to mapped memory, if successful + * NULL, if memory can not be mapped for any reason + */ +void* esp_partition_mmap(esp_partition_iterator_t iterator, uint32_t offset, uint32_t size); + /** * @brief Release partition iterator diff --git a/components/spi_flash/partition.c b/components/spi_flash/partition.c new file mode 100644 index 000000000..381a1624b --- /dev/null +++ b/components/spi_flash/partition.c @@ -0,0 +1,221 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include + +#include "esp_attr.h" +#include "esp_flash_data_types.h" +#include "esp_spi_flash.h" +#include "esp_partition.h" +#include "esp_log.h" + + +#ifndef NDEBUG +// Enable built-in checks in queue.h in debug builds +#define INVARIANTS +#endif +#include "rom/queue.h" + + +typedef struct partition_list_item_ { + esp_partition_t info; + SLIST_ENTRY(partition_list_item_) next; +} partition_list_item_t; + +typedef struct esp_partition_iterator_opaque_ { + esp_partition_type_t type; // requested type + const char* label; // requested label (can be NULL) + partition_list_item_t* next_item; // next item to iterate to + esp_partition_t* info; // pointer to info (it is redundant, but makes code more readable) +} esp_partition_iterator_opaque_t; + + +static esp_partition_iterator_opaque_t* iterator_create(esp_partition_type_t type, const char* label); +static esp_err_t load_partitions(); + + +static SLIST_HEAD(partition_list_head_, partition_list_item_) s_partition_list = + SLIST_HEAD_INITIALIZER(s_partition_list); +static _lock_t s_partition_list_lock; + + +static uint32_t get_major_type(esp_partition_type_t type) +{ + return (type >> 8) & 0xff; +} + +static uint32_t get_minor_type(esp_partition_type_t type) +{ + return type & 0xff; +} + +esp_partition_iterator_t esp_partition_find(esp_partition_type_t type, + const char* label) +{ + if (SLIST_EMPTY(&s_partition_list)) { + // only lock if list is empty (and check again after acquiring lock) + _lock_acquire(&s_partition_list_lock); + esp_err_t err = ESP_OK; + if (SLIST_EMPTY(&s_partition_list)) { + err = load_partitions(); + } + _lock_release(&s_partition_list_lock); + if (err != ESP_OK) { + return NULL; + } + } + // create an iterator pointing to the start of the list + // (next item will be the first one) + esp_partition_iterator_t it = iterator_create(type, label); + // advance iterator to the next item which matches constraints + it = esp_partition_next(it); + // if nothing found, it == NULL and iterator has been released + return it; +} + +esp_partition_iterator_t esp_partition_next(esp_partition_iterator_t it) +{ + assert(it); + // iterator reached the end of linked list? + if (it->next_item == NULL) { + return NULL; + } + uint32_t requested_major_type = get_major_type(it->type); + uint32_t requested_minor_type = get_minor_type(it->type); + _lock_acquire(&s_partition_list_lock); + for (; it->next_item != NULL; it->next_item = SLIST_NEXT(it->next_item, next)) { + esp_partition_t* p = &it->next_item->info; + uint32_t it_major_type = get_major_type(p->type); + uint32_t it_minor_type = get_minor_type(p->type); + if (requested_major_type != it_major_type) { + continue; + } + if (requested_minor_type != 0xff && requested_minor_type != it_minor_type) { + continue; + } + if (it->label != NULL && strcmp(it->label, p->label) != 0) { + continue; + } + // all constraints match, bail out + break; + } + _lock_release(&s_partition_list_lock); + if (it->next_item == NULL) { + esp_partition_iterator_release(it); + return NULL; + } + it->info = &it->next_item->info; + it->next_item = SLIST_NEXT(it->next_item, next); + return it; +} + +const esp_partition_t* esp_partition_find_first(esp_partition_type_t type, const char* label) +{ + esp_partition_iterator_t it = esp_partition_find(type, label); + if (it == NULL) { + return NULL; + } + const esp_partition_t* res = esp_partition_get(it); + esp_partition_iterator_release(it); + return res; +} + +void esp_partition_iterator_release(esp_partition_iterator_t iterator) +{ + // iterator == NULL is okay + free(iterator); +} + +const esp_partition_t* esp_partition_get(esp_partition_iterator_t iterator) +{ + assert(iterator != NULL); + return iterator->info; +} + +esp_partition_type_t esp_partition_type(esp_partition_iterator_t iterator) +{ + return esp_partition_get(iterator)->type; +} + +uint32_t esp_partition_size(esp_partition_iterator_t iterator) +{ + return esp_partition_get(iterator)->size; +} + +uint32_t esp_partition_address(esp_partition_iterator_t iterator) +{ + return esp_partition_get(iterator)->address; +} + +const char* esp_partition_label(esp_partition_iterator_t iterator) +{ + return esp_partition_get(iterator)->label; +} + +static esp_partition_iterator_opaque_t* iterator_create(esp_partition_type_t type, const char* label) +{ + esp_partition_iterator_opaque_t* it = + (esp_partition_iterator_opaque_t*) malloc(sizeof(esp_partition_iterator_opaque_t)); + it->type = type; + it->label = label; + it->next_item = SLIST_FIRST(&s_partition_list); + it->info = NULL; + return it; +} + +// Create linked list of partition_list_item_t structures. +// This function is called only once, with s_partition_list_lock taken. +static esp_err_t load_partitions() +{ + const uint32_t* ptr; + spi_flash_mmap_handle_t handle; + // map 64kB block where partition table is located + esp_err_t err = spi_flash_mmap(ESP_PARTITION_TABLE_ADDR & 0xffff0000, + SPI_FLASH_SEC_SIZE, SPI_FLASH_MMAP_DATA, (const void**) &ptr, &handle); + if (err != ESP_OK) { + return err; + } + // calculate partition address within mmap-ed region + const esp_partition_info_t* it = (const esp_partition_info_t*) + (ptr + (ESP_PARTITION_TABLE_ADDR & 0xffff) / sizeof(*ptr)); + const esp_partition_info_t* end = it + SPI_FLASH_SEC_SIZE / sizeof(*it); + // tail of the linked list of partitions + partition_list_item_t* last = NULL; + for (; it != end; ++it) { + if (it->magic != ESP_PARTITION_MAGIC) { + break; + } + // allocate new linked list item and populate it with data from partition table + partition_list_item_t* item = (partition_list_item_t*) malloc(sizeof(partition_list_item_t)); + item->info.address = it->pos.offset; + item->info.size = it->pos.size; + item->info.type = (it->type << 8) | it->subtype; + item->info.encrypted = false; + // it->label may not be zero-terminated + strncpy(item->info.label, (const char*) it->label, sizeof(it->label)); + item->info.label[sizeof(it->label)] = 0; + // add it to the list + if (last == NULL) { + SLIST_INSERT_HEAD(&s_partition_list, item, next); + } else { + SLIST_INSERT_AFTER(last, item, next); + } + } + spi_flash_munmap(handle); + return ESP_OK; +} From 2c5340d47e2a482c2fc58ae02178332775c3a699 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Fri, 21 Oct 2016 17:28:50 +0800 Subject: [PATCH 091/149] spi_flash: change argument types spi_flash_read and spi_flash_write currently have a limitation that source and destination must be word-aligned. This can be fixed by adding code paths for various unaligned scenarios, but function signatures also need to be adjusted. As a first step (since we are pre-1.0 and can still change function signatures) alignment checks are added, and pointer types are relaxed to uint8_t. Later we will add handling of unaligned operations. This change also introduces spi_flash_erase_range and spi_flash_get_chip_size functions. We probably need something like spi_flash_chip_size_detect which will detect actual chip size. This is to allow single application binary to be used on a variety of boards and modules. --- components/nvs_flash/src/nvs_page.cpp | 27 ++++--- .../nvs_flash/test/spi_flash_emulation.cpp | 10 +-- .../nvs_flash/test/spi_flash_emulation.h | 6 +- .../test/test_spi_flash_emulation.cpp | 20 ++--- components/spi_flash/flash_ops.c | 81 +++++++++++++++++-- components/spi_flash/include/esp_spi_flash.h | 50 +++++++++--- 6 files changed, 146 insertions(+), 48 deletions(-) diff --git a/components/nvs_flash/src/nvs_page.cpp b/components/nvs_flash/src/nvs_page.cpp index f4fc5430c..9ba478e21 100644 --- a/components/nvs_flash/src/nvs_page.cpp +++ b/components/nvs_flash/src/nvs_page.cpp @@ -37,7 +37,7 @@ esp_err_t Page::load(uint32_t sectorNumber) mErasedEntryCount = 0; Header header; - auto rc = spi_flash_read(mBaseAddress, reinterpret_cast(&header), sizeof(header)); + auto rc = spi_flash_read(mBaseAddress, reinterpret_cast(&header), sizeof(header)); if (rc != ESP_OK) { mState = PageState::INVALID; return rc; @@ -48,7 +48,7 @@ esp_err_t Page::load(uint32_t sectorNumber) // reading the whole page takes ~40 times less than erasing it uint32_t line[8]; for (uint32_t i = 0; i < SPI_FLASH_SEC_SIZE; i += sizeof(line)) { - rc = spi_flash_read(mBaseAddress + i, line, sizeof(line)); + rc = spi_flash_read(mBaseAddress + i, reinterpret_cast(line), sizeof(line)); if (rc != ESP_OK) { mState = PageState::INVALID; return rc; @@ -86,7 +86,7 @@ esp_err_t Page::load(uint32_t sectorNumber) esp_err_t Page::writeEntry(const Item& item) { - auto rc = spi_flash_write(getEntryAddress(mNextFreeEntry), reinterpret_cast(&item), sizeof(item)); + auto rc = spi_flash_write(getEntryAddress(mNextFreeEntry), reinterpret_cast(&item), sizeof(item)); if (rc != ESP_OK) { mState = PageState::INVALID; return rc; @@ -114,7 +114,7 @@ esp_err_t Page::writeEntryData(const uint8_t* data, size_t size) assert(mFirstUsedEntry != INVALID_ENTRY); const uint16_t count = size / ENTRY_SIZE; - auto rc = spi_flash_write(getEntryAddress(mNextFreeEntry), reinterpret_cast(data), static_cast(size)); + auto rc = spi_flash_write(getEntryAddress(mNextFreeEntry), data, size); if (rc != ESP_OK) { mState = PageState::INVALID; return rc; @@ -396,8 +396,8 @@ esp_err_t Page::mLoadEntryTable() if (mState == PageState::ACTIVE || mState == PageState::FULL || mState == PageState::FREEING) { - auto rc = spi_flash_read(mBaseAddress + ENTRY_TABLE_OFFSET, mEntryTable.data(), - static_cast(mEntryTable.byteSize())); + auto rc = spi_flash_read(mBaseAddress + ENTRY_TABLE_OFFSET, reinterpret_cast(mEntryTable.data()), + mEntryTable.byteSize()); if (rc != ESP_OK) { mState = PageState::INVALID; return rc; @@ -435,7 +435,7 @@ esp_err_t Page::mLoadEntryTable() while (mNextFreeEntry < ENTRY_COUNT) { uint32_t entryAddress = getEntryAddress(mNextFreeEntry); uint32_t header; - auto rc = spi_flash_read(entryAddress, &header, sizeof(header)); + auto rc = spi_flash_read(entryAddress, reinterpret_cast(&header), sizeof(header)); if (rc != ESP_OK) { mState = PageState::INVALID; return rc; @@ -559,7 +559,7 @@ esp_err_t Page::initialize() header.mSeqNumber = mSeqNumber; header.mCrc32 = header.calculateCrc32(); - auto rc = spi_flash_write(mBaseAddress, reinterpret_cast(&header), sizeof(header)); + auto rc = spi_flash_write(mBaseAddress, reinterpret_cast(&header), sizeof(header)); if (rc != ESP_OK) { mState = PageState::INVALID; return rc; @@ -577,7 +577,8 @@ esp_err_t Page::alterEntryState(size_t index, EntryState state) mEntryTable.set(index, state); size_t wordToWrite = mEntryTable.getWordIndex(index); uint32_t word = mEntryTable.data()[wordToWrite]; - auto rc = spi_flash_write(mBaseAddress + ENTRY_TABLE_OFFSET + static_cast(wordToWrite) * 4, &word, 4); + auto rc = spi_flash_write(mBaseAddress + ENTRY_TABLE_OFFSET + static_cast(wordToWrite) * 4, + reinterpret_cast(&word), sizeof(word)); if (rc != ESP_OK) { mState = PageState::INVALID; return rc; @@ -600,7 +601,8 @@ esp_err_t Page::alterEntryRangeState(size_t begin, size_t end, EntryState state) } if (nextWordIndex != wordIndex) { uint32_t word = mEntryTable.data()[wordIndex]; - auto rc = spi_flash_write(mBaseAddress + ENTRY_TABLE_OFFSET + static_cast(wordIndex) * 4, &word, 4); + auto rc = spi_flash_write(mBaseAddress + ENTRY_TABLE_OFFSET + static_cast(wordIndex) * 4, + reinterpret_cast(&word), 4); if (rc != ESP_OK) { return rc; } @@ -612,7 +614,8 @@ esp_err_t Page::alterEntryRangeState(size_t begin, size_t end, EntryState state) esp_err_t Page::alterPageState(PageState state) { - auto rc = spi_flash_write(mBaseAddress, reinterpret_cast(&state), sizeof(state)); + uint32_t state_val = static_cast(state); + auto rc = spi_flash_write(mBaseAddress, reinterpret_cast(&state_val), sizeof(state)); if (rc != ESP_OK) { mState = PageState::INVALID; return rc; @@ -623,7 +626,7 @@ esp_err_t Page::alterPageState(PageState state) esp_err_t Page::readEntry(size_t index, Item& dst) const { - auto rc = spi_flash_read(getEntryAddress(index), reinterpret_cast(&dst), sizeof(dst)); + auto rc = spi_flash_read(getEntryAddress(index), reinterpret_cast(&dst), sizeof(dst)); if (rc != ESP_OK) { return rc; } diff --git a/components/nvs_flash/test/spi_flash_emulation.cpp b/components/nvs_flash/test/spi_flash_emulation.cpp index 5185bd34c..bd1482268 100644 --- a/components/nvs_flash/test/spi_flash_emulation.cpp +++ b/components/nvs_flash/test/spi_flash_emulation.cpp @@ -22,7 +22,7 @@ void spi_flash_emulator_set(SpiFlashEmulator* e) s_emulator = e; } -esp_err_t spi_flash_erase_sector(uint16_t sec) +esp_err_t spi_flash_erase_sector(size_t sec) { if (!s_emulator) { return ESP_ERR_FLASH_OP_TIMEOUT; @@ -35,26 +35,26 @@ esp_err_t spi_flash_erase_sector(uint16_t sec) return ESP_OK; } -esp_err_t spi_flash_write(uint32_t des_addr, const uint32_t *src_addr, uint32_t size) +esp_err_t spi_flash_write(size_t des_addr, const uint8_t *src_addr, size_t size) { if (!s_emulator) { return ESP_ERR_FLASH_OP_TIMEOUT; } - if (!s_emulator->write(des_addr, src_addr, size)) { + if (!s_emulator->write(des_addr, reinterpret_cast(src_addr), size)) { return ESP_ERR_FLASH_OP_FAIL; } return ESP_OK; } -esp_err_t spi_flash_read(uint32_t src_addr, uint32_t *des_addr, uint32_t size) +esp_err_t spi_flash_read(size_t src_addr, uint8_t *des_addr, size_t size) { if (!s_emulator) { return ESP_ERR_FLASH_OP_TIMEOUT; } - if (!s_emulator->read(des_addr, src_addr, size)) { + if (!s_emulator->read(reinterpret_cast(des_addr), src_addr, size)) { return ESP_ERR_FLASH_OP_FAIL; } diff --git a/components/nvs_flash/test/spi_flash_emulation.h b/components/nvs_flash/test/spi_flash_emulation.h index d5a242b24..ba50c4f9e 100644 --- a/components/nvs_flash/test/spi_flash_emulation.h +++ b/components/nvs_flash/test/spi_flash_emulation.h @@ -44,7 +44,7 @@ public: spi_flash_emulator_set(nullptr); } - bool read(uint32_t* dest, uint32_t srcAddr, size_t size) const + bool read(uint32_t* dest, size_t srcAddr, size_t size) const { if (srcAddr % 4 != 0 || size % 4 != 0 || @@ -60,7 +60,7 @@ public: return true; } - bool write(uint32_t dstAddr, const uint32_t* src, size_t size) + bool write(size_t dstAddr, const uint32_t* src, size_t size) { uint32_t sectorNumber = dstAddr/SPI_FLASH_SEC_SIZE; if (sectorNumber < mLowerSectorBound || sectorNumber >= mUpperSectorBound) { @@ -96,7 +96,7 @@ public: return true; } - bool erase(uint32_t sectorNumber) + bool erase(size_t sectorNumber) { size_t offset = sectorNumber * SPI_FLASH_SEC_SIZE / 4; if (offset > mData.size()) { diff --git a/components/nvs_flash/test/test_spi_flash_emulation.cpp b/components/nvs_flash/test/test_spi_flash_emulation.cpp index ea233da61..329e721ce 100644 --- a/components/nvs_flash/test/test_spi_flash_emulation.cpp +++ b/components/nvs_flash/test/test_spi_flash_emulation.cpp @@ -30,7 +30,7 @@ TEST_CASE("flash starts with all bytes == 0xff", "[spi_flash_emu]") uint8_t sector[SPI_FLASH_SEC_SIZE]; for (int i = 0; i < 4; ++i) { - CHECK(spi_flash_read(0, reinterpret_cast(sector), sizeof(sector)) == ESP_OK); + CHECK(spi_flash_read(0, sector, sizeof(sector)) == ESP_OK); for (auto v: sector) { CHECK(v == 0xff); } @@ -42,9 +42,9 @@ TEST_CASE("invalid writes are checked", "[spi_flash_emu]") SpiFlashEmulator emu(1); uint32_t val = 0; - CHECK(spi_flash_write(0, &val, 4) == ESP_OK); + CHECK(spi_flash_write(0, reinterpret_cast(&val), 4) == ESP_OK); val = 1; - CHECK(spi_flash_write(0, &val, 4) == ESP_ERR_FLASH_OP_FAIL); + CHECK(spi_flash_write(0, reinterpret_cast(&val), 4) == ESP_ERR_FLASH_OP_FAIL); } @@ -53,11 +53,11 @@ TEST_CASE("out of bounds writes fail", "[spi_flash_emu]") SpiFlashEmulator emu(4); uint32_t vals[8]; std::fill_n(vals, 8, 0); - CHECK(spi_flash_write(0, vals, sizeof(vals)) == ESP_OK); + CHECK(spi_flash_write(0, reinterpret_cast(vals), sizeof(vals)) == ESP_OK); - CHECK(spi_flash_write(4*4096 - sizeof(vals), vals, sizeof(vals)) == ESP_OK); + CHECK(spi_flash_write(4*4096 - sizeof(vals), reinterpret_cast(vals), sizeof(vals)) == ESP_OK); - CHECK(spi_flash_write(4*4096 - sizeof(vals) + 4, vals, sizeof(vals)) == ESP_ERR_FLASH_OP_FAIL); + CHECK(spi_flash_write(4*4096 - sizeof(vals) + 4, reinterpret_cast(vals), sizeof(vals)) == ESP_ERR_FLASH_OP_FAIL); } @@ -65,9 +65,9 @@ TEST_CASE("after erase the sector is set to 0xff", "[spi_flash_emu]") { SpiFlashEmulator emu(4); uint32_t val1 = 0xab00cd12; - CHECK(spi_flash_write(0, &val1, sizeof(val1)) == ESP_OK); + CHECK(spi_flash_write(0, reinterpret_cast(&val1), sizeof(val1)) == ESP_OK); uint32_t val2 = 0x5678efab; - CHECK(spi_flash_write(4096 - 4, &val2, sizeof(val2)) == ESP_OK); + CHECK(spi_flash_write(4096 - 4, reinterpret_cast(&val2), sizeof(val2)) == ESP_OK); CHECK(emu.words()[0] == val1); CHECK(range_empty_n(emu.words() + 1, 4096 / 4 - 2)); @@ -83,7 +83,7 @@ TEST_CASE("after erase the sector is set to 0xff", "[spi_flash_emu]") TEST_CASE("read/write/erase operation times are calculated correctly", "[spi_flash_emu]") { SpiFlashEmulator emu(1); - uint32_t data[128]; + uint8_t data[512]; spi_flash_read(0, data, 4); CHECK(emu.getTotalTime() == 7); CHECK(emu.getReadOps() == 1); @@ -141,7 +141,7 @@ TEST_CASE("read/write/erase operation times are calculated correctly", "[spi_fla CHECK(emu.getTotalTime() == 37142); } -TEST_CASE("data is randomized predicatbly", "[spi_flash_emu]") +TEST_CASE("data is randomized predictably", "[spi_flash_emu]") { SpiFlashEmulator emu1(3); emu1.randomize(0x12345678); diff --git a/components/spi_flash/flash_ops.c b/components/spi_flash/flash_ops.c index 99e8ef77f..512f6d20d 100644 --- a/components/spi_flash/flash_ops.c +++ b/components/spi_flash/flash_ops.c @@ -64,6 +64,11 @@ void spi_flash_init() #endif } +size_t spi_flash_get_chip_size() +{ + return g_rom_flashchip.chip_size; +} + SpiFlashOpResult IRAM_ATTR spi_flash_unlock() { static bool unlocked = false; @@ -77,28 +82,74 @@ SpiFlashOpResult IRAM_ATTR spi_flash_unlock() return SPI_FLASH_RESULT_OK; } -esp_err_t IRAM_ATTR spi_flash_erase_sector(uint16_t sec) +esp_err_t IRAM_ATTR spi_flash_erase_sector(size_t sec) { + return spi_flash_erase_range(sec * SPI_FLASH_SEC_SIZE, SPI_FLASH_SEC_SIZE); +} + +esp_err_t IRAM_ATTR spi_flash_erase_range(uint32_t start_addr, uint32_t size) +{ + if (start_addr % SPI_FLASH_SEC_SIZE != 0) { + return ESP_ERR_INVALID_ARG; + } + if (size % SPI_FLASH_SEC_SIZE != 0) { + return ESP_ERR_INVALID_SIZE; + } + if (size + start_addr > spi_flash_get_chip_size()) { + return ESP_ERR_INVALID_SIZE; + } + size_t start = start_addr / SPI_FLASH_SEC_SIZE; + size_t end = start + size / SPI_FLASH_SEC_SIZE; + const size_t sectors_per_block = 16; COUNTER_START(); spi_flash_disable_interrupts_caches_and_other_cpu(); SpiFlashOpResult rc; rc = spi_flash_unlock(); if (rc == SPI_FLASH_RESULT_OK) { - rc = SPIEraseSector(sec); + for (size_t sector = start; sector != end && rc == SPI_FLASH_RESULT_OK; ) { + if (sector % sectors_per_block == 0 && end - sector > sectors_per_block) { + rc = SPIEraseBlock(sector / sectors_per_block); + sector += sectors_per_block; + COUNTER_ADD_BYTES(erase, sectors_per_block * SPI_FLASH_SEC_SIZE); + } + else { + rc = SPIEraseSector(sector); + ++sector; + COUNTER_ADD_BYTES(erase, SPI_FLASH_SEC_SIZE); + } + } } spi_flash_enable_interrupts_caches_and_other_cpu(); COUNTER_STOP(erase); return spi_flash_translate_rc(rc); } -esp_err_t IRAM_ATTR spi_flash_write(uint32_t dest_addr, const uint32_t *src, uint32_t size) +esp_err_t IRAM_ATTR spi_flash_write(size_t dest_addr, const uint8_t *src, size_t size) { + // TODO: replace this check with code which deals with unaligned sources + if (((ptrdiff_t) src) % 4 != 0) { + return ESP_ERR_INVALID_ARG; + } + // Destination alignment is also checked in ROM code, but we can give + // better error code here + // TODO: add handling of unaligned destinations + if (dest_addr % 4 != 0) { + return ESP_ERR_INVALID_ARG; + } + if (size % 4 != 0) { + return ESP_ERR_INVALID_SIZE; + } + // Out of bound writes are checked in ROM code, but we can give better + // error code here + if (dest_addr + size > g_rom_flashchip.chip_size) { + return ESP_ERR_INVALID_SIZE; + } COUNTER_START(); spi_flash_disable_interrupts_caches_and_other_cpu(); SpiFlashOpResult rc; rc = spi_flash_unlock(); if (rc == SPI_FLASH_RESULT_OK) { - rc = SPIWrite(dest_addr, src, (int32_t) size); + rc = SPIWrite((uint32_t) dest_addr, (const uint32_t*) src, (int32_t) size); COUNTER_ADD_BYTES(write, size); } spi_flash_enable_interrupts_caches_and_other_cpu(); @@ -106,11 +157,29 @@ esp_err_t IRAM_ATTR spi_flash_write(uint32_t dest_addr, const uint32_t *src, uin return spi_flash_translate_rc(rc); } -esp_err_t IRAM_ATTR spi_flash_read(uint32_t src_addr, uint32_t *dest, uint32_t size) +esp_err_t IRAM_ATTR spi_flash_read(size_t src_addr, uint8_t *dest, size_t size) { + // TODO: replace this check with code which deals with unaligned destinations + if (((ptrdiff_t) dest) % 4 != 0) { + return ESP_ERR_INVALID_ARG; + } + // Source alignment is also checked in ROM code, but we can give + // better error code here + // TODO: add handling of unaligned destinations + if (src_addr % 4 != 0) { + return ESP_ERR_INVALID_ARG; + } + if (size % 4 != 0) { + return ESP_ERR_INVALID_SIZE; + } + // Out of bound reads are checked in ROM code, but we can give better + // error code here + if (src_addr + size > g_rom_flashchip.chip_size) { + return ESP_ERR_INVALID_SIZE; + } COUNTER_START(); spi_flash_disable_interrupts_caches_and_other_cpu(); - SpiFlashOpResult rc = SPIRead(src_addr, dest, (int32_t) size); + SpiFlashOpResult rc = SPIRead((uint32_t) src_addr, (uint32_t*) dest, (int32_t) size); COUNTER_ADD_BYTES(read, size); spi_flash_enable_interrupts_caches_and_other_cpu(); COUNTER_STOP(read); diff --git a/components/spi_flash/include/esp_spi_flash.h b/components/spi_flash/include/esp_spi_flash.h index 597e41857..ab66a4204 100644 --- a/components/spi_flash/include/esp_spi_flash.h +++ b/components/spi_flash/include/esp_spi_flash.h @@ -38,37 +38,63 @@ extern "C" { */ void spi_flash_init(); +/** + * @brief Get flash chip size, as set in binary image header + * + * @note This value does not necessarily match real flash size. + * + * @return size of flash chip, in bytes + */ +size_t spi_flash_get_chip_size(); + /** * @brief Erase the Flash sector. * - * @param uint16 sec : Sector number, the count starts at sector 0, 4KB per sector. + * @param sector Sector number, the count starts at sector 0, 4KB per sector. * * @return esp_err_t */ -esp_err_t spi_flash_erase_sector(uint16_t sec); +esp_err_t spi_flash_erase_sector(size_t sector); + +/** + * @brief Erase a range of flash sectors + * + * @param uint32_t start_address : Address where erase operation has to start. + * Must be 4kB-aligned + * @param uint32_t size : Size of erased range, in bytes. Must be divisible by 4kB. + * + * @return esp_err_t + */ +esp_err_t spi_flash_erase_range(size_t start_addr, size_t size); + /** * @brief Write data to Flash. * - * @param uint32 des_addr : destination address in Flash. - * @param uint32 *src_addr : source address of the data. - * @param uint32 size : length of data + * @note Both des_addr and src_addr have to be 4-byte aligned. + * This is a temporary limitation which will be removed. + * + * @param des_addr destination address in Flash + * @param src_addr source address of the data + * @param size length of data, in bytes * * @return esp_err_t */ -esp_err_t spi_flash_write(uint32_t des_addr, const uint32_t *src_addr, uint32_t size); +esp_err_t spi_flash_write(size_t des_addr, const uint8_t *src_addr, size_t size); /** * @brief Read data from Flash. * - * @param uint32 src_addr : source address of the data in Flash. - * @param uint32 *des_addr : destination address. - * @param uint32 size : length of data + * @note Both des_addr and src_addr have to be 4-byte aligned. + * This is a temporary limitation which will be removed. + * + * @param src_addr source address of the data in Flash. + * @param des_addr destination address + * @param size length of data * * @return esp_err_t */ -esp_err_t spi_flash_read(uint32_t src_addr, uint32_t *des_addr, uint32_t size); - +esp_err_t spi_flash_read(size_t src_addr, uint8_t *des_addr, size_t size); /** * @brief Enumeration which specifies memory space requested in an mmap call @@ -135,7 +161,7 @@ void spi_flash_mmap_dump(); typedef struct { uint32_t count; // number of times operation was executed uint32_t time; // total time taken, in microseconds - uint32_t bytes; // total number of bytes, for read and write operations + uint32_t bytes; // total number of bytes } spi_flash_counter_t; typedef struct { From b6693225c188ab627a31bbe8e881bfa183f21c22 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Fri, 21 Oct 2016 18:44:57 +0800 Subject: [PATCH 092/149] spi_flash: implement partition API, drop trivial wrappers This implements esp_partition_read, esp_partition_write, esp_partition_erase_range, esp_partition_mmap. Also removed getters which didn't add much sugar after all. --- components/spi_flash/include/esp_partition.h | 213 +++++++++---------- components/spi_flash/include/esp_spi_flash.h | 1 + components/spi_flash/partition.c | 121 ++++++++--- 3 files changed, 186 insertions(+), 149 deletions(-) diff --git a/components/spi_flash/include/esp_partition.h b/components/spi_flash/include/esp_partition.h index 6bdba149f..89e523f9a 100644 --- a/components/spi_flash/include/esp_partition.h +++ b/components/spi_flash/include/esp_partition.h @@ -17,7 +17,9 @@ #include #include +#include #include "esp_err.h" +#include "esp_spi_flash.h" #ifdef __cplusplus extern "C" { @@ -128,123 +130,6 @@ const esp_partition_t* esp_partition_get(esp_partition_iterator_t iterator); */ esp_partition_iterator_t esp_partition_next(esp_partition_iterator_t iterator); -/** - * @brief Get partition type - * - * @note This is a helper function built around esp_partition_get. - * - * @param iterator Iterator obtained using esp_partition_find. Must be non-NULL. - * - * @return esp_partition_type_t value for partition pointed to by the iterator. - */ -esp_partition_type_t esp_partition_type(esp_partition_iterator_t iterator); - -/** - * @brief Get partition size - * - * @note This is a helper function built around esp_partition_get. - * - * @param iterator Iterator obtained using esp_partition_find. Must be non-NULL. - * - * @return partition size, in bytes - */ -uint32_t esp_partition_size(esp_partition_iterator_t iterator); - -/** - * @brief Get partition address - * - * @note This is a helper function built around esp_partition_get. - * - * @param iterator Iterator obtained using esp_partition_find. Must be non-NULL. - * - * @return flash address of partition start - */ -uint32_t esp_partition_address(esp_partition_iterator_t iterator); - -/** - * @brief Get partition label - * - * @note This is a helper function built around esp_partition_get. - * - * @param iterator Iterator obtained using esp_partition_find. Must be non-NULL. - * - * @return pointer to a zero-terminated string with partition label. - * The pointer is valid for the lifetime of the application. - */ -const char* esp_partition_label(esp_partition_iterator_t iterator); - -/** - * @brief Read data from the partition - * - * @param iterator Iterator obtained using esp_partition_find. Must be non-NULL. - * @param src_offset Address of the data to be read, relative to the - * beginning of the partition. - * @param dst Pointer to the buffer where data should be stored. - * Must be non-NULL and at least 'size' bytes long. - * @param size Size of data to be read, in bytes. - * - * @return ESP_OK, if data was read successfully; - * ESP_ERR_INVALID_ARG, if iterator or src are NULL; - * ESP_ERR_INVALID_SIZE, if read would go out of bounds of the partition; - * or one of error codes from lower-level flash driver. - */ -esp_err_t esp_partition_read(esp_partition_iterator_t iterator, - uint32_t src_offset, uint8_t* dst, uint32_t size); - -/** - * @brief Write data to the partition - * - * @param iterator Iterator obtained using esp_partition_find. Must be non-NULL. - * @param src Pointer to the source buffer. Must be non-NULL and - * at least 'size' bytes long. - * @param dst_offset Address where the data should be written, relative to the - * beginning of the partition. - * @param size Size of data to be written, in bytes. - * - * @note Prior to writing to flash memory, make sure it has been erased with - * esp_partition_erase_range call. - * - * @return ESP_OK, if data was written successfully; - * ESP_ERR_INVALID_ARG, if iterator or dst are NULL; - * ESP_ERR_INVALID_SIZE, if write would go out of bounds of the partition; - * or one of error codes from lower-level flash driver. - */ -esp_err_t esp_partition_write(esp_partition_iterator_t iterator, - const uint8_t* src, uint32_t dst_offset, uint32_t size); - -/** - * @brief Erase part of the partition - * - * @param iterator Iterator obtained using esp_partition_find. Must be non-NULL. - * @param start_addr Address where erase operation should start. Must be aligned - * to 4 kilobytes. - * @param size Size of the range which should be erased, in bytes. - * Must be divisible by 4 kilobytes. - * - * @return ESP_OK, if the range was erased successfully; - * ESP_ERR_INVALID_ARG, if iterator or dst are NULL; - * ESP_ERR_INVALID_SIZE, if erase would go out of bounds of the partition; - * or one of error codes from lower-level flash driver. - */ -esp_err_t esp_partition_erase_range(esp_partition_iterator_t iterator, - uint32_t start_addr, uint32_t size); - -/** - * @brief Configure MMU to map partition into data memory - * - * @param iterator Iterator obtained using esp_partition_find. Must be non-NULL. - * - * @param offset Offset from the beginning of partition where mapping should start. - * Must be aligned to 64k. - * - * @param size Size of the area to be mapped. - * - * @return pointer to mapped memory, if successful - * NULL, if memory can not be mapped for any reason - */ -void* esp_partition_mmap(esp_partition_iterator_t iterator, uint32_t offset, uint32_t size); - - /** * @brief Release partition iterator * @@ -256,6 +141,100 @@ void* esp_partition_mmap(esp_partition_iterator_t iterator, uint32_t offset, uin */ void esp_partition_iterator_release(esp_partition_iterator_t iterator); +/** + * @brief Read data from the partition + * + * @param partition Pointer to partition structure obtained using + * esp_partition_find_first or esp_partition_get. + * Must be non-NULL. + * @param dst Pointer to the buffer where data should be stored. + * Pointer must be non-NULL and buffer must be at least 'size' bytes long. + * @param src_offset Address of the data to be read, relative to the + * beginning of the partition. + * @param size Size of data to be read, in bytes. + * + * @return ESP_OK, if data was read successfully; + * ESP_ERR_INVALID_ARG, if iterator or src are NULL; + * ESP_ERR_INVALID_SIZE, if read would go out of bounds of the partition; + * or one of error codes from lower-level flash driver. + */ +esp_err_t esp_partition_read(const esp_partition_t* partition, + size_t src_offset, uint8_t* dst, size_t size); + +/** + * @brief Write data to the partition + * + * Before writing data to flash, corresponding region of flash needs to be erased. + * This can be done using esp_partition_erase_range function. + * + * @param partition Pointer to partition structure obtained using + * esp_partition_find_first or esp_partition_get. + * Must be non-NULL. + * @param dst_offset Address where the data should be written, relative to the + * beginning of the partition. + * @param src Pointer to the source buffer. Pointer must be non-NULL and + * buffer must be at least 'size' bytes long. + * @param size Size of data to be written, in bytes. + * + * @note Prior to writing to flash memory, make sure it has been erased with + * esp_partition_erase_range call. + * + * @return ESP_OK, if data was written successfully; + * ESP_ERR_INVALID_ARG, if iterator or dst are NULL; + * ESP_ERR_INVALID_SIZE, if write would go out of bounds of the partition; + * or one of error codes from lower-level flash driver. + */ +esp_err_t esp_partition_write(const esp_partition_t* partition, + size_t dst_offset, const uint8_t* src, size_t size); + +/** + * @brief Erase part of the partition + * + * @param partition Pointer to partition structure obtained using + * esp_partition_find_first or esp_partition_get. + * Must be non-NULL. + * @param start_addr Address where erase operation should start. Must be aligned + * to 4 kilobytes. + * @param size Size of the range which should be erased, in bytes. + * Must be divisible by 4 kilobytes. + * + * @return ESP_OK, if the range was erased successfully; + * ESP_ERR_INVALID_ARG, if iterator or dst are NULL; + * ESP_ERR_INVALID_SIZE, if erase would go out of bounds of the partition; + * or one of error codes from lower-level flash driver. + */ +esp_err_t esp_partition_erase_range(const esp_partition_t* partition, + uint32_t start_addr, uint32_t size); + +/** + * @brief Configure MMU to map partition into data memory + * + * Unlike spi_flash_mmap function, which requires a 64kB aligned base address, + * this function doesn't impose such a requirement. + * If offset results in a flash address which is not aligned to 64kB boundary, + * address will be rounded to the lower 64kB boundary, so that mapped region + * includes requested range. + * Pointer returned via out_ptr argument will be adjusted to point to the + * requested offset (not necessarily to the beginning of mmap-ed region). + * + * To release mapped memory, pass handle returned via out_handle argument to + * spi_flash_munmap function. + * + * @param partition Pointer to partition structure obtained using + * esp_partition_find_first or esp_partition_get. + * Must be non-NULL. + * @param offset Offset from the beginning of partition where mapping should start. + * @param size Size of the area to be mapped. + * @param memory Memory space where the region should be mapped + * @param out_ptr Output, pointer to the mapped memory region + * @param out_handle Output, handle which should be used for spi_flash_munmap call + * + * @return ESP_OK, if successful + */ +esp_err_t esp_partition_mmap(const esp_partition_t* partition, uint32_t offset, uint32_t size, + spi_flash_mmap_memory_t memory, + const void** out_ptr, spi_flash_mmap_handle_t* out_handle); + #ifdef __cplusplus } diff --git a/components/spi_flash/include/esp_spi_flash.h b/components/spi_flash/include/esp_spi_flash.h index ab66a4204..2aa92d9a5 100644 --- a/components/spi_flash/include/esp_spi_flash.h +++ b/components/spi_flash/include/esp_spi_flash.h @@ -16,6 +16,7 @@ #define ESP_SPI_FLASH_H #include +#include #include "esp_err.h" #include "sdkconfig.h" diff --git a/components/spi_flash/partition.c b/components/spi_flash/partition.c index 381a1624b..d138f09b5 100644 --- a/components/spi_flash/partition.c +++ b/components/spi_flash/partition.c @@ -135,38 +135,6 @@ const esp_partition_t* esp_partition_find_first(esp_partition_type_t type, const return res; } -void esp_partition_iterator_release(esp_partition_iterator_t iterator) -{ - // iterator == NULL is okay - free(iterator); -} - -const esp_partition_t* esp_partition_get(esp_partition_iterator_t iterator) -{ - assert(iterator != NULL); - return iterator->info; -} - -esp_partition_type_t esp_partition_type(esp_partition_iterator_t iterator) -{ - return esp_partition_get(iterator)->type; -} - -uint32_t esp_partition_size(esp_partition_iterator_t iterator) -{ - return esp_partition_get(iterator)->size; -} - -uint32_t esp_partition_address(esp_partition_iterator_t iterator) -{ - return esp_partition_get(iterator)->address; -} - -const char* esp_partition_label(esp_partition_iterator_t iterator) -{ - return esp_partition_get(iterator)->label; -} - static esp_partition_iterator_opaque_t* iterator_create(esp_partition_type_t type, const char* label) { esp_partition_iterator_opaque_t* it = @@ -219,3 +187,92 @@ static esp_err_t load_partitions() spi_flash_munmap(handle); return ESP_OK; } + +void esp_partition_iterator_release(esp_partition_iterator_t iterator) +{ + // iterator == NULL is okay + free(iterator); +} + +const esp_partition_t* esp_partition_get(esp_partition_iterator_t iterator) +{ + assert(iterator != NULL); + return iterator->info; +} + +esp_err_t esp_partition_read(const esp_partition_t* partition, + size_t src_offset, uint8_t* dst, size_t size) +{ + assert(partition != NULL); + if (src_offset > partition->size) { + return ESP_ERR_INVALID_ARG; + } + if (src_offset + size > partition->size) { + return ESP_ERR_INVALID_SIZE; + } + return spi_flash_read(partition->address + src_offset, dst, size); +} + +esp_err_t esp_partition_write(const esp_partition_t* partition, + size_t dst_offset, const uint8_t* src, size_t size) +{ + assert(partition != NULL); + if (dst_offset > partition->size) { + return ESP_ERR_INVALID_ARG; + } + if (dst_offset + size > partition->size) { + return ESP_ERR_INVALID_SIZE; + } + return spi_flash_write(partition->address + dst_offset, src, size); +} + +esp_err_t esp_partition_erase_range(const esp_partition_t* partition, + size_t start_addr, size_t size) +{ + assert(partition != NULL); + if (start_addr > partition->size) { + return ESP_ERR_INVALID_ARG; + } + if (start_addr + size > partition->size) { + return ESP_ERR_INVALID_SIZE; + } + if (size % SPI_FLASH_SEC_SIZE != 0) { + return ESP_ERR_INVALID_SIZE; + } + if (start_addr % SPI_FLASH_SEC_SIZE != 0) { + return ESP_ERR_INVALID_ARG; + } + return spi_flash_erase_range(partition->address + start_addr, size); + +} + +/* + * Note: current implementation ignores the possibility of multiple regions in the same partition being + * mapped. Reference counting and address space re-use is delegated to spi_flash_mmap. + * + * If this becomes a performance issue (i.e. if we need to map multiple regions within the partition), + * we can add esp_partition_mmapv which will accept an array of offsets and sizes, and return array of + * mmaped pointers, and a single handle for all these regions. + */ +esp_err_t esp_partition_mmap(const esp_partition_t* partition, uint32_t offset, uint32_t size, + spi_flash_mmap_memory_t memory, + const void** out_ptr, spi_flash_mmap_handle_t* out_handle) +{ + assert(partition != NULL); + if (offset > partition->size) { + return ESP_ERR_INVALID_ARG; + } + if (offset + size > partition->size) { + return ESP_ERR_INVALID_SIZE; + } + size_t phys_addr = partition->address + offset; + // offset within 64kB block + size_t region_offset = phys_addr & 0xffff; + size_t mmap_addr = phys_addr & 0xffff0000; + esp_err_t rc = spi_flash_mmap(mmap_addr, size, memory, out_ptr, out_handle); + // adjust returned pointer to point to the correct offset + if (rc == ESP_OK) { + *out_ptr = (void*) (((ptrdiff_t) *out_ptr) + region_offset); + } + return rc; +} From e229ec0340ba2a921223eec51169200cc10c8329 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Fri, 21 Oct 2016 19:33:42 +0800 Subject: [PATCH 093/149] spi_flash: check physical address in mmap against flash chip size --- components/spi_flash/flash_mmap.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/spi_flash/flash_mmap.c b/components/spi_flash/flash_mmap.c index 8636a2605..2165a784d 100644 --- a/components/spi_flash/flash_mmap.c +++ b/components/spi_flash/flash_mmap.c @@ -88,6 +88,9 @@ esp_err_t IRAM_ATTR spi_flash_mmap(uint32_t src_addr, size_t size, spi_flash_mma if (src_addr & 0xffff) { return ESP_ERR_INVALID_ARG; } + if (src_addr + size > g_rom_flashchip.chip_size) { + return ESP_ERR_INVALID_ARG; + } spi_flash_disable_interrupts_caches_and_other_cpu(); if (s_mmap_page_refcnt[0] == 0) { spi_flash_mmap_init(); From e03b45eb0a9eb07eb1e78c7869af18a7faa386d5 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Tue, 25 Oct 2016 11:43:00 +0800 Subject: [PATCH 094/149] spi_flash: improve documentation --- .../esp32/include/esp_flash_data_types.h | 38 ++--- components/spi_flash/README.rst | 133 +++++++++++++++++- components/spi_flash/include/esp_spi_flash.h | 2 + 3 files changed, 152 insertions(+), 21 deletions(-) diff --git a/components/esp32/include/esp_flash_data_types.h b/components/esp32/include/esp_flash_data_types.h index b16ee59f5..4bf886c84 100644 --- a/components/esp32/include/esp_flash_data_types.h +++ b/components/esp32/include/esp_flash_data_types.h @@ -24,7 +24,7 @@ extern "C" #define ESP_PARTITION_TABLE_ADDR 0x4000 #define ESP_PARTITION_MAGIC 0x50AA -/*spi mode,saved in third byte in flash */ +/* SPI flash mode, used in esp_image_header_t */ typedef enum { ESP_IMAGE_SPI_MODE_QIO, ESP_IMAGE_SPI_MODE_QOUT, @@ -34,7 +34,7 @@ typedef enum { ESP_IMAGE_SPI_MODE_SLOW_READ } esp_image_spi_mode_t; -/* spi speed*/ +/* SPI flash clock frequency */ enum { ESP_IMAGE_SPI_SPEED_40M, ESP_IMAGE_SPI_SPEED_26M, @@ -42,7 +42,7 @@ enum { ESP_IMAGE_SPI_SPEED_80M = 0xF } esp_image_spi_freq_t; -/*supported flash sizes*/ +/* Supported SPI flash sizes */ typedef enum { ESP_IMAGE_FLASH_SIZE_1MB = 0, ESP_IMAGE_FLASH_SIZE_2MB, @@ -52,22 +52,23 @@ typedef enum { ESP_IMAGE_FLASH_SIZE_MAX } esp_image_flash_size_t; +/* Main header of binary image */ typedef struct { - char magic; - char blocks; - char spi_mode; /* flag of flash read mode in unpackage and usage in future */ - char spi_speed: 4; /* low bit */ - char spi_size: 4; - unsigned int entry_addr; + uint8_t magic; + uint8_t blocks; + uint8_t spi_mode; /* flash read mode (esp_image_spi_mode_t as uint8_t) */ + uint8_t spi_speed: 4; /* flash frequency (esp_image_spi_freq_t as uint8_t) */ + uint8_t spi_size: 4; /* flash chip size (esp_image_flash_size_t as uint8_t) */ + uint32_t entry_addr; uint8_t encrypt_flag; /* encrypt flag */ uint8_t secure_boot_flag; /* secure boot flag */ - char extra_header[14]; /* ESP32 additional header, unused by second bootloader */ + uint8_t extra_header[14]; /* ESP32 additional header, unused by second bootloader */ } esp_image_header_t; -/* each header of flash bin block */ +/* Header of binary image segment */ typedef struct { - unsigned int load_addr; - unsigned int data_len; + uint32_t load_addr; + uint32_t data_len; } esp_image_section_header_t; @@ -85,13 +86,16 @@ typedef struct { uint32_t size; } esp_partition_pos_t; +/* Structure which describes the layout of partition table entry. + * See docs/partition_tables.rst for more information about individual fields. + */ typedef struct { uint16_t magic; - uint8_t type; /* partition Type */ - uint8_t subtype; /* part_subtype */ + uint8_t type; + uint8_t subtype; esp_partition_pos_t pos; - uint8_t label[16]; /* label for the partition */ - uint8_t reserved[4]; /* reserved */ + uint8_t label[16]; + uint8_t reserved[4]; } esp_partition_info_t; diff --git a/components/spi_flash/README.rst b/components/spi_flash/README.rst index 22f98cf02..b479c3b0e 100644 --- a/components/spi_flash/README.rst +++ b/components/spi_flash/README.rst @@ -1,5 +1,128 @@ -Driver for SPI flash read/write/erase operations -================================================ +SPI flash related APIs +====================== + +Overview +-------- +Spi_flash component contains APIs related to reading, writing, erasing, +memory mapping data in the external SPI flash. It also has higher-level +APIs which work with partition table and partitions. + +Note that all the functionality is limited to the "main" flash chip, +i.e. the flash chip from which program runs. For ``spi_flash_*`` functions, +this is software limitation. Underlying ROM functions which work with SPI flash +do not have provisions for working with flash chips attached to SPI peripherals +other than SPI0. + +SPI flash access APIs +--------------------- + +This is the set of APIs for working with data in flash: + +- ``spi_flash_read`` used to read data from flash to RAM +- ``spi_flash_write`` used to write data from RAM to flash +- ``spi_flash_erase_sector`` used to erase individual sectors of flash +- ``spi_flash_erase_range`` used to erase range of addresses in flash +- ``spi_flash_get_chip_size`` returns flash chip size, in bytes, as configured in menuconfig + +There are some data alignment limitations which need to be considered when using +spi_flash_read/spi_flash_write functions: + +- buffer in RAM must be 4-byte aligned +- size must be 4-byte aligned +- address in flash must be 4-byte aligned + +These alignment limitations are purely software, and should be removed in future +versions. + +It is assumed that correct SPI flash chip size is set at compile time using +menuconfig. While run-time detection of SPI flash chip size is possible, it is +not implemented yet. Applications which need this (e.g. to provide one firmware +binary for different flash sizes) can do flash chip size detection and set +the correct flash chip size in ``chip_size`` member of ``g_rom_flashchip`` +structure. This size is used by ``spi_flash_*`` functions for bounds checking. + +SPI flash APIs disable instruction and data caches while reading/writing/erasing. +See implementation notes below on details how this happens. For application +this means that at some periods of time, code can not be run from flash, +and constant data can not be fetched from flash by the CPU. This is not an +issue for normal code which runs in a task, because SPI flash APIs prevent +other tasks from running while caches are disabled. This is an issue for +interrupt handlers, which can still be called while flash operation is in +progress. If the interrupt handler is not placed into IRAM, there is a +possibility that interrupt will happen at the time when caches are disabled, +which will cause an illegal instruction exception. + +To prevent this, make sure that all ISR code, and all functions called from ISR +code are placed into IRAM, or are located in ROM. Most useful C library +functions are located in ROM, so they can be called from ISR. + +To place a function into IRAM, use ``IRAM_ATTR`` attribute, e.g.:: + + #include "esp_attr.h" + + void IRAM_ATTR gpio_isr_handler(void* arg) + { + // ... + } + +When flash encryption is enabled, ``spi_flash_read`` will read data as it is +stored in flash (without decryption), and ``spi_flash_write`` will write data +in plain text. In other words, ``spi_flash_read/write`` APIs don't have +provisions to deal with encrypted data. + + +Partition table APIs +-------------------- + +ESP-IDF uses partition table to maintain information about various regions of +SPI flash memory (bootloader, various application binaries, data, filesystems). +More information about partition tables can be found in docs/partition_tables.rst. + +This component provides APIs to enumerate partitions found in the partition table +and perform operations on them. These functions are declared in ``esp_partition.h``: + +- ``esp_partition_find`` used to search partition table for entries with specific type, returns an opaque iterator +- ``esp_partition_get`` returns a structure describing the partition, for the given iterator +- ``esp_partition_next`` advances iterator to the next partition found +- ``esp_partition_iterator_release`` releases iterator returned by ``esp_partition_find`` +- ``esp_partition_find_first`` is a convenience function which returns structure describing the first partition found by esp_partition_find +- ``esp_partition_read``, ``esp_partition_write``, ``esp_partition_erase_range`` are equivalent to ``spi_flash_read``, ``spi_flash_write``, ``spi_flash_erase_range``, but operate within partition boundaries + +Most application code should use ``esp_partition_*`` APIs instead of lower level +``spi_flash_*`` APIs. Partition APIs do bounds checking and calculate correct +offsets in flash based on data stored in partition table. + +Memory mapping APIs +------------------- + +ESP32 features memory hardware which allows regions of flash memory to be mapped +into instruction and data address spaces. This mapping works only for read operations, +it is not possible to modify contents of flash memory by writing to mapped memory +region. Mapping happens in 64KB pages. Memory mapping hardware can map up to +4 megabytes of flash into data address space, and up to 16 megabytes of flash into +instruction address space. See the technical reference manual for more details +about memory mapping hardware. + +Note that some number of 64KB pages is used to map the application +itself into memory, so the actual number of available 64KB pages may be less. + +Reading data from flash using a memory mapped region is the only way to decrypt +contents of flash when flash encryption is enabled. Decryption is performed at +hardware level. + +Memory mapping APIs are declared in ``esp_spi_flash.h`` and ``esp_partition.h``: + +- ``spi_flash_mmap`` maps a region of physical flash addresses into instruction space or data space of the CPU +- ``spi_flash_munmap`` unmaps previously mapped region +- ``esp_partition_mmap`` maps part of a partition into the instruction space or data space of the CPU + +Differences between ``spi_flash_mmap`` and ``esp_partition_mmap`` are as follows: + +- ``spi_flash_mmap`` must be given a 64KB aligned physical address +- ``esp_partition_mmap`` may be given an arbitrary offset within the partition, it will adjust returned pointer to mapped memory as necessary + +Note that because memory mapping happens in 64KB blocks, it may be possible to +read data outside of the partition provided to ``esp_partition_mmap``. Implementation notes -------------------- @@ -19,8 +142,10 @@ signals that cache is disabled by setting s_flash_op_can_start flag. Then the task on CPU A disables cache as well, and proceeds to execute flash operation. -While flash operation is running, interrupts can still run on CPU B. -We assume that all interrupt code is placed into RAM. +While flash operation is running, interrupts can still run on CPUs A and B. +We assume that all interrupt code is placed into RAM. Once interrupt allocation +API is added, we should add a flag to request interrupt to be disabled for +the duration of flash operations. Once flash operation is complete, function on CPU A sets another flag, s_flash_op_complete, to let the task on CPU B know that it can re-enable diff --git a/components/spi_flash/include/esp_spi_flash.h b/components/spi_flash/include/esp_spi_flash.h index 2aa92d9a5..16b50a718 100644 --- a/components/spi_flash/include/esp_spi_flash.h +++ b/components/spi_flash/include/esp_spi_flash.h @@ -35,6 +35,8 @@ extern "C" { * * This function must be called exactly once, before any other * spi_flash_* functions are called. + * Currently this function is called from startup code. There is + * no need to call it from application code. * */ void spi_flash_init(); From 9f0f05d5201aeed9390f94b50f5cf65403040729 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Thu, 27 Oct 2016 00:46:33 +0800 Subject: [PATCH 095/149] spi_flash: change pointer type to void* --- components/nvs_flash/src/nvs_page.cpp | 20 +++++++++---------- .../nvs_flash/test/spi_flash_emulation.cpp | 4 ++-- .../test/test_spi_flash_emulation.cpp | 14 ++++++------- components/spi_flash/flash_ops.c | 4 ++-- components/spi_flash/include/esp_partition.h | 4 ++-- components/spi_flash/include/esp_spi_flash.h | 4 ++-- 6 files changed, 25 insertions(+), 25 deletions(-) diff --git a/components/nvs_flash/src/nvs_page.cpp b/components/nvs_flash/src/nvs_page.cpp index 9ba478e21..a5e058758 100644 --- a/components/nvs_flash/src/nvs_page.cpp +++ b/components/nvs_flash/src/nvs_page.cpp @@ -37,7 +37,7 @@ esp_err_t Page::load(uint32_t sectorNumber) mErasedEntryCount = 0; Header header; - auto rc = spi_flash_read(mBaseAddress, reinterpret_cast(&header), sizeof(header)); + auto rc = spi_flash_read(mBaseAddress, &header, sizeof(header)); if (rc != ESP_OK) { mState = PageState::INVALID; return rc; @@ -48,7 +48,7 @@ esp_err_t Page::load(uint32_t sectorNumber) // reading the whole page takes ~40 times less than erasing it uint32_t line[8]; for (uint32_t i = 0; i < SPI_FLASH_SEC_SIZE; i += sizeof(line)) { - rc = spi_flash_read(mBaseAddress + i, reinterpret_cast(line), sizeof(line)); + rc = spi_flash_read(mBaseAddress + i, line, sizeof(line)); if (rc != ESP_OK) { mState = PageState::INVALID; return rc; @@ -86,7 +86,7 @@ esp_err_t Page::load(uint32_t sectorNumber) esp_err_t Page::writeEntry(const Item& item) { - auto rc = spi_flash_write(getEntryAddress(mNextFreeEntry), reinterpret_cast(&item), sizeof(item)); + auto rc = spi_flash_write(getEntryAddress(mNextFreeEntry), &item, sizeof(item)); if (rc != ESP_OK) { mState = PageState::INVALID; return rc; @@ -396,7 +396,7 @@ esp_err_t Page::mLoadEntryTable() if (mState == PageState::ACTIVE || mState == PageState::FULL || mState == PageState::FREEING) { - auto rc = spi_flash_read(mBaseAddress + ENTRY_TABLE_OFFSET, reinterpret_cast(mEntryTable.data()), + auto rc = spi_flash_read(mBaseAddress + ENTRY_TABLE_OFFSET, mEntryTable.data(), mEntryTable.byteSize()); if (rc != ESP_OK) { mState = PageState::INVALID; @@ -435,7 +435,7 @@ esp_err_t Page::mLoadEntryTable() while (mNextFreeEntry < ENTRY_COUNT) { uint32_t entryAddress = getEntryAddress(mNextFreeEntry); uint32_t header; - auto rc = spi_flash_read(entryAddress, reinterpret_cast(&header), sizeof(header)); + auto rc = spi_flash_read(entryAddress, &header, sizeof(header)); if (rc != ESP_OK) { mState = PageState::INVALID; return rc; @@ -559,7 +559,7 @@ esp_err_t Page::initialize() header.mSeqNumber = mSeqNumber; header.mCrc32 = header.calculateCrc32(); - auto rc = spi_flash_write(mBaseAddress, reinterpret_cast(&header), sizeof(header)); + auto rc = spi_flash_write(mBaseAddress, &header, sizeof(header)); if (rc != ESP_OK) { mState = PageState::INVALID; return rc; @@ -578,7 +578,7 @@ esp_err_t Page::alterEntryState(size_t index, EntryState state) size_t wordToWrite = mEntryTable.getWordIndex(index); uint32_t word = mEntryTable.data()[wordToWrite]; auto rc = spi_flash_write(mBaseAddress + ENTRY_TABLE_OFFSET + static_cast(wordToWrite) * 4, - reinterpret_cast(&word), sizeof(word)); + &word, sizeof(word)); if (rc != ESP_OK) { mState = PageState::INVALID; return rc; @@ -602,7 +602,7 @@ esp_err_t Page::alterEntryRangeState(size_t begin, size_t end, EntryState state) if (nextWordIndex != wordIndex) { uint32_t word = mEntryTable.data()[wordIndex]; auto rc = spi_flash_write(mBaseAddress + ENTRY_TABLE_OFFSET + static_cast(wordIndex) * 4, - reinterpret_cast(&word), 4); + &word, 4); if (rc != ESP_OK) { return rc; } @@ -615,7 +615,7 @@ esp_err_t Page::alterEntryRangeState(size_t begin, size_t end, EntryState state) esp_err_t Page::alterPageState(PageState state) { uint32_t state_val = static_cast(state); - auto rc = spi_flash_write(mBaseAddress, reinterpret_cast(&state_val), sizeof(state)); + auto rc = spi_flash_write(mBaseAddress, &state_val, sizeof(state)); if (rc != ESP_OK) { mState = PageState::INVALID; return rc; @@ -626,7 +626,7 @@ esp_err_t Page::alterPageState(PageState state) esp_err_t Page::readEntry(size_t index, Item& dst) const { - auto rc = spi_flash_read(getEntryAddress(index), reinterpret_cast(&dst), sizeof(dst)); + auto rc = spi_flash_read(getEntryAddress(index), &dst, sizeof(dst)); if (rc != ESP_OK) { return rc; } diff --git a/components/nvs_flash/test/spi_flash_emulation.cpp b/components/nvs_flash/test/spi_flash_emulation.cpp index bd1482268..914efc145 100644 --- a/components/nvs_flash/test/spi_flash_emulation.cpp +++ b/components/nvs_flash/test/spi_flash_emulation.cpp @@ -35,7 +35,7 @@ esp_err_t spi_flash_erase_sector(size_t sec) return ESP_OK; } -esp_err_t spi_flash_write(size_t des_addr, const uint8_t *src_addr, size_t size) +esp_err_t spi_flash_write(size_t des_addr, const void *src_addr, size_t size) { if (!s_emulator) { return ESP_ERR_FLASH_OP_TIMEOUT; @@ -48,7 +48,7 @@ esp_err_t spi_flash_write(size_t des_addr, const uint8_t *src_addr, size_t size) return ESP_OK; } -esp_err_t spi_flash_read(size_t src_addr, uint8_t *des_addr, size_t size) +esp_err_t spi_flash_read(size_t src_addr, void *des_addr, size_t size) { if (!s_emulator) { return ESP_ERR_FLASH_OP_TIMEOUT; diff --git a/components/nvs_flash/test/test_spi_flash_emulation.cpp b/components/nvs_flash/test/test_spi_flash_emulation.cpp index 329e721ce..0c77aa966 100644 --- a/components/nvs_flash/test/test_spi_flash_emulation.cpp +++ b/components/nvs_flash/test/test_spi_flash_emulation.cpp @@ -42,9 +42,9 @@ TEST_CASE("invalid writes are checked", "[spi_flash_emu]") SpiFlashEmulator emu(1); uint32_t val = 0; - CHECK(spi_flash_write(0, reinterpret_cast(&val), 4) == ESP_OK); + CHECK(spi_flash_write(0, &val, 4) == ESP_OK); val = 1; - CHECK(spi_flash_write(0, reinterpret_cast(&val), 4) == ESP_ERR_FLASH_OP_FAIL); + CHECK(spi_flash_write(0, &val, 4) == ESP_ERR_FLASH_OP_FAIL); } @@ -53,11 +53,11 @@ TEST_CASE("out of bounds writes fail", "[spi_flash_emu]") SpiFlashEmulator emu(4); uint32_t vals[8]; std::fill_n(vals, 8, 0); - CHECK(spi_flash_write(0, reinterpret_cast(vals), sizeof(vals)) == ESP_OK); + CHECK(spi_flash_write(0, vals, sizeof(vals)) == ESP_OK); - CHECK(spi_flash_write(4*4096 - sizeof(vals), reinterpret_cast(vals), sizeof(vals)) == ESP_OK); + CHECK(spi_flash_write(4*4096 - sizeof(vals), vals, sizeof(vals)) == ESP_OK); - CHECK(spi_flash_write(4*4096 - sizeof(vals) + 4, reinterpret_cast(vals), sizeof(vals)) == ESP_ERR_FLASH_OP_FAIL); + CHECK(spi_flash_write(4*4096 - sizeof(vals) + 4, vals, sizeof(vals)) == ESP_ERR_FLASH_OP_FAIL); } @@ -65,9 +65,9 @@ TEST_CASE("after erase the sector is set to 0xff", "[spi_flash_emu]") { SpiFlashEmulator emu(4); uint32_t val1 = 0xab00cd12; - CHECK(spi_flash_write(0, reinterpret_cast(&val1), sizeof(val1)) == ESP_OK); + CHECK(spi_flash_write(0, &val1, sizeof(val1)) == ESP_OK); uint32_t val2 = 0x5678efab; - CHECK(spi_flash_write(4096 - 4, reinterpret_cast(&val2), sizeof(val2)) == ESP_OK); + CHECK(spi_flash_write(4096 - 4, &val2, sizeof(val2)) == ESP_OK); CHECK(emu.words()[0] == val1); CHECK(range_empty_n(emu.words() + 1, 4096 / 4 - 2)); diff --git a/components/spi_flash/flash_ops.c b/components/spi_flash/flash_ops.c index 512f6d20d..ae72568aa 100644 --- a/components/spi_flash/flash_ops.c +++ b/components/spi_flash/flash_ops.c @@ -124,7 +124,7 @@ esp_err_t IRAM_ATTR spi_flash_erase_range(uint32_t start_addr, uint32_t size) return spi_flash_translate_rc(rc); } -esp_err_t IRAM_ATTR spi_flash_write(size_t dest_addr, const uint8_t *src, size_t size) +esp_err_t IRAM_ATTR spi_flash_write(size_t dest_addr, const void *src, size_t size) { // TODO: replace this check with code which deals with unaligned sources if (((ptrdiff_t) src) % 4 != 0) { @@ -157,7 +157,7 @@ esp_err_t IRAM_ATTR spi_flash_write(size_t dest_addr, const uint8_t *src, size_t return spi_flash_translate_rc(rc); } -esp_err_t IRAM_ATTR spi_flash_read(size_t src_addr, uint8_t *dest, size_t size) +esp_err_t IRAM_ATTR spi_flash_read(size_t src_addr, void *dest, size_t size) { // TODO: replace this check with code which deals with unaligned destinations if (((ptrdiff_t) dest) % 4 != 0) { diff --git a/components/spi_flash/include/esp_partition.h b/components/spi_flash/include/esp_partition.h index 89e523f9a..432575f19 100644 --- a/components/spi_flash/include/esp_partition.h +++ b/components/spi_flash/include/esp_partition.h @@ -159,7 +159,7 @@ void esp_partition_iterator_release(esp_partition_iterator_t iterator); * or one of error codes from lower-level flash driver. */ esp_err_t esp_partition_read(const esp_partition_t* partition, - size_t src_offset, uint8_t* dst, size_t size); + size_t src_offset, void* dst, size_t size); /** * @brief Write data to the partition @@ -185,7 +185,7 @@ esp_err_t esp_partition_read(const esp_partition_t* partition, * or one of error codes from lower-level flash driver. */ esp_err_t esp_partition_write(const esp_partition_t* partition, - size_t dst_offset, const uint8_t* src, size_t size); + size_t dst_offset, const void* src, size_t size); /** * @brief Erase part of the partition diff --git a/components/spi_flash/include/esp_spi_flash.h b/components/spi_flash/include/esp_spi_flash.h index 16b50a718..df0a8decc 100644 --- a/components/spi_flash/include/esp_spi_flash.h +++ b/components/spi_flash/include/esp_spi_flash.h @@ -83,7 +83,7 @@ esp_err_t spi_flash_erase_range(size_t start_addr, size_t size); * * @return esp_err_t */ -esp_err_t spi_flash_write(size_t des_addr, const uint8_t *src_addr, size_t size); +esp_err_t spi_flash_write(size_t dest, const void *src, size_t size); /** * @brief Read data from Flash. @@ -97,7 +97,7 @@ esp_err_t spi_flash_write(size_t des_addr, const uint8_t *src_addr, size_t size) * * @return esp_err_t */ -esp_err_t spi_flash_read(size_t src_addr, uint8_t *des_addr, size_t size); +esp_err_t spi_flash_read(size_t src, void *dest, size_t size); /** * @brief Enumeration which specifies memory space requested in an mmap call From c581229e1d47da7b531433905896e4820ccdf54e Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Thu, 27 Oct 2016 00:48:10 +0800 Subject: [PATCH 096/149] partition API: separate type and subtype into two enums --- components/spi_flash/include/esp_partition.h | 91 ++++++++++---------- components/spi_flash/partition.c | 41 ++++----- 2 files changed, 62 insertions(+), 70 deletions(-) diff --git a/components/spi_flash/include/esp_partition.h b/components/spi_flash/include/esp_partition.h index 432575f19..e2f289eb7 100644 --- a/components/spi_flash/include/esp_partition.h +++ b/components/spi_flash/include/esp_partition.h @@ -26,51 +26,52 @@ extern "C" { #endif typedef enum { - ESP_PARTITION_APP_MASK = 0x0000, - ESP_PARTITION_APP_FACTORY = ESP_PARTITION_APP_MASK | 0x00, - ESP_PARTITION_APP_OTA_MIN = ESP_PARTITION_APP_MASK | 0x10, - ESP_PARTITION_APP_OTA_0 = ESP_PARTITION_APP_OTA_MIN + 0, - ESP_PARTITION_APP_OTA_1 = ESP_PARTITION_APP_OTA_MIN + 1, - ESP_PARTITION_APP_OTA_2 = ESP_PARTITION_APP_OTA_MIN + 2, - ESP_PARTITION_APP_OTA_3 = ESP_PARTITION_APP_OTA_MIN + 3, - ESP_PARTITION_APP_OTA_4 = ESP_PARTITION_APP_OTA_MIN + 4, - ESP_PARTITION_APP_OTA_5 = ESP_PARTITION_APP_OTA_MIN + 5, - ESP_PARTITION_APP_OTA_6 = ESP_PARTITION_APP_OTA_MIN + 6, - ESP_PARTITION_APP_OTA_7 = ESP_PARTITION_APP_OTA_MIN + 7, - ESP_PARTITION_APP_OTA_8 = ESP_PARTITION_APP_OTA_MIN + 8, - ESP_PARTITION_APP_OTA_9 = ESP_PARTITION_APP_OTA_MIN + 9, - ESP_PARTITION_APP_OTA_10 = ESP_PARTITION_APP_OTA_MIN + 10, - ESP_PARTITION_APP_OTA_11 = ESP_PARTITION_APP_OTA_MIN + 11, - ESP_PARTITION_APP_OTA_12 = ESP_PARTITION_APP_OTA_MIN + 12, - ESP_PARTITION_APP_OTA_13 = ESP_PARTITION_APP_OTA_MIN + 13, - ESP_PARTITION_APP_OTA_14 = ESP_PARTITION_APP_OTA_MIN + 14, - ESP_PARTITION_APP_OTA_15 = ESP_PARTITION_APP_OTA_MIN + 15, - ESP_PARTITION_APP_OTA_MAX = ESP_PARTITION_APP_MASK | 0x1f, - ESP_PARTITION_APP_TEST = ESP_PARTITION_APP_MASK | 0x20, - ESP_PARTITION_APP_ANY = ESP_PARTITION_APP_MASK | 0xff, - - ESP_PARTITION_DATA_MASK = 0x0100, - ESP_PARTITION_DATA_OTA = ESP_PARTITION_DATA_MASK | 0x00, - ESP_PARTITION_DATA_RF = ESP_PARTITION_DATA_MASK | 0x01, - ESP_PARTITION_DATA_WIFI = ESP_PARTITION_DATA_MASK | 0x02, - ESP_PARTITION_DATA_ANY = ESP_PARTITION_DATA_MASK | 0xff, - - ESP_PARTITION_FILESYSTEM_MASK = 0x0200, - ESP_PARTITION_FILESYSTEM_ESPHTTPD = 0x0200, - ESP_PARTITION_FILESYSTEM_FAT = 0x0201, - ESP_PARTITION_FILESYSTEM_SPIFFS = 0x0202, - ESP_PARTITION_FILESYSTEM_ANY = 0x20ff, - - ESP_PARTITION_END = 0xffff + ESP_PARTITION_TYPE_APP = 0x00, + ESP_PARTITION_TYPE_DATA = 0x01, + ESP_PARTITION_TYPE_FILESYSTEM = 0x02, } esp_partition_type_t; -#define ESP_PARTITION_APP_OTA(i) ((esp_partition_type_t)(ESP_PARTITION_APP_OTA_MIN + ((i) & 0xf))) +typedef enum { + ESP_PARTITION_SUBTYPE_APP_FACTORY = 0x00, + ESP_PARTITION_SUBTYPE_APP_OTA_MIN = 0x10, + ESP_PARTITION_SUBTYPE_APP_OTA_0 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 0, + ESP_PARTITION_SUBTYPE_APP_OTA_1 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 1, + ESP_PARTITION_SUBTYPE_APP_OTA_2 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 2, + ESP_PARTITION_SUBTYPE_APP_OTA_3 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 3, + ESP_PARTITION_SUBTYPE_APP_OTA_4 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 4, + ESP_PARTITION_SUBTYPE_APP_OTA_5 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 5, + ESP_PARTITION_SUBTYPE_APP_OTA_6 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 6, + ESP_PARTITION_SUBTYPE_APP_OTA_7 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 7, + ESP_PARTITION_SUBTYPE_APP_OTA_8 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 8, + ESP_PARTITION_SUBTYPE_APP_OTA_9 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 9, + ESP_PARTITION_SUBTYPE_APP_OTA_10 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 10, + ESP_PARTITION_SUBTYPE_APP_OTA_11 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 11, + ESP_PARTITION_SUBTYPE_APP_OTA_12 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 12, + ESP_PARTITION_SUBTYPE_APP_OTA_13 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 13, + ESP_PARTITION_SUBTYPE_APP_OTA_14 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 14, + ESP_PARTITION_SUBTYPE_APP_OTA_15 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 15, + ESP_PARTITION_SUBTYPE_APP_OTA_MAX = 15, + ESP_PARTITION_SUBTYPE_APP_TEST = 0x20, + + ESP_PARTITION_SUBTYPE_DATA_OTA = 0x00, + ESP_PARTITION_SUBTYPE_DATA_RF = 0x01, + ESP_PARTITION_SUBTYPE_DATA_NVS = 0x02, + + ESP_PARTITION_SUBTYPE_FILESYSTEM_ESPHTTPD = 0x00, + ESP_PARTITION_SUBTYPE_FILESYSTEM_FAT = 0x01, + ESP_PARTITION_SUBTYPE_FILESYSTEM_SPIFFS = 0x02, + + ESP_PARTITION_SUBTYPE_ANY = 0xff, +} esp_partition_subtype_t; + +#define ESP_PARTITION_SUBTYPE_OTA(i) ((esp_partition_subtype_t)(ESP_PARTITION_SUBTYPE_APP_OTA_MIN + ((i) & 0xf))) typedef struct esp_partition_iterator_opaque_* esp_partition_iterator_t; typedef struct { esp_partition_type_t type; + esp_partition_subtype_t subtype; uint32_t address; uint32_t size; char label[17]; @@ -81,9 +82,9 @@ typedef struct { * @brief Find partition based on one or more parameters * * @param type Partition type, one of esp_partition_type_t values - * To find all app partitions or all filesystem partitions, - * use ESP_PARTITION_APP_ANY or ESP_PARTITION_FILESYSTEM_ANY, - * respectively. + * @param subtype Partition subtype, of esp_partition_subtype_t values. + * To find all partitions of given type, use + * ESP_PARTITION_SUBTYPE_ANY. * @param label (optional) Partition label. Set this value if looking * for partition with a specific name. Pass NULL otherwise. * @@ -92,22 +93,22 @@ typedef struct { * Iterator obtained through this function has to be released * using esp_partition_iterator_release when not used any more. */ -esp_partition_iterator_t esp_partition_find(esp_partition_type_t type, const char* label); +esp_partition_iterator_t esp_partition_find(esp_partition_type_t type, esp_partition_subtype_t subtype, const char* label); /** * @brief Find first partition based on one or more parameters * * @param type Partition type, one of esp_partition_type_t values - * To find all app partitions or all filesystem partitions, - * use ESP_PARTITION_APP_ANY or ESP_PARTITION_FILESYSTEM_ANY, - * respectively. + * @param subtype Partition subtype, of esp_partition_subtype_t values. + * To find all partitions of given type, use + * ESP_PARTITION_SUBTYPE_ANY. * @param label (optional) Partition label. Set this value if looking * for partition with a specific name. Pass NULL otherwise. * * @return pointer to esp_partition_t structure, or NULL if no parition is found. * This pointer is valid for the lifetime of the application. */ -const esp_partition_t* esp_partition_find_first(esp_partition_type_t type, const char* label); +const esp_partition_t* esp_partition_find_first(esp_partition_type_t type, esp_partition_subtype_t subtype, const char* label); /** * @brief Get esp_partition_t structure for given partition diff --git a/components/spi_flash/partition.c b/components/spi_flash/partition.c index d138f09b5..21013d96f 100644 --- a/components/spi_flash/partition.c +++ b/components/spi_flash/partition.c @@ -39,13 +39,14 @@ typedef struct partition_list_item_ { typedef struct esp_partition_iterator_opaque_ { esp_partition_type_t type; // requested type + esp_partition_subtype_t subtype; // requested subtype const char* label; // requested label (can be NULL) partition_list_item_t* next_item; // next item to iterate to esp_partition_t* info; // pointer to info (it is redundant, but makes code more readable) } esp_partition_iterator_opaque_t; -static esp_partition_iterator_opaque_t* iterator_create(esp_partition_type_t type, const char* label); +static esp_partition_iterator_opaque_t* iterator_create(esp_partition_type_t type, esp_partition_subtype_t subtype, const char* label); static esp_err_t load_partitions(); @@ -54,18 +55,8 @@ static SLIST_HEAD(partition_list_head_, partition_list_item_) s_partition_list = static _lock_t s_partition_list_lock; -static uint32_t get_major_type(esp_partition_type_t type) -{ - return (type >> 8) & 0xff; -} - -static uint32_t get_minor_type(esp_partition_type_t type) -{ - return type & 0xff; -} - esp_partition_iterator_t esp_partition_find(esp_partition_type_t type, - const char* label) + esp_partition_subtype_t subtype, const char* label) { if (SLIST_EMPTY(&s_partition_list)) { // only lock if list is empty (and check again after acquiring lock) @@ -81,7 +72,7 @@ esp_partition_iterator_t esp_partition_find(esp_partition_type_t type, } // create an iterator pointing to the start of the list // (next item will be the first one) - esp_partition_iterator_t it = iterator_create(type, label); + esp_partition_iterator_t it = iterator_create(type, subtype, label); // advance iterator to the next item which matches constraints it = esp_partition_next(it); // if nothing found, it == NULL and iterator has been released @@ -95,17 +86,13 @@ esp_partition_iterator_t esp_partition_next(esp_partition_iterator_t it) if (it->next_item == NULL) { return NULL; } - uint32_t requested_major_type = get_major_type(it->type); - uint32_t requested_minor_type = get_minor_type(it->type); _lock_acquire(&s_partition_list_lock); for (; it->next_item != NULL; it->next_item = SLIST_NEXT(it->next_item, next)) { esp_partition_t* p = &it->next_item->info; - uint32_t it_major_type = get_major_type(p->type); - uint32_t it_minor_type = get_minor_type(p->type); - if (requested_major_type != it_major_type) { + if (it->type != p->type) { continue; } - if (requested_minor_type != 0xff && requested_minor_type != it_minor_type) { + if (it->subtype != 0xff && it->subtype != p->subtype) { continue; } if (it->label != NULL && strcmp(it->label, p->label) != 0) { @@ -124,9 +111,10 @@ esp_partition_iterator_t esp_partition_next(esp_partition_iterator_t it) return it; } -const esp_partition_t* esp_partition_find_first(esp_partition_type_t type, const char* label) +const esp_partition_t* esp_partition_find_first(esp_partition_type_t type, + esp_partition_subtype_t subtype, const char* label) { - esp_partition_iterator_t it = esp_partition_find(type, label); + esp_partition_iterator_t it = esp_partition_find(type, subtype, label); if (it == NULL) { return NULL; } @@ -135,11 +123,13 @@ const esp_partition_t* esp_partition_find_first(esp_partition_type_t type, const return res; } -static esp_partition_iterator_opaque_t* iterator_create(esp_partition_type_t type, const char* label) +static esp_partition_iterator_opaque_t* iterator_create(esp_partition_type_t type, + esp_partition_subtype_t subtype, const char* label) { esp_partition_iterator_opaque_t* it = (esp_partition_iterator_opaque_t*) malloc(sizeof(esp_partition_iterator_opaque_t)); it->type = type; + it->subtype = subtype; it->label = label; it->next_item = SLIST_FIRST(&s_partition_list); it->info = NULL; @@ -172,7 +162,8 @@ static esp_err_t load_partitions() partition_list_item_t* item = (partition_list_item_t*) malloc(sizeof(partition_list_item_t)); item->info.address = it->pos.offset; item->info.size = it->pos.size; - item->info.type = (it->type << 8) | it->subtype; + item->info.type = it->type; + item->info.subtype = it->subtype; item->info.encrypted = false; // it->label may not be zero-terminated strncpy(item->info.label, (const char*) it->label, sizeof(it->label)); @@ -201,7 +192,7 @@ const esp_partition_t* esp_partition_get(esp_partition_iterator_t iterator) } esp_err_t esp_partition_read(const esp_partition_t* partition, - size_t src_offset, uint8_t* dst, size_t size) + size_t src_offset, void* dst, size_t size) { assert(partition != NULL); if (src_offset > partition->size) { @@ -214,7 +205,7 @@ esp_err_t esp_partition_read(const esp_partition_t* partition, } esp_err_t esp_partition_write(const esp_partition_t* partition, - size_t dst_offset, const uint8_t* src, size_t size) + size_t dst_offset, const void* src, size_t size) { assert(partition != NULL); if (dst_offset > partition->size) { From dc2b5d0c96ff018ebd5b988939f44793dbed3f9d Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Thu, 27 Oct 2016 10:16:42 +0800 Subject: [PATCH 097/149] spi_flash: update comment blocks --- components/spi_flash/include/esp_partition.h | 21 +++++++++----------- components/spi_flash/include/esp_spi_flash.h | 8 ++++---- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/components/spi_flash/include/esp_partition.h b/components/spi_flash/include/esp_partition.h index e2f289eb7..ae0185dcd 100644 --- a/components/spi_flash/include/esp_partition.h +++ b/components/spi_flash/include/esp_partition.h @@ -82,9 +82,9 @@ typedef struct { * @brief Find partition based on one or more parameters * * @param type Partition type, one of esp_partition_type_t values - * @param subtype Partition subtype, of esp_partition_subtype_t values. - * To find all partitions of given type, use - * ESP_PARTITION_SUBTYPE_ANY. + * @param subtype Partition subtype, one of esp_partition_subtype_t values. + * To find all partitions of given type, use + * ESP_PARTITION_SUBTYPE_ANY. * @param label (optional) Partition label. Set this value if looking * for partition with a specific name. Pass NULL otherwise. * @@ -99,13 +99,13 @@ esp_partition_iterator_t esp_partition_find(esp_partition_type_t type, esp_parti * @brief Find first partition based on one or more parameters * * @param type Partition type, one of esp_partition_type_t values - * @param subtype Partition subtype, of esp_partition_subtype_t values. - * To find all partitions of given type, use - * ESP_PARTITION_SUBTYPE_ANY. + * @param subtype Partition subtype, one of esp_partition_subtype_t values. + * To find all partitions of given type, use + * ESP_PARTITION_SUBTYPE_ANY. * @param label (optional) Partition label. Set this value if looking * for partition with a specific name. Pass NULL otherwise. * - * @return pointer to esp_partition_t structure, or NULL if no parition is found. + * @return pointer to esp_partition_t structure, or NULL if no partition is found. * This pointer is valid for the lifetime of the application. */ const esp_partition_t* esp_partition_find_first(esp_partition_type_t type, esp_partition_subtype_t subtype, const char* label); @@ -134,9 +134,6 @@ esp_partition_iterator_t esp_partition_next(esp_partition_iterator_t iterator); /** * @brief Release partition iterator * - * Any pointers obtained using esp_partition_label function will be invalid - * after this call. - * * @param iterator Iterator obtained using esp_partition_find. Must be non-NULL. * */ @@ -155,7 +152,7 @@ void esp_partition_iterator_release(esp_partition_iterator_t iterator); * @param size Size of data to be read, in bytes. * * @return ESP_OK, if data was read successfully; - * ESP_ERR_INVALID_ARG, if iterator or src are NULL; + * ESP_ERR_INVALID_ARG, if src_offset exceeds partition size; * ESP_ERR_INVALID_SIZE, if read would go out of bounds of the partition; * or one of error codes from lower-level flash driver. */ @@ -181,7 +178,7 @@ esp_err_t esp_partition_read(const esp_partition_t* partition, * esp_partition_erase_range call. * * @return ESP_OK, if data was written successfully; - * ESP_ERR_INVALID_ARG, if iterator or dst are NULL; + * ESP_ERR_INVALID_ARG, if dst_offset exceeds partition size; * ESP_ERR_INVALID_SIZE, if write would go out of bounds of the partition; * or one of error codes from lower-level flash driver. */ diff --git a/components/spi_flash/include/esp_spi_flash.h b/components/spi_flash/include/esp_spi_flash.h index df0a8decc..c65eaa583 100644 --- a/components/spi_flash/include/esp_spi_flash.h +++ b/components/spi_flash/include/esp_spi_flash.h @@ -77,8 +77,8 @@ esp_err_t spi_flash_erase_range(size_t start_addr, size_t size); * @note Both des_addr and src_addr have to be 4-byte aligned. * This is a temporary limitation which will be removed. * - * @param des_addr destination address in Flash - * @param src_addr source address of the data + * @param dest destination address in Flash + * @param src pointer to the source buffer * @param size length of data, in bytes * * @return esp_err_t @@ -91,8 +91,8 @@ esp_err_t spi_flash_write(size_t dest, const void *src, size_t size); * @note Both des_addr and src_addr have to be 4-byte aligned. * This is a temporary limitation which will be removed. * - * @param src_addr source address of the data in Flash. - * @param des_addr destination address + * @param src source address of the data in Flash. + * @param dest pointer to the destination buffer * @param size length of data * * @return esp_err_t From 305b63209ecb3e7c3c6c0bf20f60d41135e7da8f Mon Sep 17 00:00:00 2001 From: Liu Zhi Fu Date: Fri, 28 Oct 2016 12:03:51 +0800 Subject: [PATCH 098/149] lwip: support max 16 sockets Since the customers need more sockets in their application, support max 16 sockets, in other words, the total socket number of UDP/TCP/RAW sockets should not exceed 16. --- components/lwip/include/lwip/port/lwipopts.h | 32 +++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/components/lwip/include/lwip/port/lwipopts.h b/components/lwip/include/lwip/port/lwipopts.h index 75f349280..e72279067 100755 --- a/components/lwip/include/lwip/port/lwipopts.h +++ b/components/lwip/include/lwip/port/lwipopts.h @@ -99,13 +99,37 @@ extern unsigned long os_random(void); * MEMP_NUM_TCP_PCB: the number of simulatenously active TCP connections. * (requires the LWIP_TCP option) */ -#define MEMP_NUM_TCP_PCB 5 /** * MEMP_NUM_NETCONN: the number of struct netconns. * (only needed if you use the sequential API, like api_lib.c) */ -#define MEMP_NUM_NETCONN 10 +#define MEMP_NUM_NETCONN CONFIG_LWIP_MAX_SOCKETS + +/** + * MEMP_NUM_RAW_PCB: Number of raw connection PCBs + * (requires the LWIP_RAW option) + */ +#define MEMP_NUM_RAW_PCB 16 + +/** + * MEMP_NUM_TCP_PCB: the number of simulatenously active TCP connections. + * (requires the LWIP_TCP option) + */ +#define MEMP_NUM_TCP_PCB 16 + +/** + * MEMP_NUM_TCP_PCB_LISTEN: the number of listening TCP connections. + * (requires the LWIP_TCP option) + */ +#define MEMP_NUM_TCP_PCB_LISTEN 16 + +/** + * MEMP_NUM_UDP_PCB: the number of UDP protocol control blocks. One + * per active UDP "connection". + * (requires the LWIP_UDP option) + */ +#define MEMP_NUM_UDP_PCB 16 /* -------------------------------- @@ -542,9 +566,9 @@ extern unsigned char misc_prof_get_tcp_snd_buf(void); * DHCP_DEBUG: Enable debugging in dhcp.c. */ #define DHCP_DEBUG LWIP_DBG_OFF -#define LWIP_DEBUG 0 +#define LWIP_DEBUG LWIP_DBG_OFF #define TCP_DEBUG LWIP_DBG_OFF -#define ESP_THREAD_SAFE_DEBUG LWIP_DBG_OFF +#define ESP_THREAD_SAFE_DEBUG LWIP_DBG_OFF #define CHECKSUM_CHECK_UDP 0 #define CHECKSUM_CHECK_IP 0 From 4d8ad3c87738eed231c2dec97b713c135b292524 Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Fri, 28 Oct 2016 12:05:42 +0800 Subject: [PATCH 099/149] Fix int wdt iram, fix some fallout of moving panic stuff to esp32 --- components/esp32/Kconfig | 2 +- components/esp32/int_wdt.c | 5 +++-- components/freertos/FreeRTOS-openocd.c | 2 +- components/freertos/Kconfig | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index 9e141529b..3b50dbd2c 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -157,7 +157,7 @@ config ULP_COPROC_RESERVE_MEM choice ESP32_PANIC prompt "Panic handler behaviour" - default FREERTOS_PANIC_PRINT_REBOOT + default ESP32_PANIC_PRINT_REBOOT help If FreeRTOS detects unexpected behaviour or an unhandled exception, the panic handler is invoked. Configure the panic handlers action here. diff --git a/components/esp32/int_wdt.c b/components/esp32/int_wdt.c index 93a4d9fe6..11de8f20d 100644 --- a/components/esp32/int_wdt.c +++ b/components/esp32/int_wdt.c @@ -24,6 +24,7 @@ #include #include "esp_err.h" #include "esp_intr.h" +#include "esp_attr.h" #include "soc/timer_group_struct.h" #include "soc/timer_group_reg.h" @@ -66,7 +67,7 @@ void esp_int_wdt_init() { //Not static; the ISR assembly checks this. bool int_wdt_app_cpu_ticked=false; -void vApplicationTickHook(void) { +void IRAM_ATTR vApplicationTickHook(void) { if (xPortGetCoreID()!=0) { int_wdt_app_cpu_ticked=true; } else { @@ -82,7 +83,7 @@ void vApplicationTickHook(void) { } } #else -void vApplicationTickHook(void) { +void IRAM_ATTR vApplicationTickHook(void) { if (xPortGetCoreID()!=0) return; TIMERG1.wdt_wprotect=TIMG_WDT_WKEY_VALUE; TIMERG1.wdt_config2=CONFIG_INT_WDT_TIMEOUT_MS*2; //Set timeout before interrupt diff --git a/components/freertos/FreeRTOS-openocd.c b/components/freertos/FreeRTOS-openocd.c index 6177f0205..d74564495 100644 --- a/components/freertos/FreeRTOS-openocd.c +++ b/components/freertos/FreeRTOS-openocd.c @@ -18,6 +18,6 @@ #define USED #endif -#ifdef CONFIG_FREERTOS_DEBUG_OCDAWARE +#ifdef CONFIG_ESP32_DEBUG_OCDAWARE const int USED uxTopUsedPriority = configMAX_PRIORITIES - 1; #endif \ No newline at end of file diff --git a/components/freertos/Kconfig b/components/freertos/Kconfig index 413c710d2..25d5581e8 100644 --- a/components/freertos/Kconfig +++ b/components/freertos/Kconfig @@ -121,7 +121,7 @@ endchoice config FREERTOS_BREAK_ON_SCHEDULER_START_JTAG bool "Stop program on scheduler start when JTAG/OCD is detected" - depends on FREERTOS_DEBUG_OCDAWARE + depends on ESP32_DEBUG_OCDAWARE default y help If JTAG/OCD is connected, stop execution when the scheduler is started and the first From 38ff616e4a46650869fff52441c327dfa129932c Mon Sep 17 00:00:00 2001 From: Liu Zhi Fu Date: Fri, 28 Oct 2016 12:29:26 +0800 Subject: [PATCH 100/149] lwip: add prompt when configure max sockets number in menuconfig --- components/lwip/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/lwip/Kconfig b/components/lwip/Kconfig index 715d7dd46..a957c1fdb 100644 --- a/components/lwip/Kconfig +++ b/components/lwip/Kconfig @@ -1,7 +1,7 @@ menu "LWIP" config LWIP_MAX_SOCKETS - int "Max number of open sockets" + int "Max number of open sockets, the valid value is from 1 to 16" range 1 16 default 4 help From 9e7bc900c51dd31545222dca993e18c2964cb060 Mon Sep 17 00:00:00 2001 From: Liu Zhi Fu Date: Fri, 28 Oct 2016 13:35:06 +0800 Subject: [PATCH 101/149] lwip: rework comments according to review --- components/lwip/Kconfig | 5 +++-- components/lwip/include/lwip/port/lwipopts.h | 4 ---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/components/lwip/Kconfig b/components/lwip/Kconfig index a957c1fdb..15c94c66b 100644 --- a/components/lwip/Kconfig +++ b/components/lwip/Kconfig @@ -1,13 +1,14 @@ menu "LWIP" config LWIP_MAX_SOCKETS - int "Max number of open sockets, the valid value is from 1 to 16" + int "Max number of open sockets" range 1 16 default 4 help Sockets take up a certain amount of memory, and allowing fewer sockets to be open at the same time conserves memory. Specify - the maximum amount of sockets here. + the maximum amount of sockets here. The valid value is from 1 + to 16. config LWIP_THREAD_LOCAL_STORAGE_INDEX int "Index for thread-local-storage pointer for lwip" diff --git a/components/lwip/include/lwip/port/lwipopts.h b/components/lwip/include/lwip/port/lwipopts.h index e72279067..35c2800ed 100755 --- a/components/lwip/include/lwip/port/lwipopts.h +++ b/components/lwip/include/lwip/port/lwipopts.h @@ -95,10 +95,6 @@ extern unsigned long os_random(void); ---------- Internal Memory Pool Sizes ---------- ------------------------------------------------ */ -/** - * MEMP_NUM_TCP_PCB: the number of simulatenously active TCP connections. - * (requires the LWIP_TCP option) - */ /** * MEMP_NUM_NETCONN: the number of struct netconns. From a038a2b5335f2b330eb32748f1950add28b85947 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Fri, 28 Oct 2016 13:41:07 +0800 Subject: [PATCH 102/149] freertos: fix calling first task hook --- components/freertos/tasks.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/components/freertos/tasks.c b/components/freertos/tasks.c index 64cc3a65d..bd32f834f 100644 --- a/components/freertos/tasks.c +++ b/components/freertos/tasks.c @@ -1018,6 +1018,11 @@ static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB, TaskFunction_t pxTaskCode if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 ) { +#if portFIRST_TASK_HOOK + if ( xPortGetCoreID() == 0 ) { + vPortFirstTaskHook(pxTaskCode); + } +#endif /* configFIRST_TASK_HOOK */ /* This is the first task to be created so do the preliminary initialisation required. We will not recover if this call fails, but we will report the failure. */ @@ -1044,12 +1049,6 @@ static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB, TaskFunction_t pxTaskCode /* Schedule if nothing is scheduled yet, or overwrite a task of lower prio. */ if ( pxCurrentTCB[i] == NULL || pxCurrentTCB[i]->uxPriority <= pxNewTCB->uxPriority ) { -#if portFIRST_TASK_HOOK - if ( i == 0) { - vPortFirstTaskHook(pxTaskCode); - } -#endif /* configFIRST_TASK_HOOK */ - pxCurrentTCB[i] = pxNewTCB; break; } From 309bd12855b15b55a3226733c9a25cd09033cb65 Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Fri, 28 Oct 2016 14:32:11 +0800 Subject: [PATCH 103/149] Re-add panic.o to IRAM/DRAM. --- components/esp32/ld/esp32.common.ld | 3 ++- components/esp32/panic.c | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/components/esp32/ld/esp32.common.ld b/components/esp32/ld/esp32.common.ld index 2226e9882..7b14b6da1 100644 --- a/components/esp32/ld/esp32.common.ld +++ b/components/esp32/ld/esp32.common.ld @@ -47,6 +47,7 @@ SECTIONS _iram_text_start = ABSOLUTE(.); *(.iram1 .iram1.*) *libfreertos.a:(.literal .text .literal.* .text.*) + *libesp32.a:panic.o(.literal .text .literal.* .text.*) *libphy.a:(.literal .text .literal.* .text.*) *librtc.a:(.literal .text .literal.* .text.*) *libpp.a:(.literal .text .literal.* .text.*) @@ -92,7 +93,7 @@ SECTIONS KEEP(*(.gnu.linkonce.s2.*)) KEEP(*(.jcr)) *(.dram1 .dram1.*) - *libfreertos.a:panic.o(.rodata .rodata.*) + *libesp32.a:panic.o(.rodata .rodata.*) _data_end = ABSOLUTE(.); . = ALIGN(4); _heap_start = ABSOLUTE(.); diff --git a/components/esp32/panic.c b/components/esp32/panic.c index c806dace2..c5d8aa669 100644 --- a/components/esp32/panic.c +++ b/components/esp32/panic.c @@ -30,7 +30,7 @@ #include "esp_gdbstub.h" #include "esp_panic.h" - +#include "esp_attr.h" /* Panic handlers; these get called when an unhandled exception occurs or the assembly-level @@ -38,6 +38,10 @@ task switching / interrupt code runs into an unrecoverable error. The default ta overflow handler also is in here. */ +/* +Note: The linker script will put everything in this file in IRAM/DRAM, so it also works with flash cache disabled. +*/ + #if !CONFIG_ESP32_PANIC_SILENT_REBOOT //printf may be broken, so we fix our own printing fns... inline static void panicPutchar(char c) { From 3cd86d6bce502b41248b6fb8bccaeb1a4ee47c7d Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Fri, 28 Oct 2016 14:37:36 +0800 Subject: [PATCH 104/149] Also call tick hook on app cpu when scheduler is suspended --- components/freertos/tasks.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/freertos/tasks.c b/components/freertos/tasks.c index bd32f834f..e4b887273 100644 --- a/components/freertos/tasks.c +++ b/components/freertos/tasks.c @@ -2308,7 +2308,7 @@ BaseType_t xSwitchRequired = pdFALSE; { /* Guard against the tick hook being called when the pended tick count is being unwound (when the scheduler is being unlocked). */ - if( uxPendedTicks == ( UBaseType_t ) 0U ) + if( ( uxSchedulerSuspended[ xPortGetCoreID() ] != ( UBaseType_t ) pdFALSE ) || uxPendedTicks == ( UBaseType_t ) 0U ) { vApplicationTickHook(); } From 0e90983c9f0aaf1caf003b92d2f0471c92a389f0 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Fri, 28 Oct 2016 16:16:12 +0800 Subject: [PATCH 105/149] vfs: fix adding CR --- components/vfs/vfs_uart.c | 1 + 1 file changed, 1 insertion(+) diff --git a/components/vfs/vfs_uart.c b/components/vfs/vfs_uart.c index bfccad896..d9d755f9b 100644 --- a/components/vfs/vfs_uart.c +++ b/components/vfs/vfs_uart.c @@ -18,6 +18,7 @@ #include "sys/errno.h" #include "sys/lock.h" #include "soc/uart_struct.h" +#include "sdkconfig.h" static uart_dev_t* s_uarts[3] = {&UART0, &UART1, &UART2}; static _lock_t s_uart_locks[3]; // per-UART locks, lazily initialized From 5ffd6155f28eb906212238868fe242e0aeaa6da4 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Fri, 28 Oct 2016 16:17:41 +0800 Subject: [PATCH 106/149] set default interrupt watchdog timeout to 300ms 10ms is too low for openocd/gdb to work, so it's not a very useful default value. --- components/esp32/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index 3b50dbd2c..b5a8d2f2d 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -206,7 +206,7 @@ config INT_WDT config INT_WDT_TIMEOUT_MS int "Interrupt watchdog timeout (ms)" depends on INT_WDT - default 10 + default 300 range 10 10000 help The timeout of the watchdog, in miliseconds. Make this higher than the FreeRTOS tick rate. From 4f2719236fc49a7f15ef636da6369c4dd52ae7c7 Mon Sep 17 00:00:00 2001 From: Liu Zhi Fu Date: Fri, 28 Oct 2016 16:53:49 +0800 Subject: [PATCH 107/149] esp32/tcpip_adapter: softap supports max 10 stations The max number of stations softap supports is modified from 8 to 10 --- components/esp32/include/esp_wifi_types.h | 4 ++-- components/esp32/lib | 2 +- components/tcpip_adapter/include/tcpip_adapter.h | 4 ++-- components/tcpip_adapter/tcpip_adapter_lwip.c | 1 + 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/components/esp32/include/esp_wifi_types.h b/components/esp32/include/esp_wifi_types.h index 0ea719a65..3304a8a3e 100644 --- a/components/esp32/include/esp_wifi_types.h +++ b/components/esp32/include/esp_wifi_types.h @@ -154,10 +154,10 @@ typedef struct { uint8_t mac[6]; /**< mac address of sta that associated with ESP32 soft-AP */ }wifi_sta_info_t; -#define ESP_WIFI_MAX_CONN_NUM (8+2) /**< max number of sta the eSP32 soft-AP can connect */ +#define ESP_WIFI_MAX_CONN_NUM (10) /**< max number of sta the ESP32 soft-AP can connect */ typedef struct { wifi_sta_info_t sta[ESP_WIFI_MAX_CONN_NUM]; /**< station list */ - uint8_t num; /**< number of station that associated with ESP32 soft-AP */ + int num; /**< number of station that associated with ESP32 soft-AP */ }wifi_sta_list_t; typedef enum { diff --git a/components/esp32/lib b/components/esp32/lib index 12b3435fc..9d18fd1a8 160000 --- a/components/esp32/lib +++ b/components/esp32/lib @@ -1 +1 @@ -Subproject commit 12b3435fc0cd04efc249d52d71efb1cdecda50f8 +Subproject commit 9d18fd1a8f7610130e69f8be74ec68f6399221b1 diff --git a/components/tcpip_adapter/include/tcpip_adapter.h b/components/tcpip_adapter/include/tcpip_adapter.h index 218325320..e84701688 100644 --- a/components/tcpip_adapter/include/tcpip_adapter.h +++ b/components/tcpip_adapter/include/tcpip_adapter.h @@ -73,8 +73,8 @@ typedef struct { }tcpip_adapter_sta_info_t; typedef struct { - tcpip_adapter_sta_info_t sta[ESP_WIFI_MAX_CONN_NUM+2]; - uint8_t num; + tcpip_adapter_sta_info_t sta[ESP_WIFI_MAX_CONN_NUM]; + int num; }tcpip_adapter_sta_list_t; #endif diff --git a/components/tcpip_adapter/tcpip_adapter_lwip.c b/components/tcpip_adapter/tcpip_adapter_lwip.c index 677368008..9b6e9d94f 100644 --- a/components/tcpip_adapter/tcpip_adapter_lwip.c +++ b/components/tcpip_adapter/tcpip_adapter_lwip.c @@ -598,6 +598,7 @@ esp_err_t tcpip_adapter_get_sta_list(wifi_sta_list_t *wifi_sta_list, tcpip_adapt return ESP_ERR_TCPIP_ADAPTER_INVALID_PARAMS; memset(tcpip_sta_list, 0, sizeof(tcpip_adapter_sta_list_t)); + tcpip_sta_list->num = wifi_sta_list->num; for (i=0; inum; i++){ memcpy(tcpip_sta_list->sta[i].mac, wifi_sta_list->sta[i].mac, 6); dhcp_search_ip_on_mac(tcpip_sta_list->sta[i].mac, &tcpip_sta_list->sta[i].ip); From 90b787636a49fd2f36c6c30e2a57c06303afb20e Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Mon, 31 Oct 2016 11:00:27 +0800 Subject: [PATCH 108/149] Remove redundant volatile keyword --- components/esp32/crosscore_int.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp32/crosscore_int.c b/components/esp32/crosscore_int.c index 78287d405..60a45da40 100644 --- a/components/esp32/crosscore_int.c +++ b/components/esp32/crosscore_int.c @@ -44,7 +44,7 @@ ToDo: There is a small chance the CPU already has yielded when this ISR is servi the ISR will cause it to switch _away_ from it. portYIELD_FROM_ISR will probably just schedule the task again, but have to check that. */ static void esp_crosscore_isr(void *arg) { - volatile uint32_t myReasonVal; + uint32_t myReasonVal; #if 0 //A pointer to the correct reason array item is passed to this ISR. volatile uint32_t *myReason=arg; From f30887bc700aff63f88362dca8e0fb3557ada7f9 Mon Sep 17 00:00:00 2001 From: Liu Zhi Fu Date: Mon, 31 Oct 2016 11:17:33 +0800 Subject: [PATCH 109/149] modify comments according to review --- components/esp32/include/esp_wifi_types.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp32/include/esp_wifi_types.h b/components/esp32/include/esp_wifi_types.h index 3304a8a3e..fb19aa8fc 100644 --- a/components/esp32/include/esp_wifi_types.h +++ b/components/esp32/include/esp_wifi_types.h @@ -154,7 +154,7 @@ typedef struct { uint8_t mac[6]; /**< mac address of sta that associated with ESP32 soft-AP */ }wifi_sta_info_t; -#define ESP_WIFI_MAX_CONN_NUM (10) /**< max number of sta the ESP32 soft-AP can connect */ +#define ESP_WIFI_MAX_CONN_NUM (10) /**< max number of stations which can connect to ESP32 soft-AP */ typedef struct { wifi_sta_info_t sta[ESP_WIFI_MAX_CONN_NUM]; /**< station list */ int num; /**< number of station that associated with ESP32 soft-AP */ From a5552b1e21b725bc17882e2b44452d194b56f97a Mon Sep 17 00:00:00 2001 From: Liu Zhi Fu Date: Mon, 31 Oct 2016 17:50:09 +0800 Subject: [PATCH 110/149] lwip: fix tcp rx abnormal issue(tw8242) In tcp_alloc(), initialize per_soc_tcp_wnd before initializing recv_wnd because recv_wnd depends on per_soc_tcp_wnd. --- components/lwip/core/tcp.c | 11 ++++++----- components/lwip/core/tcp_out.c | 4 ++-- components/lwip/include/lwip/port/lwipopts.h | 20 -------------------- 3 files changed, 8 insertions(+), 27 deletions(-) diff --git a/components/lwip/core/tcp.c b/components/lwip/core/tcp.c index 87ddf5f1a..627df6d29 100755 --- a/components/lwip/core/tcp.c +++ b/components/lwip/core/tcp.c @@ -1532,6 +1532,12 @@ tcp_alloc(u8_t prio) } if (pcb != NULL) { memset(pcb, 0, sizeof(struct tcp_pcb)); + +#if ESP_PER_SOC_TCP_WND + pcb->per_soc_tcp_wnd = TCP_WND_DEFAULT; + pcb->per_soc_tcp_snd_buf = TCP_SND_BUF_DEFAULT; +#endif + pcb->prio = prio; pcb->snd_buf = TCP_SND_BUF_DEFAULT; pcb->snd_queuelen = 0; @@ -1575,11 +1581,6 @@ tcp_alloc(u8_t prio) #endif /* LWIP_TCP_KEEPALIVE */ pcb->keep_cnt_sent = 0; - -#if ESP_PER_SOC_TCP_WND - pcb->per_soc_tcp_wnd = TCP_WND_DEFAULT; - pcb->per_soc_tcp_snd_buf = TCP_SND_BUF_DEFAULT; -#endif } return pcb; diff --git a/components/lwip/core/tcp_out.c b/components/lwip/core/tcp_out.c index f189623f5..35a8aa145 100755 --- a/components/lwip/core/tcp_out.c +++ b/components/lwip/core/tcp_out.c @@ -1320,9 +1320,9 @@ tcp_rst(u32_t seqno, u32_t ackno, #endif #else #if LWIP_WND_SCALE - tcphdr->wnd = PP_HTONS(((TCP_WND >> TCP_RCV_SCALE) & 0xFFFF)); + tcphdr->wnd = PP_HTONS(((TCP_WND_DEFAULT >> TCP_RCV_SCALE) & 0xFFFF)); #else - tcphdr->wnd = PP_HTONS(TCP_WND); + tcphdr->wnd = PP_HTONS(TCP_WND_DEFAULT); #endif #endif tcphdr->chksum = 0; diff --git a/components/lwip/include/lwip/port/lwipopts.h b/components/lwip/include/lwip/port/lwipopts.h index 35c2800ed..b8811813d 100755 --- a/components/lwip/include/lwip/port/lwipopts.h +++ b/components/lwip/include/lwip/port/lwipopts.h @@ -240,26 +240,6 @@ extern unsigned long os_random(void); ---------- TCP options ---------- --------------------------------- */ -/** - * TCP_WND: The size of a TCP window. This must be at least - * (2 * TCP_MSS) for things to work well - */ - -#define ESP_PER_SOC_TCP_WND 1 -#if ESP_PER_SOC_TCP_WND -#define TCP_WND_DEFAULT (4*TCP_MSS) -#define TCP_SND_BUF_DEFAULT (2*TCP_MSS) - -#define TCP_WND(pcb) (pcb->per_soc_tcp_wnd) -#define TCP_SND_BUF(pcb) (pcb->per_soc_tcp_snd_buf) -#else -#ifdef PERF -extern unsigned char misc_prof_get_tcpw(void); -extern unsigned char misc_prof_get_tcp_snd_buf(void); -#define TCP_WND(pcb) (misc_prof_get_tcpw()*TCP_MSS) -#define TCP_SND_BUF(pcb) (misc_prof_get_tcp_snd_buf()*TCP_MSS) -#endif -#endif /** From 2119b9846962c2b2d961fa13c26662a2be711e19 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 31 Oct 2016 19:08:56 +0800 Subject: [PATCH 111/149] spi_flash: remove unnecessary src pointer alignment check in spi_flash_write ROM SPIWrite routine can work with unaligned sources, so this check is unnecessary. Furthermore, it breaks nvs_set_str and nvs_get_blob when data pointer is unaligned. Also fix stray backslash in COUNTER_STOP macro --- components/spi_flash/flash_ops.c | 6 +----- components/spi_flash/include/esp_spi_flash.h | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/components/spi_flash/flash_ops.c b/components/spi_flash/flash_ops.c index ae72568aa..134e1fe65 100644 --- a/components/spi_flash/flash_ops.c +++ b/components/spi_flash/flash_ops.c @@ -39,7 +39,7 @@ static spi_flash_counters_t s_flash_stats; #define COUNTER_STOP(counter) \ do{ \ s_flash_stats.counter.count++; \ - s_flash_stats.counter.time += (xthal_get_ccount() - ts_begin) / (XT_CLOCK_FREQ / 1000000); \\ + s_flash_stats.counter.time += (xthal_get_ccount() - ts_begin) / (XT_CLOCK_FREQ / 1000000); \ } while(0) #define COUNTER_ADD_BYTES(counter, size) \ @@ -126,10 +126,6 @@ esp_err_t IRAM_ATTR spi_flash_erase_range(uint32_t start_addr, uint32_t size) esp_err_t IRAM_ATTR spi_flash_write(size_t dest_addr, const void *src, size_t size) { - // TODO: replace this check with code which deals with unaligned sources - if (((ptrdiff_t) src) % 4 != 0) { - return ESP_ERR_INVALID_ARG; - } // Destination alignment is also checked in ROM code, but we can give // better error code here // TODO: add handling of unaligned destinations diff --git a/components/spi_flash/include/esp_spi_flash.h b/components/spi_flash/include/esp_spi_flash.h index c65eaa583..840bbc497 100644 --- a/components/spi_flash/include/esp_spi_flash.h +++ b/components/spi_flash/include/esp_spi_flash.h @@ -74,7 +74,7 @@ esp_err_t spi_flash_erase_range(size_t start_addr, size_t size); /** * @brief Write data to Flash. * - * @note Both des_addr and src_addr have to be 4-byte aligned. + * @note Address in flash, dest, has to be 4-byte aligned. * This is a temporary limitation which will be removed. * * @param dest destination address in Flash @@ -88,7 +88,7 @@ esp_err_t spi_flash_write(size_t dest, const void *src, size_t size); /** * @brief Read data from Flash. * - * @note Both des_addr and src_addr have to be 4-byte aligned. + * @note Both src and dest have to be 4-byte aligned. * This is a temporary limitation which will be removed. * * @param src source address of the data in Flash. From 269332f473b6e3b2960f53605dbac840ecedd60d Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 31 Oct 2016 19:11:40 +0800 Subject: [PATCH 112/149] nvs_flash: use CRC-32 routine compatible with ROM version Host tests used different flavour of CRC-32, which made it impossible to load NVS partition dumps created on the chip --- components/nvs_flash/test/crc.cpp | 64 ++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/components/nvs_flash/test/crc.cpp b/components/nvs_flash/test/crc.cpp index 288b58a32..4cbb9be9e 100644 --- a/components/nvs_flash/test/crc.cpp +++ b/components/nvs_flash/test/crc.cpp @@ -14,25 +14,51 @@ #include #include -extern "C" unsigned long crc32_le(unsigned long crc_in, unsigned char const* data, unsigned int length) -{ - uint32_t i; - bool bit; - uint8_t c; - uint32_t crc = (uint32_t) crc_in; +static const unsigned int crc32_le_table[256] = { + 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, 0x706af48fL, 0xe963a535L, 0x9e6495a3L, + 0x0edb8832L, 0x79dcb8a4L, 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, 0x90bf1d91L, + 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, + 0x136c9856L, 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, 0xfa0f3d63L, 0x8d080df5L, + 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, + 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, + 0x26d930acL, 0x51de003aL, 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, 0xb8bda50fL, + 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, + 0x76dc4190L, 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, 0x9fbfe4a5L, 0xe8b8d433L, + 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, + 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, + 0x65b0d9c6L, 0x12b7e950L, 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, 0xfbd44c65L, + 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, + 0x4369e96aL, 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, 0xaa0a4c5fL, 0xdd0d7cc9L, + 0x5005713cL, 0x270241aaL, 0xbe0b1010L, 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, + 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, + + 0xedb88320L, 0x9abfb3b6L, 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, 0x73dc1683L, + 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, + 0xf00f9344L, 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, 0x196c3671L, 0x6e6b06e7L, + 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, + 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, + 0xd80d2bdaL, 0xaf0a1b4cL, 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, 0x4669be79L, + 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, + 0xc5ba3bbeL, 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, 0x2cd99e8bL, 0x5bdeae1dL, + 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, + 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, + 0x86d3d2d4L, 0xf1d4e242L, 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, 0x18b74777L, + 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, + 0xa00ae278L, 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, 0x4969474dL, 0x3e6e77dbL, + 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, + 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, 0xcdd70693L, 0x54de5729L, 0x23d967bfL, + 0xb3667a2eL, 0xc4614ab8L, 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, 0x2d02ef8dL +}; - while (length--) { - c = *data++; - for (i = 0x80; i > 0; i >>= 1) { - bit = crc & 0x80000000; - if (c & i) { - bit = !bit; - } - crc <<= 1; - if (bit) { - crc ^= 0x04c11db7; - } - } + + +extern "C" unsigned int crc32_le(unsigned int crc, unsigned char const * buf,unsigned int len) +{ + unsigned int i; + crc = ~crc; + for(i=0;i>8); } - return crc; + return ~crc; } + From 413f2c00f69ff6ce9777eab366280eb82cb3ac35 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 31 Oct 2016 19:17:25 +0800 Subject: [PATCH 113/149] nvs_flash: introduce write failures after each word written Previously the test bench would check failure recovery by introducing error after each write operation. This makes checks a bit more extensive (and much longer) by failing after every word written. Surprisingly, this change didn't expose any bugs. --- .../nvs_flash/test/spi_flash_emulation.h | 8 +++---- components/nvs_flash/test/test_nvs.cpp | 22 ++++++++++--------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/components/nvs_flash/test/spi_flash_emulation.h b/components/nvs_flash/test/spi_flash_emulation.h index ba50c4f9e..4e544a39e 100644 --- a/components/nvs_flash/test/spi_flash_emulation.h +++ b/components/nvs_flash/test/spi_flash_emulation.h @@ -74,11 +74,11 @@ public: return false; } - if (mFailCountdown != SIZE_MAX && mFailCountdown-- == 0) { - return false; - } - for (size_t i = 0; i < size / 4; ++i) { + if (mFailCountdown != SIZE_MAX && mFailCountdown-- == 0) { + return false; + } + uint32_t sv = src[i]; size_t pos = dstAddr / 4 + i; uint32_t& dv = mData[pos]; diff --git a/components/nvs_flash/test/test_nvs.cpp b/components/nvs_flash/test/test_nvs.cpp index ce552578d..223e5dea9 100644 --- a/components/nvs_flash/test/test_nvs.cpp +++ b/components/nvs_flash/test/test_nvs.cpp @@ -894,7 +894,7 @@ TEST_CASE("test recovery from sudden poweroff", "[.][long][nvs][recovery][monkey size_t totalOps = 0; int lastPercent = -1; - for (uint32_t errDelay = 4; ; ++errDelay) { + for (uint32_t errDelay = 0; ; ++errDelay) { INFO(errDelay); emu.randomize(seed); emu.clearStats(); @@ -903,23 +903,25 @@ TEST_CASE("test recovery from sudden poweroff", "[.][long][nvs][recovery][monkey if (totalOps != 0) { int percent = errDelay * 100 / totalOps; - if (percent != lastPercent) { + if (percent > lastPercent) { printf("%d/%d (%d%%)\r\n", errDelay, static_cast(totalOps), percent); lastPercent = percent; } } - TEST_ESP_OK(nvs_flash_init_custom(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN)); nvs_handle handle; - TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle)); - size_t count = iter_count; - if(test.doRandomThings(handle, gen, count) != ESP_ERR_FLASH_OP_FAIL) { - nvs_close(handle); - break; + + if (nvs_flash_init_custom(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) == ESP_OK) { + if (nvs_open("namespace1", NVS_READWRITE, &handle) == ESP_OK) { + if(test.doRandomThings(handle, gen, count) != ESP_ERR_FLASH_OP_FAIL) { + nvs_close(handle); + break; + } + nvs_close(handle); + } } - nvs_close(handle); TEST_ESP_OK(nvs_flash_init_custom(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN)); TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle)); @@ -929,7 +931,7 @@ TEST_CASE("test recovery from sudden poweroff", "[.][long][nvs][recovery][monkey CHECK(0); } nvs_close(handle); - totalOps = emu.getEraseOps() + emu.getWriteOps(); + totalOps = emu.getEraseOps() + emu.getWriteBytes() / 4; } } From 19f61332a9a9c5c0d585d88d2bef7475594724a2 Mon Sep 17 00:00:00 2001 From: Liu Zhi Fu Date: Mon, 31 Oct 2016 19:38:47 +0800 Subject: [PATCH 114/149] make build pass when disable per soc tcp window --- components/lwip/core/init.c | 22 ++++++++++---------- components/lwip/include/lwip/port/lwipopts.h | 5 +++-- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/components/lwip/core/init.c b/components/lwip/core/init.c index 774e9a2be..8b2e92669 100755 --- a/components/lwip/core/init.c +++ b/components/lwip/core/init.c @@ -135,7 +135,7 @@ //#endif #else /* LWIP_WND_SCALE */ -#if (ESP_PER_SOC_TCP_WND == 0) +#if ! ESP_PER_SOC_TCP_WND #if (LWIP_TCP && (TCP_WND > 0xffff)) #error "If you want to use TCP, TCP_WND must fit in an u16_t, so, you have to reduce it in your lwipopts.h (or enable window scaling)" #endif @@ -143,11 +143,11 @@ #endif /* LWIP_WND_SCALE */ -#if (ESP_PER_SOC_TCP_WND == 0) -#if (LWIP_TCP && (TCP_SND_QUEUELEN > 0xffff)) +#if ! ESP_PER_SOC_TCP_WND +#if (LWIP_TCP && (TCP_SND_QUEUELEN(0) > 0xffff)) #error "If you want to use TCP, TCP_SND_QUEUELEN must fit in an u16_t, so, you have to reduce it in your lwipopts.h" #endif -#if (LWIP_TCP && (TCP_SND_QUEUELEN < 2)) +#if (LWIP_TCP && (TCP_SND_QUEUELEN(0) < 2)) #error "TCP_SND_QUEUELEN must be at least 2 for no-copy TCP writes to work" #endif @@ -286,25 +286,25 @@ /* TCP sanity checks */ #if !LWIP_DISABLE_TCP_SANITY_CHECKS +#if ! ESP_PER_SOC_TCP_WND #if LWIP_TCP -#if !MEMP_MEM_MALLOC && (MEMP_NUM_TCP_SEG < TCP_SND_QUEUELEN) +#if !MEMP_MEM_MALLOC && (MEMP_NUM_TCP_SEG < TCP_SND_QUEUELEN(0)) #error "lwip_sanity_check: WARNING: MEMP_NUM_TCP_SEG should be at least as big as TCP_SND_QUEUELEN. If you know what you are doing, define LWIP_DISABLE_TCP_SANITY_CHECKS to 1 to disable this error." #endif -#if (ESP_PER_SOC_TCP_WND == 0) -#if TCP_SND_BUF < (2 * TCP_MSS) +#if TCP_SND_BUF(0) < (2 * TCP_MSS) #error "lwip_sanity_check: WARNING: TCP_SND_BUF must be at least as much as (2 * TCP_MSS) for things to work smoothly. If you know what you are doing, define LWIP_DISABLE_TCP_SANITY_CHECKS to 1 to disable this error." #endif -#if TCP_SND_QUEUELEN < (2 * (TCP_SND_BUF / TCP_MSS)) - #error "lwip_sanity_check: WARNING: TCP_SND_QUEUELEN must be at least as much as (2 * TCP_SND_BUF/TCP_MSS) for things to work. If you know what you are doing, define LWIP_DISABLE_TCP_SANITY_CHECKS to 1 to disable this error." +#if TCP_SND_QUEUELEN(0) < (2 * (TCP_SND_BUF(0) / TCP_MSS)) + #error "lwip_sanity_check: WARNING: TCP_SND_QUEUELEN must be at least as much as (2 * TCP_SND_BUF(0)/TCP_MSS) for things to work. If you know what you are doing, define LWIP_DISABLE_TCP_SANITY_CHECKS to 1 to disable this error." #endif -#if TCP_SNDLOWAT >= TCP_SND_BUF +#if TCP_SNDLOWAT >= TCP_SND_BUF(0) #error "lwip_sanity_check: WARNING: TCP_SNDLOWAT must be less than TCP_SND_BUF. If you know what you are doing, define LWIP_DISABLE_TCP_SANITY_CHECKS to 1 to disable this error." #endif #if TCP_SNDLOWAT >= (0xFFFF - (4 * TCP_MSS)) #error "lwip_sanity_check: WARNING: TCP_SNDLOWAT must at least be 4*MSS below u16_t overflow!" #endif -#if TCP_SNDQUEUELOWAT >= TCP_SND_QUEUELEN +#if TCP_SNDQUEUELOWAT >= TCP_SND_QUEUELEN(0) #error "lwip_sanity_check: WARNING: TCP_SNDQUEUELOWAT must be less than TCP_SND_QUEUELEN. If you know what you are doing, define LWIP_DISABLE_TCP_SANITY_CHECKS to 1 to disable this error." #endif #endif diff --git a/components/lwip/include/lwip/port/lwipopts.h b/components/lwip/include/lwip/port/lwipopts.h index b8811813d..2e11bdd5c 100755 --- a/components/lwip/include/lwip/port/lwipopts.h +++ b/components/lwip/include/lwip/port/lwipopts.h @@ -512,7 +512,7 @@ extern unsigned long os_random(void); /* Enable all Espressif-only options */ #define ESP_LWIP 1 -#define ESP_PER_SOC_TCP_WND 1 +#define ESP_PER_SOC_TCP_WND 0 #define ESP_THREAD_SAFE 1 #define ESP_THREAD_SAFE_DEBUG LWIP_DBG_OFF #define ESP_DHCP 1 @@ -524,9 +524,10 @@ extern unsigned long os_random(void); #define ESP_LIGHT_SLEEP 1 -#if ESP_PER_SOC_TCP_WND #define TCP_WND_DEFAULT (4*TCP_MSS) #define TCP_SND_BUF_DEFAULT (2*TCP_MSS) + +#if ESP_PER_SOC_TCP_WND #define TCP_WND(pcb) (pcb->per_soc_tcp_wnd) #define TCP_SND_BUF(pcb) (pcb->per_soc_tcp_snd_buf) #else From 2b722ea468ee69693d5e728101e7b4e36c66f231 Mon Sep 17 00:00:00 2001 From: Liu Zhi Fu Date: Mon, 31 Oct 2016 19:43:18 +0800 Subject: [PATCH 115/149] turn on per socket tcp window by default --- components/lwip/include/lwip/port/lwipopts.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/lwip/include/lwip/port/lwipopts.h b/components/lwip/include/lwip/port/lwipopts.h index 2e11bdd5c..67a62b822 100755 --- a/components/lwip/include/lwip/port/lwipopts.h +++ b/components/lwip/include/lwip/port/lwipopts.h @@ -512,7 +512,7 @@ extern unsigned long os_random(void); /* Enable all Espressif-only options */ #define ESP_LWIP 1 -#define ESP_PER_SOC_TCP_WND 0 +#define ESP_PER_SOC_TCP_WND 1 #define ESP_THREAD_SAFE 1 #define ESP_THREAD_SAFE_DEBUG LWIP_DBG_OFF #define ESP_DHCP 1 From d9cdc7de5811a450919518b5ea8d6a8edd0a960f Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 31 Oct 2016 19:48:28 +0800 Subject: [PATCH 116/149] nvs_flash: don't allow more operation to be done on page in PageState::INVALID Currently a restart is required to recover a page from invalid state. The long-term solution is to detect such a condition and recover automatically (without a restart). This will be implemented in a separate change set. --- components/nvs_flash/src/nvs_page.cpp | 26 +++++--- .../nvs_flash/test/spi_flash_emulation.h | 12 ++++ components/nvs_flash/test/test_nvs.cpp | 62 +++++++++++++++++++ 3 files changed, 93 insertions(+), 7 deletions(-) diff --git a/components/nvs_flash/src/nvs_page.cpp b/components/nvs_flash/src/nvs_page.cpp index fae1f6f1b..a10b88c97 100644 --- a/components/nvs_flash/src/nvs_page.cpp +++ b/components/nvs_flash/src/nvs_page.cpp @@ -3,7 +3,7 @@ // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at - +// // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software @@ -131,8 +131,12 @@ esp_err_t Page::writeEntryData(const uint8_t* data, size_t size) esp_err_t Page::writeItem(uint8_t nsIndex, ItemType datatype, const char* key, const void* data, size_t dataSize) { Item item; - esp_err_t err; + + if (mState == PageState::INVALID) { + return ESP_ERR_NVS_INVALID_STATE; + } + if (mState == PageState::UNINITIALIZED) { err = initialize(); if (err != ESP_OK) { @@ -166,7 +170,6 @@ esp_err_t Page::writeItem(uint8_t nsIndex, ItemType datatype, const char* key, c } // write first item - size_t span = (totalSize + ENTRY_SIZE - 1) / ENTRY_SIZE; item = Item(nsIndex, datatype, span, key); mHashList.insert(item, mNextFreeEntry); @@ -215,6 +218,11 @@ esp_err_t Page::readItem(uint8_t nsIndex, ItemType datatype, const char* key, vo { size_t index = 0; Item item; + + if (mState == PageState::INVALID) { + return ESP_ERR_NVS_INVALID_STATE; + } + esp_err_t rc = findItem(nsIndex, datatype, key, index, item); if (rc != ESP_OK) { return rc; @@ -478,6 +486,8 @@ esp_err_t Page::mLoadEntryTable() mState = PageState::INVALID; return err; } + + mHashList.insert(item, i); if (item.crc32 != item.calculateCrc32()) { err = eraseEntryAndSpan(i); @@ -488,8 +498,6 @@ esp_err_t Page::mLoadEntryTable() continue; } - mHashList.insert(item, i); - if (item.datatype != ItemType::BLOB && item.datatype != ItemType::SZ) { continue; } @@ -785,8 +793,12 @@ void Page::debugDump() const Item item; readEntry(i, item); if (skip == 0) { - printf("W ns=%2u type=%2u span=%3u key=\"%s\"\n", item.nsIndex, static_cast(item.datatype), item.span, item.key); - skip = item.span - 1; + printf("W ns=%2u type=%2u span=%3u key=\"%s\" len=%d\n", item.nsIndex, static_cast(item.datatype), item.span, item.key, (item.span != 1)?((int)item.varLength.dataSize):-1); + if (item.span > 0 && item.span <= ENTRY_COUNT - i) { + skip = item.span - 1; + } else { + skip = 0; + } } else { printf("D\n"); skip--; diff --git a/components/nvs_flash/test/spi_flash_emulation.h b/components/nvs_flash/test/spi_flash_emulation.h index 4e544a39e..14e56bab6 100644 --- a/components/nvs_flash/test/spi_flash_emulation.h +++ b/components/nvs_flash/test/spi_flash_emulation.h @@ -141,6 +141,18 @@ public: { return reinterpret_cast(mData.data()); } + + void load(const char* filename) + { + FILE* f = fopen(filename, "rb"); + fseek(f, 0, SEEK_END); + off_t size = ftell(f); + assert(size % SPI_FLASH_SEC_SIZE == 0); + mData.resize(size); + fseek(f, 0, SEEK_SET); + auto s = fread(mData.data(), SPI_FLASH_SEC_SIZE, size / SPI_FLASH_SEC_SIZE, f); + assert(s == static_cast(size / SPI_FLASH_SEC_SIZE)); + } void clearStats() { diff --git a/components/nvs_flash/test/test_nvs.cpp b/components/nvs_flash/test/test_nvs.cpp index 223e5dea9..b70801f84 100644 --- a/components/nvs_flash/test/test_nvs.cpp +++ b/components/nvs_flash/test/test_nvs.cpp @@ -953,6 +953,68 @@ TEST_CASE("test for memory leaks in open/set", "[leaks]") } } +TEST_CASE("duplicate items are removed", "[nvs][dupes]") +{ + SpiFlashEmulator emu(3); + { + // create one item + nvs::Page p; + p.load(0); + p.writeItem(1, "opmode", 3); + } + { + // add another one without deleting the first one + nvs::Item item(1, ItemType::U8, 1, "opmode"); + item.data[0] = 2; + item.crc32 = item.calculateCrc32(); + emu.write(3 * 32, reinterpret_cast(&item), sizeof(item)); + uint32_t mask = 0xFFFFFFFA; + emu.write(32, &mask, 4); + } + { + // load page and check that second item persists + nvs::Page p; + p.load(0); + uint8_t val; + p.readItem(1, "opmode", val); + CHECK(val == 2); + CHECK(p.getErasedEntryCount() == 1); + CHECK(p.getUsedEntryCount() == 1); + } +} + +TEST_CASE("recovery after failure to write data", "[nvs]") +{ + SpiFlashEmulator emu(3); + // TODO: remove explicit alignment + const char str[] __attribute__((aligned(4))) = "value 0123456789abcdef012345678value 0123456789abcdef012345678"; + + // make flash write fail exactly in Page::writeEntryData + emu.failAfter(17); + { + Storage storage; + TEST_ESP_OK(storage.init(0, 3)); + + TEST_ESP_ERR(storage.writeItem(1, ItemType::SZ, "key", str, strlen(str)), ESP_ERR_FLASH_OP_FAIL); + + // check that repeated operations cause an error + TEST_ESP_ERR(storage.writeItem(1, ItemType::SZ, "key", str, strlen(str)), ESP_ERR_NVS_INVALID_STATE); + + uint8_t val; + TEST_ESP_ERR(storage.readItem(1, ItemType::U8, "key", &val, sizeof(val)), ESP_ERR_NVS_NOT_FOUND); + } + { + // load page and check that data was erased + Page p; + p.load(0); + CHECK(p.getErasedEntryCount() == 3); + CHECK(p.getUsedEntryCount() == 0); + + // try to write again + TEST_ESP_OK(p.writeItem(1, ItemType::SZ, "key", str, strlen(str))); + } +} + TEST_CASE("dump all performance data", "[nvs]") { std::cout << "====================" << std::endl << "Dumping benchmarks" << std::endl; From abea6c50f15f74c43f467a1179166ad031443b0a Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 31 Oct 2016 21:10:47 +0800 Subject: [PATCH 117/149] nvs_flash: delete all duplicate entries in a page while loading Due to previous flash write bug it was possible to create multiple duplicate entries in a single page. Recovery logic detected that case and bailed out with an assert. This change adds graceful recovery from this condition. Tests included. --- components/nvs_flash/src/nvs_page.cpp | 40 +++++++++++------- components/nvs_flash/test/test_nvs.cpp | 56 ++++++++++++++++++++++---- 2 files changed, 73 insertions(+), 23 deletions(-) diff --git a/components/nvs_flash/src/nvs_page.cpp b/components/nvs_flash/src/nvs_page.cpp index a10b88c97..23cefd1aa 100644 --- a/components/nvs_flash/src/nvs_page.cpp +++ b/components/nvs_flash/src/nvs_page.cpp @@ -301,6 +301,8 @@ esp_err_t Page::eraseEntryAndSpan(size_t index) } if (item.calculateCrc32() != item.crc32) { rc = alterEntryState(index, EntryState::ERASED); + --mUsedEntryCount; + ++mErasedEntryCount; if (rc != ESP_OK) { return rc; } @@ -473,7 +475,9 @@ esp_err_t Page::mLoadEntryTable() if (end > ENTRY_COUNT) { end = ENTRY_COUNT; } - for (size_t i = 0; i < end; ++i) { + size_t span; + for (size_t i = 0; i < end; i += span) { + span = 1; if (mEntryTable.get(i) == EntryState::ERASED) { lastItemIndex = INVALID_ENTRY; continue; @@ -488,6 +492,9 @@ esp_err_t Page::mLoadEntryTable() } mHashList.insert(item, i); + + // search for potential duplicate item + size_t duplicateIndex = mHashList.find(0, item); if (item.crc32 != item.calculateCrc32()) { err = eraseEntryAndSpan(i); @@ -498,23 +505,26 @@ esp_err_t Page::mLoadEntryTable() continue; } - if (item.datatype != ItemType::BLOB && item.datatype != ItemType::SZ) { - continue; - } - - size_t span = item.span; - bool needErase = false; - for (size_t j = i; j < i + span; ++j) { - if (mEntryTable.get(j) != EntryState::WRITTEN) { - needErase = true; - lastItemIndex = INVALID_ENTRY; - break; + + if (item.datatype == ItemType::BLOB || item.datatype == ItemType::SZ) { + span = item.span; + bool needErase = false; + for (size_t j = i; j < i + span; ++j) { + if (mEntryTable.get(j) != EntryState::WRITTEN) { + needErase = true; + lastItemIndex = INVALID_ENTRY; + break; + } + } + if (needErase) { + eraseEntryAndSpan(i); + continue; } } - if (needErase) { - eraseEntryAndSpan(i); + + if (duplicateIndex < i) { + eraseEntryAndSpan(duplicateIndex); } - i += span - 1; } // check that last item is not duplicate diff --git a/components/nvs_flash/test/test_nvs.cpp b/components/nvs_flash/test/test_nvs.cpp index b70801f84..528c9df68 100644 --- a/components/nvs_flash/test/test_nvs.cpp +++ b/components/nvs_flash/test/test_nvs.cpp @@ -963,22 +963,27 @@ TEST_CASE("duplicate items are removed", "[nvs][dupes]") p.writeItem(1, "opmode", 3); } { - // add another one without deleting the first one + // add another two without deleting the first one nvs::Item item(1, ItemType::U8, 1, "opmode"); item.data[0] = 2; item.crc32 = item.calculateCrc32(); emu.write(3 * 32, reinterpret_cast(&item), sizeof(item)); - uint32_t mask = 0xFFFFFFFA; + emu.write(4 * 32, reinterpret_cast(&item), sizeof(item)); + uint32_t mask = 0xFFFFFFEA; emu.write(32, &mask, 4); } { // load page and check that second item persists - nvs::Page p; - p.load(0); + nvs::Storage s; + s.init(0, 3); uint8_t val; - p.readItem(1, "opmode", val); + ESP_ERROR_CHECK(s.readItem(1, "opmode", val)); CHECK(val == 2); - CHECK(p.getErasedEntryCount() == 1); + } + { + Page p; + p.load(0); + CHECK(p.getErasedEntryCount() == 2); CHECK(p.getUsedEntryCount() == 1); } } @@ -986,8 +991,7 @@ TEST_CASE("duplicate items are removed", "[nvs][dupes]") TEST_CASE("recovery after failure to write data", "[nvs]") { SpiFlashEmulator emu(3); - // TODO: remove explicit alignment - const char str[] __attribute__((aligned(4))) = "value 0123456789abcdef012345678value 0123456789abcdef012345678"; + const char str[] = "value 0123456789abcdef012345678value 0123456789abcdef012345678"; // make flash write fail exactly in Page::writeEntryData emu.failAfter(17); @@ -1015,6 +1019,42 @@ TEST_CASE("recovery after failure to write data", "[nvs]") } } +TEST_CASE("crc error in variable length item is handled", "[nvs]") +{ + SpiFlashEmulator emu(3); + const uint64_t before_val = 0xbef04e; + const uint64_t after_val = 0xaf7e4; + // write some data + { + Page p; + p.load(0); + TEST_ESP_OK(p.writeItem(0, "before", before_val)); + const char* str = "foobar"; + TEST_ESP_OK(p.writeItem(0, ItemType::SZ, "key", str, strlen(str))); + TEST_ESP_OK(p.writeItem(0, "after", after_val)); + } + // corrupt some data + uint32_t w; + CHECK(emu.read(&w, 32 * 3 + 8, sizeof(w))); + w &= 0xf000000f; + CHECK(emu.write(32 * 3 + 8, &w, sizeof(w))); + // load and check + { + Page p; + p.load(0); + CHECK(p.getUsedEntryCount() == 2); + CHECK(p.getErasedEntryCount() == 2); + + uint64_t val; + TEST_ESP_OK(p.readItem(0, "before", val)); + CHECK(val == before_val); + TEST_ESP_ERR(p.findItem(0, ItemType::SZ, "key"), ESP_ERR_NVS_NOT_FOUND); + TEST_ESP_OK(p.readItem(0, "after", val)); + CHECK(val == after_val); + } +} + + TEST_CASE("dump all performance data", "[nvs]") { std::cout << "====================" << std::endl << "Dumping benchmarks" << std::endl; From 92b663d9f214180486f46c5c6926f518b345083b Mon Sep 17 00:00:00 2001 From: Liu Zhi Fu Date: Mon, 31 Oct 2016 21:26:33 +0800 Subject: [PATCH 118/149] lwip: optimize tx flow control 1. Remove tx flow control for TCP 2. Remove tx flow control for UDP temporary 3. Return the error code when call esp_wifi_internal_tx() --- components/lwip/api/sockets.c | 5 +++-- components/lwip/port/netif/wlanif.c | 7 ++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/components/lwip/api/sockets.c b/components/lwip/api/sockets.c index 455d007ea..df658578a 100755 --- a/components/lwip/api/sockets.c +++ b/components/lwip/api/sockets.c @@ -395,12 +395,15 @@ static void lwip_socket_drop_registered_memberships(int s); */ static inline void esp32_tx_flow_ctrl(void) { +//TODO we need to do flow control for UDP +#if 0 uint8_t _wait_delay = 1; while ((system_get_free_heap_size() < HEAP_HIGHWAT) || esp_wifi_internal_tx_is_stop()){ vTaskDelay(_wait_delay/portTICK_RATE_MS); if (_wait_delay < 64) _wait_delay *= 2; } +#endif } #else @@ -1208,8 +1211,6 @@ lwip_send(int s, const void *data, size_t size, int flags) #endif /* (LWIP_UDP || LWIP_RAW) */ } - esp32_tx_flow_ctrl(); - write_flags = NETCONN_COPY | ((flags & MSG_MORE) ? NETCONN_MORE : 0) | ((flags & MSG_DONTWAIT) ? NETCONN_DONTBLOCK : 0); diff --git a/components/lwip/port/netif/wlanif.c b/components/lwip/port/netif/wlanif.c index 548bb7f97..ffad69cd4 100755 --- a/components/lwip/port/netif/wlanif.c +++ b/components/lwip/port/netif/wlanif.c @@ -142,16 +142,13 @@ low_level_output(struct netif *netif, struct pbuf *p) } } - esp_wifi_internal_tx(wifi_if, q->payload, pbuf_x_len); - return ERR_OK; - + return esp_wifi_internal_tx(wifi_if, q->payload, pbuf_x_len); #else for(q = p; q != NULL; q = q->next) { esp_wifi_internal_tx(wifi_if, q->payload, q->len); } -#endif - return ERR_OK; +#endif } /** From 41514f497aa5965a7b8b238392205d11c6c8805b Mon Sep 17 00:00:00 2001 From: Krzysztof Date: Sun, 23 Oct 2016 22:12:34 +0200 Subject: [PATCH 119/149] Renamed .md to .rst --- CONTRIBUTING.md => CONTRIBUTING.rst | 15 ++++++++++----- README.md => README.rst | 29 ++++++++++++++++++++--------- 2 files changed, 30 insertions(+), 14 deletions(-) rename CONTRIBUTING.md => CONTRIBUTING.rst (91%) rename README.md => README.rst (86%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.rst similarity index 91% rename from CONTRIBUTING.md rename to CONTRIBUTING.rst index b0af761d5..a6e759eb1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.rst @@ -1,12 +1,15 @@ -# Contributions Guide +Contributions Guide +=================== We welcome contributions to the esp-idf project! -## How to Contribute +How to Contribute +----------------- Contributions to esp-idf - fixing bugs, adding features, adding documentation - are welcome. We accept contributions via [Github Pull Requests](https://help.github.com/articles/about-pull-requests/). -## Before Contributing +Before Contributing +------------------- Before sending us a Pull Request, please consider this list of points: @@ -24,7 +27,8 @@ Before sending us a Pull Request, please consider this list of points: * If you're unsure about any of these points, please open the Pull Request anyhow and then ask us for feedback. -## Pull Request Process +Pull Request Process +-------------------- After you open the Pull Request, there will probably be some discussion in the comments field of the request itself. @@ -32,6 +36,7 @@ Once the Pull Request is ready to merge, it will first be merged into our intern If this process passes, it will be merged onto the public github repository. -## Legal Part +Legal Part +---------- Before a contribution can be accepted, you will need to sign our [Contributor Agreement](docs/contributor-agreement.rst). You will be prompted for this automatically as part of the Pull Request process. diff --git a/README.md b/README.rst similarity index 86% rename from README.md rename to README.rst index ff645c339..65d616767 100644 --- a/README.md +++ b/README.rst @@ -1,6 +1,10 @@ -# Using Espressif IoT Development Framework with the ESP32 +Using Espressif IoT Development Framework with the ESP32 +======================================================== -# Setting Up ESP-IDF +|docs| + +Setting Up ESP-IDF +------------------ In the [docs](docs) directory you will find per-platform setup guides: @@ -8,23 +12,27 @@ In the [docs](docs) directory you will find per-platform setup guides: * [Mac OS Setup Guide](docs/macos-setup.rst) * [Linux Setup Guide](docs/linux-setup.rst) -# Finding A Project +Finding A Project +----------------- As well as the [esp-idf-template](https://github.com/espressif/esp-idf-template) project mentioned in the setup guide, esp-idf comes with some example projects in the [examples](examples) directory. Once you've found the project you want to work with, change to its directory and you can configure and build it: -# Configuring your project +Configuring your project +------------------------ `make menuconfig` -# Compiling your project +Compiling your project +---------------------- `make all` ... will compile app, bootloader and generate a partition table based on the config. -# Flashing your project +Flashing your project +--------------------- When `make all` finishes, it will print a command line to use esptool.py to flash the chip. However you can also do this from make by running: @@ -34,7 +42,8 @@ This will flash the entire project (app, bootloader and partition table) to a ne You don't need to run `make all` before running `make flash`, `make flash` will automatically rebuild anything which needs it. -# Compiling & Flashing Just the App +Compiling & Flashing Just the App +--------------------------------- After the initial flash, you may just want to build and flash just your app, not the bootloader and partition table: @@ -45,7 +54,8 @@ After the initial flash, you may just want to build and flash just your app, not (There's no downside to reflashing the bootloader and partition table each time, if they haven't changed.) -# The Partition Table +The Partition Table +------------------- Once you've compiled your project, the "build" directory will contain a binary file with a name like "my_app.bin". This is an ESP32 image binary that can be loaded by the bootloader. @@ -62,7 +72,8 @@ In both cases the factory app is flashed at offset 0x10000. If you `make partiti For more details about partition tables and how to create custom variations, view the `docs/partition_tables.rst` file. -# Resources +Resources +--------- * The [docs directory of the esp-idf repository](docs) contains esp-idf documentation. From ec99397c3ea057903fba8cf12e1360df0727baae Mon Sep 17 00:00:00 2001 From: Krzysztof Date: Sun, 23 Oct 2016 22:14:24 +0200 Subject: [PATCH 120/149] Initial Sphinx / ReadTheDocs.org configuration --- docs/Doxyfile | 12 ++ docs/Makefile | 177 +++++++++++++++++++++++++++ docs/_static/1.png | Bin 0 -> 11412 bytes docs/conf.py | 270 ++++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 88 ++++++++++++++ docs/requirements.txt | 1 + 6 files changed, 548 insertions(+) create mode 100644 docs/Doxyfile create mode 100644 docs/Makefile create mode 100644 docs/_static/1.png create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 docs/requirements.txt diff --git a/docs/Doxyfile b/docs/Doxyfile new file mode 100644 index 000000000..795bfa5e8 --- /dev/null +++ b/docs/Doxyfile @@ -0,0 +1,12 @@ +PROJECT_NAME = "ESP32 Programming Guide" +XML_OUTPUT = xml +GENERATE_LATEX = NO +GENERATE_MAN = NO +GENERATE_RTF = NO +CASE_SENSE_NAMES = NO +INPUT = ../components/driver/include +RECURSIVE = YES +QUIET = YES +JAVADOC_AUTOBRIEF = YES +GENERATE_HTML = NO +GENERATE_XML = YES \ No newline at end of file diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 000000000..c04268ff1 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,177 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ReadtheDocsTemplate.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ReadtheDocsTemplate.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/ReadtheDocsTemplate" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ReadtheDocsTemplate" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/_static/1.png b/docs/_static/1.png new file mode 100644 index 0000000000000000000000000000000000000000..4920892781f90c3db323f82033866c3f7116c073 GIT binary patch literal 11412 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 i-AuB$6Izt`{{a9CH0GJi2@$^l0000 v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'ReadtheDocsTemplatedoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ('index', 'ReadtheDocsTemplate.tex', u'Read the Docs Template Documentation', + u'Read the Docs', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'readthedocstemplate', u'Read the Docs Template Documentation', + [u'Read the Docs'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'ReadtheDocsTemplate', u'Read the Docs Template Documentation', + u'Read the Docs', 'ReadtheDocsTemplate', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 000000000..f74a3acae --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,88 @@ +.. Read the Docs Template documentation master file + +Welcome to ESP32 Programming Guide +================================== + + +Example C functions +------------------- + +.. c:function:: esp_err_t esp_wifi_get_ap_list (uint16_t *number, wifi_ap_list_t *ap_list) + +.. c:function:: esp_err_t esp_wifi_set_protocol (wifi_interface_t ifx, uint8_t protocol_bitmap) + + +Example C function integration +------------------------------ + +.. doxygenfunction:: esp_wifi_init +.. doxygenfunction:: esp_wifi_set_config + +.. doxygenfunction:: gpio_isr_register +.. doxygenfunction:: ledc_timer_set + + +Example C enum integration +-------------------------- + +.. doxygenenum:: wifi_auth_mode_t + + +Example C struct integration +---------------------------- + +.. doxygenstruct:: wifi_scan_config_t + :members: + + +Contents: + +.. About - TBA + +.. toctree:: + :caption: Toolchain Setup + :maxdepth: 1 + + windows-setup + linux-setup + macos-setup + eclipse-setup + +.. API Reference - TBA + +.. Technical Reference - TBA + +.. toctree:: + :caption: Debugging + :maxdepth: 1 + + openocd + +.. Resources - TBA + +.. toctree:: + :caption: Contribute + :maxdepth: 1 + + contributing + contributor-agreement + +.. toctree:: + :caption: Copyrights and Licenses + :maxdepth: 1 + + COPYRIGHT + +.. toctree:: + :caption: Flapping Documents + :maxdepth: 1 + + partition-tables + build_system + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`search` diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 000000000..188f51e62 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1 @@ +breathe \ No newline at end of file From 99fc7600888ae76252772ac544aed3a8399c1de6 Mon Sep 17 00:00:00 2001 From: Krzysztof Date: Sun, 23 Oct 2016 22:15:13 +0200 Subject: [PATCH 121/149] Link to file a folder up --- docs/contributing.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/contributing.rst diff --git a/docs/contributing.rst b/docs/contributing.rst new file mode 100644 index 000000000..3bdd7dc21 --- /dev/null +++ b/docs/contributing.rst @@ -0,0 +1 @@ +.. include:: ../CONTRIBUTING.rst \ No newline at end of file From cb70ac831f3430175cbb8f230e48ce2c8b164f28 Mon Sep 17 00:00:00 2001 From: Krzysztof Date: Sun, 23 Oct 2016 22:25:28 +0200 Subject: [PATCH 122/149] Increased buikd coverage of DoxyGen --- docs/Doxyfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Doxyfile b/docs/Doxyfile index 795bfa5e8..23f6f1a2d 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -4,7 +4,7 @@ GENERATE_LATEX = NO GENERATE_MAN = NO GENERATE_RTF = NO CASE_SENSE_NAMES = NO -INPUT = ../components/driver/include +INPUT = ../components RECURSIVE = YES QUIET = YES JAVADOC_AUTOBRIEF = YES From 6435a71de2b9b9e5a3c731f630cf0dc8a33953ac Mon Sep 17 00:00:00 2001 From: Krzysztof Date: Sun, 23 Oct 2016 22:31:15 +0200 Subject: [PATCH 123/149] Reduced the build coverage by DoxyGen --- docs/Doxyfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Doxyfile b/docs/Doxyfile index 23f6f1a2d..795bfa5e8 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -4,7 +4,7 @@ GENERATE_LATEX = NO GENERATE_MAN = NO GENERATE_RTF = NO CASE_SENSE_NAMES = NO -INPUT = ../components +INPUT = ../components/driver/include RECURSIVE = YES QUIET = YES JAVADOC_AUTOBRIEF = YES From 4900c63071a88883b327aedc754bcae0bcc6fd62 Mon Sep 17 00:00:00 2001 From: Krzysztof Date: Sun, 23 Oct 2016 22:59:53 +0200 Subject: [PATCH 124/149] Added ReadTheDocs.org badges --- README.rst | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 65d616767..9711e2294 100644 --- a/README.rst +++ b/README.rst @@ -15,7 +15,9 @@ In the [docs](docs) directory you will find per-platform setup guides: Finding A Project ----------------- -As well as the [esp-idf-template](https://github.com/espressif/esp-idf-template) project mentioned in the setup guide, esp-idf comes with some example projects in the [examples](examples) directory. +As well as the esp-idf-template_ project mentioned in the setup guide, esp-idf comes with some example projects in the [examples](examples) directory. + +.. _esp-idf-template: https://github.com/espressif/esp-idf-template Once you've found the project you want to work with, change to its directory and you can configure and build it: @@ -82,3 +84,9 @@ Resources * [Check the Issues section on github](https://github.com/espressif/esp-idf/issues) if you find a bug or have a feature request. Please check existing Issues before opening a new one. * If you're interested in contributing to esp-idf, please check the [CONTRIBUTING.md](CONTRIBUTING.md) file. + + +.. |docs| image:: https://readthedocs.org/projects/docs/badge/?version=latest + :alt: Documentation Status + :scale: 100% + :target: http://esp-idf.readthedocs.io/en/latest/?badge=latest \ No newline at end of file From 6ce1af58984751a3dafc3d5111e8bbedffdf7aa3 Mon Sep 17 00:00:00 2001 From: Krzysztof Date: Mon, 24 Oct 2016 20:43:13 +0200 Subject: [PATCH 125/149] Set up of theme for local builds --- docs/conf.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 7c4614d45..5ba76d7f2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -20,14 +20,16 @@ import os # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) -# run doxygen +# -- Run DoxyGen to prepare XML for Sphinx--------------------------------- +# ref. https://github.com/rtfd/readthedocs.org/issues/388 # -# for implementation on readthedocs.org check -# https://github.com/rtfd/readthedocs.org/issues/388 +# added by krzychb, 24-Oct-2016 # + from subprocess import call call('doxygen') + # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. @@ -268,3 +270,18 @@ texinfo_documents = [ # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False + +# -- Use sphinx_rtd_theme for local builds -------------------------------- +# ref. https://github.com/snide/sphinx_rtd_theme#using-this-theme-locally-then-building-on-read-the-docs +# +# added by krzychb, 24-Oct-2016 +# +# on_rtd is whether we are on readthedocs.org +on_rtd = os.environ.get('READTHEDOCS', None) == 'True' + +if not on_rtd: # only import and set the theme if we're building docs locally + import sphinx_rtd_theme + html_theme = 'sphinx_rtd_theme' + html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + +# otherwise, readthedocs.org uses their theme by default, so no need to specify it From 8ae97a285551a07046e7b80a5fb4de157fb36faf Mon Sep 17 00:00:00 2001 From: Krzysztof Date: Mon, 24 Oct 2016 20:44:57 +0200 Subject: [PATCH 126/149] Initial list of Wi-Fi API --- docs/COPYRIGHT.rst | 6 ++- docs/Doxyfile | 4 +- docs/api/esp_wifi.rst | 59 ++++++++++++++++++++++++ docs/api/example.rst | 41 +++++++++++++++++ docs/build_system.rst | 2 +- docs/contributor-agreement.rst | 2 +- docs/eclipse-setup.rst | 3 ++ docs/index.rst | 82 ++++++++++++++-------------------- docs/linux-setup.rst | 3 ++ docs/macos-setup.rst | 3 ++ docs/openocd.rst | 3 ++ docs/partition-tables.rst | 5 ++- docs/windows-setup.rst | 7 ++- 13 files changed, 162 insertions(+), 58 deletions(-) create mode 100644 docs/api/esp_wifi.rst create mode 100644 docs/api/example.rst diff --git a/docs/COPYRIGHT.rst b/docs/COPYRIGHT.rst index da5f5b204..67b3d9bf1 100644 --- a/docs/COPYRIGHT.rst +++ b/docs/COPYRIGHT.rst @@ -1,3 +1,6 @@ +Copyrights and Licenses +*********************** + Software Copyrights =================== @@ -87,8 +90,7 @@ developments under license policy of following terms. Copyright (C) 2011, ChaN, all right reserved. * The TJpgDec module is a free software and there is NO WARRANTY. -* No restriction on use. You can use, modify and redistribute it for -personal, non-profit or commercial products UNDER YOUR RESPONSIBILITY. +* No restriction on use. You can use, modify and redistribute it for personal, non-profit or commercial products UNDER YOUR RESPONSIBILITY. * Redistributions of source code must retain the above copyright notice. diff --git a/docs/Doxyfile b/docs/Doxyfile index 795bfa5e8..9c8fb81df 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -4,9 +4,9 @@ GENERATE_LATEX = NO GENERATE_MAN = NO GENERATE_RTF = NO CASE_SENSE_NAMES = NO -INPUT = ../components/driver/include +INPUT = ../components/esp32/include/esp_wifi.h RECURSIVE = YES QUIET = YES JAVADOC_AUTOBRIEF = YES GENERATE_HTML = NO -GENERATE_XML = YES \ No newline at end of file +GENERATE_XML = YES diff --git a/docs/api/esp_wifi.rst b/docs/api/esp_wifi.rst new file mode 100644 index 000000000..48b4db204 --- /dev/null +++ b/docs/api/esp_wifi.rst @@ -0,0 +1,59 @@ +Wi-Fi API +========= + +Macros +------ + +.. doxygendefine:: WIFI_INIT_CONFIG_DEFAULT + + +Typedefs +-------- + +.. doxygentypedef:: wifi_promiscuous_cb_t +.. doxygentypedef:: wifi_rxcb_t +.. doxygentypedef:: esp_vendor_ie_cb_t + + +Functions +--------- + +.. doxygenfunction:: esp_wifi_init +.. doxygenfunction:: esp_wifi_deinit +.. doxygenfunction:: esp_wifi_set_mode +.. doxygenfunction:: esp_wifi_get_mode +.. doxygenfunction:: esp_wifi_start +.. doxygenfunction:: esp_wifi_stop +.. doxygenfunction:: esp_wifi_connect +.. doxygenfunction:: esp_wifi_disconnect +.. doxygenfunction:: esp_wifi_clear_fast_connect +.. doxygenfunction:: esp_wifi_kick_station +.. doxygenfunction:: esp_wifi_scan_start +.. doxygenfunction:: esp_wifi_scan_stop +.. doxygenfunction:: esp_wifi_get_ap_num +.. doxygenfunction:: esp_wifi_get_ap_list +.. doxygenfunction:: esp_wifi_set_ps +.. doxygenfunction:: esp_wifi_get_ps +.. doxygenfunction:: esp_wifi_set_protocol +.. doxygenfunction:: esp_wifi_get_protocol +.. doxygenfunction:: esp_wifi_set_bandwidth +.. doxygenfunction:: esp_wifi_get_bandwidth +.. doxygenfunction:: esp_wifi_set_channel +.. doxygenfunction:: esp_wifi_get_channel +.. doxygenfunction:: esp_wifi_set_country +.. doxygenfunction:: esp_wifi_get_country +.. doxygenfunction:: esp_wifi_set_mac +.. doxygenfunction:: esp_wifi_get_mac +.. doxygenfunction:: esp_wifi_set_promiscuous_rx_cb +.. doxygenfunction:: esp_wifi_set_promiscuous +.. doxygenfunction:: esp_wifi_get_promiscuous +.. doxygenfunction:: esp_wifi_set_config +.. doxygenfunction:: esp_wifi_get_config +.. doxygenfunction:: esp_wifi_get_station_list +.. doxygenfunction:: esp_wifi_free_station_list +.. doxygenfunction:: esp_wifi_set_storage +.. doxygenfunction:: esp_wifi_reg_rxcb +.. doxygenfunction:: esp_wifi_set_auto_connect +.. doxygenfunction:: esp_wifi_get_auto_connect +.. doxygenfunction:: esp_wifi_set_vendor_ie +.. doxygenfunction:: esp_wifi_set_vendor_ie_cb diff --git a/docs/api/example.rst b/docs/api/example.rst new file mode 100644 index 000000000..88ecb4601 --- /dev/null +++ b/docs/api/example.rst @@ -0,0 +1,41 @@ +Example Visualizations +====================== + +Function prototpe +----------------- + +.. c:function:: esp_err_t esp_wifi_get_ap_list (uint16_t *number, wifi_ap_list_t *ap_list) +.. c:function:: esp_err_t esp_wifi_set_protocol (wifi_interface_t ifx, uint8_t protocol_bitmap) + + +Function definition +------------------- + +Wi-Fi +^^^^^ +.. doxygenfunction:: esp_wifi_init +.. doxygenfunction:: esp_wifi_set_config + +GPIO +^^^^ +.. doxygenfunction:: gpio_isr_register + +Led Control +^^^^^^^^^^^ + +.. doxygenfunction:: ledc_timer_set + + +Enum definition +--------------- + +.. doxygenenum:: wifi_auth_mode_t + + +Struct definition +----------------- + +.. doxygenstruct:: wifi_scan_config_t + :members: + + diff --git a/docs/build_system.rst b/docs/build_system.rst index 4df65b1b5..34db487e0 100644 --- a/docs/build_system.rst +++ b/docs/build_system.rst @@ -1,5 +1,5 @@ Build System ------------- +************ This document explains the Espressif IoT Development Framework build system and the concept of "components" diff --git a/docs/contributor-agreement.rst b/docs/contributor-agreement.rst index 7c194e772..de294740c 100644 --- a/docs/contributor-agreement.rst +++ b/docs/contributor-agreement.rst @@ -1,5 +1,5 @@ Contributor Agreement ---------------------- +===================== Individual Contributor Non-Exclusive License Agreement ------------------------------------------------------ diff --git a/docs/eclipse-setup.rst b/docs/eclipse-setup.rst index 21d83a7f0..32d60a17a 100644 --- a/docs/eclipse-setup.rst +++ b/docs/eclipse-setup.rst @@ -1,3 +1,6 @@ +Build and Falsh with Eclipse IDE +******************************** + Installing Eclipse IDE ====================== diff --git a/docs/index.rst b/docs/index.rst index f74a3acae..30e981e8e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,54 +1,41 @@ .. Read the Docs Template documentation master file -Welcome to ESP32 Programming Guide -================================== - - -Example C functions -------------------- - -.. c:function:: esp_err_t esp_wifi_get_ap_list (uint16_t *number, wifi_ap_list_t *ap_list) - -.. c:function:: esp_err_t esp_wifi_set_protocol (wifi_interface_t ifx, uint8_t protocol_bitmap) - - -Example C function integration ------------------------------- - -.. doxygenfunction:: esp_wifi_init -.. doxygenfunction:: esp_wifi_set_config - -.. doxygenfunction:: gpio_isr_register -.. doxygenfunction:: ledc_timer_set - - -Example C enum integration --------------------------- - -.. doxygenenum:: wifi_auth_mode_t - - -Example C struct integration ----------------------------- - -.. doxygenstruct:: wifi_scan_config_t - :members: - +ESP32 Programming Guide +======================= Contents: -.. About - TBA - .. toctree:: - :caption: Toolchain Setup + :caption: Setup Toolchain :maxdepth: 1 - windows-setup - linux-setup - macos-setup - eclipse-setup + Windows + Linux + Mac OS -.. API Reference - TBA +.. Configure - TBA + +.. Connect - TBA + +.. toctree:: + :caption: Build and Flash + :maxdepth: 1 + + Eclipse IDE + +.. toctree:: + :caption: Tweak + :maxdepth: 1 + + partition-tables + build_system + +.. toctree:: + :caption: API Reference + :maxdepth: 1 + + Wi-Fi + api/example .. Technical Reference - TBA @@ -68,17 +55,14 @@ Contents: contributor-agreement .. toctree:: - :caption: Copyrights and Licenses + :caption: Legal :maxdepth: 1 COPYRIGHT -.. toctree:: - :caption: Flapping Documents - :maxdepth: 1 - - partition-tables - build_system +.. About - TBA + + Indices and tables diff --git a/docs/linux-setup.rst b/docs/linux-setup.rst index 13e1b3a9c..cf5e78b63 100644 --- a/docs/linux-setup.rst +++ b/docs/linux-setup.rst @@ -1,3 +1,6 @@ +Set up of Toolchain for Linux +***************************** + Step 0: Prerequisites ===================== diff --git a/docs/macos-setup.rst b/docs/macos-setup.rst index 8178a17ad..53c6fe54c 100644 --- a/docs/macos-setup.rst +++ b/docs/macos-setup.rst @@ -1,3 +1,6 @@ +Set up of Toolchain for Mac OS +****************************** + Step 0: Prerequisites ===================== diff --git a/docs/openocd.rst b/docs/openocd.rst index 57ee93db4..6a6f50e3f 100644 --- a/docs/openocd.rst +++ b/docs/openocd.rst @@ -1,3 +1,6 @@ +Debugging +========= + OpenOCD setup for ESP32 ----------------------- diff --git a/docs/partition-tables.rst b/docs/partition-tables.rst index e0a39126b..88597532d 100644 --- a/docs/partition-tables.rst +++ b/docs/partition-tables.rst @@ -1,5 +1,8 @@ Partition Tables ----------------- +================ + +Overview +-------- A single ESP32's flash can contain multiple apps, as well as many different kinds of data (calibration data, filesystems, parameter storage, etc). For this reason a partition table is flashed to offset 0x4000 in the flash. diff --git a/docs/windows-setup.rst b/docs/windows-setup.rst index baea1ac40..a425f5b3a 100644 --- a/docs/windows-setup.rst +++ b/docs/windows-setup.rst @@ -1,5 +1,8 @@ -Step 1: Toolchain for Windows: Quick Steps -================================== +Set up of Toolchain for Windows +******************************* + +Step 1: Quick Steps +=================== Windows doesn't have a built-in "make" environment, so as well as installing the toolchain you will need a GNU-compatible environment. We use the MSYS2_ environment to provide. You don't need to use this environment all the time (you can use Eclipse_ or some other front-end), but it runs behind the scenes. From b6a463e94f5886bc953515e8b9a45b7c13a514a2 Mon Sep 17 00:00:00 2001 From: krzychb Date: Tue, 25 Oct 2016 12:09:05 +0200 Subject: [PATCH 127/149] Draft of GPIO API included --- docs/Doxyfile | 25 +++++++++--------- docs/api/gpio.rst | 67 +++++++++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 1 + 3 files changed, 81 insertions(+), 12 deletions(-) create mode 100644 docs/api/gpio.rst diff --git a/docs/Doxyfile b/docs/Doxyfile index 9c8fb81df..8328d44ca 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -1,12 +1,13 @@ -PROJECT_NAME = "ESP32 Programming Guide" -XML_OUTPUT = xml -GENERATE_LATEX = NO -GENERATE_MAN = NO -GENERATE_RTF = NO -CASE_SENSE_NAMES = NO -INPUT = ../components/esp32/include/esp_wifi.h -RECURSIVE = YES -QUIET = YES -JAVADOC_AUTOBRIEF = YES -GENERATE_HTML = NO -GENERATE_XML = YES +PROJECT_NAME = "ESP32 Programming Guide" +XML_OUTPUT = xml +GENERATE_LATEX = NO +GENERATE_MAN = NO +GENERATE_RTF = NO +CASE_SENSE_NAMES = NO +INPUT = ../components/esp32/include/esp_wifi.h ../components/driver/include/driver/gpio.h ../components/esp32/include/rom/gpio.h +RECURSIVE = YES +QUIET = YES +JAVADOC_AUTOBRIEF = YES +GENERATE_HTML = NO +GENERATE_XML = YES +WARN_LOGFILE = "DoxyGenWarningLog.txt" \ No newline at end of file diff --git a/docs/api/gpio.rst b/docs/api/gpio.rst new file mode 100644 index 000000000..11b0e4a46 --- /dev/null +++ b/docs/api/gpio.rst @@ -0,0 +1,67 @@ +GPIO API +======== + +Functions +--------- + +.. doxygenfunction:: gpio_config +.. doxygenfunction:: gpio_set_intr_type +.. doxygenfunction:: gpio_intr_enable +.. doxygenfunction:: gpio_intr_disable +.. doxygenfunction:: gpio_set_level +.. doxygenfunction:: gpio_get_level +.. doxygenfunction:: gpio_set_direction +.. doxygenfunction:: gpio_set_pull_mode +.. doxygenfunction:: gpio_wakeup_enable +.. doxygenfunction:: gpio_wakeup_disable +.. doxygenfunction:: gpio_isr_register + +*Example code:* + +Configuration of GPIO as an output + +.. code-block:: c + + gpio_config_t io_conf; + io_conf.intr_type = GPIO_INTR_DISABLE; //disable interrupt + io_conf.mode = GPIO_MODE_OUTPUT; //set as output mode + io_conf.pin_bit_mask = GPIO_SEL_18 | GPIO_SEL_19; //bit mask of the pins that you want to set,e.g.GPIO18/19 + io_conf.pull_down_en = 0; //disable pull-down mode + io_conf.pull_up_en = 0; //disable pull-up mode + gpio_config(&io_conf); //configure GPIO with the given settings + +Configuration of GPIO as an input + +.. code-block:: c + + gpio_config_t io_conf; + io_conf.intr_type = GPIO_INTR_POSEDGE; //set posedge interrupt + io_conf.mode = GPIO_MODE_INPUT; //set as input + io_conf.pin_bit_mask = GPIO_SEL_4 | GPIO_SEL_5; //bit mask of the pins that you want to set, e.g.,GPIO4/5 + io_conf.pull_down_en = 0; //disable pull-down mode + io_conf.pull_up_en = 1; //enable pull-up mode + gpio_config(&io_conf); //configure GPIO with the given settings + + +Low level ROM GPIO functions + +.. doxygenfunction:: gpio_init +.. doxygenfunction:: gpio_output_set +.. doxygenfunction:: gpio_output_set_high +.. doxygenfunction:: gpio_input_get +.. doxygenfunction:: gpio_input_get_high +.. doxygenfunction:: gpio_intr_handler_register +.. doxygenfunction:: gpio_intr_pending +.. doxygenfunction:: gpio_intr_pending_high +.. doxygenfunction:: gpio_intr_ack +.. doxygenfunction:: gpio_intr_ack_high +.. doxygenfunction:: gpio_pin_wakeup_enable +.. doxygenfunction:: gpio_pin_wakeup_disable +.. doxygenfunction:: gpio_matrix_in +.. doxygenfunction:: gpio_matrix_out +.. doxygenfunction:: gpio_pad_select_gpio +.. doxygenfunction:: gpio_pad_set_drv +.. doxygenfunction:: gpio_pad_pullup +.. doxygenfunction:: gpio_pad_pulldown +.. doxygenfunction:: gpio_pad_unhold +.. doxygenfunction:: gpio_pad_hold diff --git a/docs/index.rst b/docs/index.rst index 30e981e8e..26b906c36 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -35,6 +35,7 @@ Contents: :maxdepth: 1 Wi-Fi + GPIO api/example .. Technical Reference - TBA From 3584fcfc7cf3a3211c476cb2d02b66360d649bce Mon Sep 17 00:00:00 2001 From: Krzysztof Date: Tue, 25 Oct 2016 21:28:21 +0200 Subject: [PATCH 128/149] Make section --- docs/index.rst | 19 ++++++------- docs/make-project.rst | 63 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 11 deletions(-) create mode 100644 docs/make-project.rst diff --git a/docs/index.rst b/docs/index.rst index 26b906c36..84f37b560 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -21,14 +21,16 @@ Contents: :caption: Build and Flash :maxdepth: 1 + Make Eclipse IDE .. toctree:: - :caption: Tweak + :caption: Want More? :maxdepth: 1 partition-tables build_system + openocd .. toctree:: :caption: API Reference @@ -38,13 +40,10 @@ Contents: GPIO api/example -.. Technical Reference - TBA - .. toctree:: - :caption: Debugging - :maxdepth: 1 + :caption: Technical Reference - openocd + Technical Reference .. Resources - TBA @@ -63,11 +62,9 @@ Contents: .. About - TBA - - - -Indices and tables -================== + +Indices +======= * :ref:`genindex` * :ref:`search` diff --git a/docs/make-project.rst b/docs/make-project.rst new file mode 100644 index 000000000..e72bb81dd --- /dev/null +++ b/docs/make-project.rst @@ -0,0 +1,63 @@ +Build and Flash with Make +========================= + +Finding a project +----------------- + +As well as the `esp-idf-template `_ project mentioned in the setup guide, esp-idf comes with some example projects on github in the `examples `_ directory. + +Once you've found the project you want to work with, change to its directory and you can configure and build it: + +Configuring your project +------------------------ + +`make menuconfig` + +Compiling your project +---------------------- + +`make all` + +... will compile app, bootloader and generate a partition table based on the config. + +Flashing your project +--------------------- + +When `make all` finishes, it will print a command line to use esptool.py to flash the chip. However you can also do this from make by running: + +`make flash` + +This will flash the entire project (app, bootloader and partition table) to a new chip. The settings for serial port flashing can be configured with `make menuconfig`. + +You don't need to run `make all` before running `make flash`, `make flash` will automatically rebuild anything which needs it. + +Compiling & Flashing Just the App +--------------------------------- + +After the initial flash, you may just want to build and flash just your app, not the bootloader and partition table: + +* `make app` - build just the app. +* `make app-flash` - flash just the app. + +`make app-flash` will automatically rebuild the app if it needs it. + +(There's no downside to reflashing the bootloader and partition table each time, if they haven't changed.) + +The Partition Table +------------------- + +Once you've compiled your project, the "build" directory will contain a binary file with a name like "my_app.bin". This is an ESP32 image binary that can be loaded by the bootloader. + +A single ESP32's flash can contain multiple apps, as well as many different kinds of data (calibration data, filesystems, parameter storage, etc). For this reason a partition table is flashed to offset 0x4000 in the flash. + +Each entry in the partition table has a name (label), type (app, data, or something else), subtype and the offset in flash where the partition is loaded. + +The simplest way to use the partition table is to `make menuconfig` and choose one of the simple predefined partition tables: + +* "Single factory app, no OTA" +* "Factory app, two OTA definitions" + +In both cases the factory app is flashed at offset 0x10000. If you `make partition_table` then it will print a summary of the partition table. + +For more details about :doc:`partition tables ` and how to create custom variations, view the :doc:`documentation `. + From 591b7caa9514ab7d2a2c03bf8b42bec490eea86f Mon Sep 17 00:00:00 2001 From: Krzysztof Date: Tue, 25 Oct 2016 21:28:42 +0200 Subject: [PATCH 129/149] Back to .md format --- CONTRIBUTING.rst | 6 +++--- README.rst => README.md | 44 +++++++++++++---------------------------- 2 files changed, 17 insertions(+), 33 deletions(-) rename README.rst => README.md (72%) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index a6e759eb1..ed1c0b92d 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -6,7 +6,7 @@ We welcome contributions to the esp-idf project! How to Contribute ----------------- -Contributions to esp-idf - fixing bugs, adding features, adding documentation - are welcome. We accept contributions via [Github Pull Requests](https://help.github.com/articles/about-pull-requests/). +Contributions to esp-idf - fixing bugs, adding features, adding documentation - are welcome. We accept contributions via `Github Pull Requests `_. Before Contributing ------------------- @@ -23,7 +23,7 @@ Before sending us a Pull Request, please consider this list of points: * Are comments and documentation written in clear English, with no spelling or grammar errors? -* If the contribution contains multiple commits, are they grouped together into logical changes (one major change per pull request)? Are any commits with names like "fixed typo" [squashed into previous commits](http://eli.thegreenplace.net/2014/02/19/squashing-github-pull-requests-into-a-single-commit/)? +* If the contribution contains multiple commits, are they grouped together into logical changes (one major change per pull request)? Are any commits with names like "fixed typo" `squashed into previous commits `_? * If you're unsure about any of these points, please open the Pull Request anyhow and then ask us for feedback. @@ -39,4 +39,4 @@ If this process passes, it will be merged onto the public github repository. Legal Part ---------- -Before a contribution can be accepted, you will need to sign our [Contributor Agreement](docs/contributor-agreement.rst). You will be prompted for this automatically as part of the Pull Request process. +Before a contribution can be accepted, you will need to sign our :doc:`Contributor Agreement `. You will be prompted for this automatically as part of the Pull Request process. diff --git a/README.rst b/README.md similarity index 72% rename from README.rst rename to README.md index 9711e2294..c01e314f1 100644 --- a/README.rst +++ b/README.md @@ -1,10 +1,8 @@ -Using Espressif IoT Development Framework with the ESP32 -======================================================== +# Using Espressif IoT Development Framework with the ESP32 -|docs| +[![alt text](https://readthedocs.org/projects/docs/badge/?version=latest "Documentation Status")](http://esp-idf.readthedocs.io/en/latest/?badge=latest) -Setting Up ESP-IDF ------------------- +# Setting Up ESP-IDF In the [docs](docs) directory you will find per-platform setup guides: @@ -12,29 +10,23 @@ In the [docs](docs) directory you will find per-platform setup guides: * [Mac OS Setup Guide](docs/macos-setup.rst) * [Linux Setup Guide](docs/linux-setup.rst) -Finding A Project ------------------ +# Finding A Project -As well as the esp-idf-template_ project mentioned in the setup guide, esp-idf comes with some example projects in the [examples](examples) directory. - -.. _esp-idf-template: https://github.com/espressif/esp-idf-template +As well as the [esp-idf-template](https://github.com/espressif/esp-idf-template) project mentioned in the setup guide, esp-idf comes with some example projects in the [examples](examples) directory. Once you've found the project you want to work with, change to its directory and you can configure and build it: -Configuring your project ------------------------- +# Configuring your project `make menuconfig` -Compiling your project ----------------------- +# Compiling your project `make all` ... will compile app, bootloader and generate a partition table based on the config. -Flashing your project ---------------------- +# Flashing your project When `make all` finishes, it will print a command line to use esptool.py to flash the chip. However you can also do this from make by running: @@ -44,8 +36,7 @@ This will flash the entire project (app, bootloader and partition table) to a ne You don't need to run `make all` before running `make flash`, `make flash` will automatically rebuild anything which needs it. -Compiling & Flashing Just the App ---------------------------------- +# Compiling & Flashing Just the App After the initial flash, you may just want to build and flash just your app, not the bootloader and partition table: @@ -56,8 +47,7 @@ After the initial flash, you may just want to build and flash just your app, not (There's no downside to reflashing the bootloader and partition table each time, if they haven't changed.) -The Partition Table -------------------- +# The Partition Table Once you've compiled your project, the "build" directory will contain a binary file with a name like "my_app.bin". This is an ESP32 image binary that can be loaded by the bootloader. @@ -72,21 +62,15 @@ The simplest way to use the partition table is to `make menuconfig` and choose o In both cases the factory app is flashed at offset 0x10000. If you `make partition_table` then it will print a summary of the partition table. -For more details about partition tables and how to create custom variations, view the `docs/partition_tables.rst` file. +For more details about partition tables and how to create custom variations, view the `docs/partition-tables.rst` file. -Resources ---------- +# Resources -* The [docs directory of the esp-idf repository](docs) contains esp-idf documentation. +* The [docs directory of the esp-idf repository](docs) contains source of [esp-idf](http://esp-idf.readthedocs.io/) documentation. * The [esp32.com forum](http://esp32.com/) is a place to ask questions and find community resources. * [Check the Issues section on github](https://github.com/espressif/esp-idf/issues) if you find a bug or have a feature request. Please check existing Issues before opening a new one. -* If you're interested in contributing to esp-idf, please check the [CONTRIBUTING.md](CONTRIBUTING.md) file. +* If you're interested in contributing to esp-idf, please check the [Contributions Guide](http://esp-idf.readthedocs.io/en/latest/contributing.html>). - -.. |docs| image:: https://readthedocs.org/projects/docs/badge/?version=latest - :alt: Documentation Status - :scale: 100% - :target: http://esp-idf.readthedocs.io/en/latest/?badge=latest \ No newline at end of file From adae42fd85381071a2564940f398e2fdf22fdc97 Mon Sep 17 00:00:00 2001 From: Krzysztof Date: Wed, 26 Oct 2016 21:08:36 +0200 Subject: [PATCH 130/149] API template - reflected template in other DRAFT API dcouments - DRAFT of Bluetooth API - link fixes in CONTRIBUTING.rst --- CONTRIBUTING.rst | 6 +++-- docs/Doxyfile | 26 +++++++++--------- docs/api/bt.rst | 31 ++++++++++++++++++++++ docs/api/esp_wifi.rst | 15 +++++++++++ docs/api/example.rst | 41 ----------------------------- docs/api/gpio.rst | 28 +++++++++++++++----- docs/api/template.rst | 61 +++++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 7 +++-- 8 files changed, 149 insertions(+), 66 deletions(-) create mode 100644 docs/api/bt.rst delete mode 100644 docs/api/example.rst create mode 100644 docs/api/template.rst diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index ed1c0b92d..3bf43f6db 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -19,7 +19,7 @@ Before sending us a Pull Request, please consider this list of points: * Is the code adequately commented for people to understand how it is structured? -* Is there documentation or examples that go with code contributions? [There are additional suggestions for writing good examples in the examples README](examples/README.md). +* Is there documentation or examples that go with code contributions? `There are additional suggestions for writing good examples in the examples README `_. * Are comments and documentation written in clear English, with no spelling or grammar errors? @@ -39,4 +39,6 @@ If this process passes, it will be merged onto the public github repository. Legal Part ---------- -Before a contribution can be accepted, you will need to sign our :doc:`Contributor Agreement `. You will be prompted for this automatically as part of the Pull Request process. +Before a contribution can be accepted, you will need to sign our `Contributor Agreement `_. You will be prompted for this automatically as part of the Pull Request process. + + diff --git a/docs/Doxyfile b/docs/Doxyfile index 8328d44ca..f905de74c 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -1,13 +1,13 @@ -PROJECT_NAME = "ESP32 Programming Guide" -XML_OUTPUT = xml -GENERATE_LATEX = NO -GENERATE_MAN = NO -GENERATE_RTF = NO -CASE_SENSE_NAMES = NO -INPUT = ../components/esp32/include/esp_wifi.h ../components/driver/include/driver/gpio.h ../components/esp32/include/rom/gpio.h -RECURSIVE = YES -QUIET = YES -JAVADOC_AUTOBRIEF = YES -GENERATE_HTML = NO -GENERATE_XML = YES -WARN_LOGFILE = "DoxyGenWarningLog.txt" \ No newline at end of file +PROJECT_NAME = "ESP32 Programming Guide" +XML_OUTPUT = xml +GENERATE_LATEX = NO +GENERATE_MAN = NO +GENERATE_RTF = NO +CASE_SENSE_NAMES = NO +INPUT = ../components/esp32/include/esp_wifi.h ../components/driver/include/driver/gpio.h ../components/esp32/include/rom/gpio.h ../components/bt/include/bt.h +RECURSIVE = YES +QUIET = YES +JAVADOC_AUTOBRIEF = YES +GENERATE_HTML = NO +GENERATE_XML = YES +WARN_LOGFILE = "DoxyGenWarningLog.txt" diff --git a/docs/api/bt.rst b/docs/api/bt.rst new file mode 100644 index 000000000..16f30dc4e --- /dev/null +++ b/docs/api/bt.rst @@ -0,0 +1,31 @@ +Bluetooth API +============= + +Overview +-------- + +`Instructions `_ + +Application Example +------------------- + +`Instructions `_ + +Reference +--------- + +`Instructions `_ + +Type Definitions +^^^^^^^^^^^^^^^^ + +.. doxygentypedef:: vhci_host_callback + +Functions +^^^^^^^^^ + +.. doxygenfunction:: API_vhci_host_check_send_available +.. doxygenfunction:: API_vhci_host_register_callback +.. doxygenfunction:: API_vhci_host_send_packet +.. doxygenfunction:: bt_controller_init + diff --git a/docs/api/esp_wifi.rst b/docs/api/esp_wifi.rst index 48b4db204..e4ec59fc8 100644 --- a/docs/api/esp_wifi.rst +++ b/docs/api/esp_wifi.rst @@ -1,6 +1,21 @@ Wi-Fi API ========= +Overview +-------- + +`Instructions `_ + +Application Example +------------------- + +`Instructions `_ + +Reference +--------- + +`Instructions `_ + Macros ------ diff --git a/docs/api/example.rst b/docs/api/example.rst deleted file mode 100644 index 88ecb4601..000000000 --- a/docs/api/example.rst +++ /dev/null @@ -1,41 +0,0 @@ -Example Visualizations -====================== - -Function prototpe ------------------ - -.. c:function:: esp_err_t esp_wifi_get_ap_list (uint16_t *number, wifi_ap_list_t *ap_list) -.. c:function:: esp_err_t esp_wifi_set_protocol (wifi_interface_t ifx, uint8_t protocol_bitmap) - - -Function definition -------------------- - -Wi-Fi -^^^^^ -.. doxygenfunction:: esp_wifi_init -.. doxygenfunction:: esp_wifi_set_config - -GPIO -^^^^ -.. doxygenfunction:: gpio_isr_register - -Led Control -^^^^^^^^^^^ - -.. doxygenfunction:: ledc_timer_set - - -Enum definition ---------------- - -.. doxygenenum:: wifi_auth_mode_t - - -Struct definition ------------------ - -.. doxygenstruct:: wifi_scan_config_t - :members: - - diff --git a/docs/api/gpio.rst b/docs/api/gpio.rst index 11b0e4a46..c12c991ce 100644 --- a/docs/api/gpio.rst +++ b/docs/api/gpio.rst @@ -1,9 +1,24 @@ GPIO API ======== -Functions +Overview +-------- + +`Instructions `_ + +Application Example +------------------- + +`Instructions `_ + +Reference --------- +`Instructions `_ + +Functions +^^^^^^^^^ + .. doxygenfunction:: gpio_config .. doxygenfunction:: gpio_set_intr_type .. doxygenfunction:: gpio_intr_enable @@ -16,9 +31,7 @@ Functions .. doxygenfunction:: gpio_wakeup_disable .. doxygenfunction:: gpio_isr_register -*Example code:* - -Configuration of GPIO as an output +*Example code:* Configuration of GPIO as an output .. code-block:: c @@ -30,7 +43,7 @@ Configuration of GPIO as an output io_conf.pull_up_en = 0; //disable pull-up mode gpio_config(&io_conf); //configure GPIO with the given settings -Configuration of GPIO as an input +*Example code:* Configuration of GPIO as an input .. code-block:: c @@ -43,7 +56,8 @@ Configuration of GPIO as an input gpio_config(&io_conf); //configure GPIO with the given settings -Low level ROM GPIO functions +ROM GPIO functions +^^^^^^^^^^^^^^^^^^ .. doxygenfunction:: gpio_init .. doxygenfunction:: gpio_output_set @@ -65,3 +79,5 @@ Low level ROM GPIO functions .. doxygenfunction:: gpio_pad_pulldown .. doxygenfunction:: gpio_pad_unhold .. doxygenfunction:: gpio_pad_hold + + diff --git a/docs/api/template.rst b/docs/api/template.rst new file mode 100644 index 000000000..8b1dfd4c5 --- /dev/null +++ b/docs/api/template.rst @@ -0,0 +1,61 @@ +Template API +============= + +Overview +-------- + +INSTRUCTIONS: Provide overview where and how this API may be used. For large number of functions, break down description into groups. + + +Application Example +------------------- + +INSTRUCTIONS: Provide one or more pratical examples to demonstrate functionality of this API. + + +Reference +--------- + +INSTRUCTIONS: Provide list of API memebers divided into sections. Use coresponding **.. doxygen** directices, so member documentation is auto updated. + +* Data Structures **.. doxygenstruct** +* Macros **.. doxygendefine** +* Type Definitions **.. doxygentypedef** +* Enumerations **.. doxygenenum** +* Functions **.. doxygenfunction** +* Variables **.. doxygenvariable** + +Include code snippotes to ilustrate functionality of particular functions where applicable. Skip section hearder if empty. + + +Data Structures +^^^^^^^^^^^^^^^ + +.. Data Structures .. doxygenstruct + +Macros +^^^^^^ + +.. Macros .. doxygendefine + +Type Definitions +^^^^^^^^^^^^^^^^ + +.. Type Definitions .. doxygentypedef + +Enumerations +^^^^^^^^^^^^ + +.. Enumerations .. doxygenenum + +Functions +^^^^^^^^^ + +.. Functions .. doxygenfunction + +Variables +^^^^^^^^^ + +.. Variables .. doxygenvariable + + diff --git a/docs/index.rst b/docs/index.rst index 84f37b560..5c4d7025c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,5 +1,3 @@ -.. Read the Docs Template documentation master file - ESP32 Programming Guide ======================= @@ -25,7 +23,7 @@ Contents: Eclipse IDE .. toctree:: - :caption: Want More? + :caption: What Else? :maxdepth: 1 partition-tables @@ -37,8 +35,9 @@ Contents: :maxdepth: 1 Wi-Fi + Bluetooth GPIO - api/example + Template .. toctree:: :caption: Technical Reference From f05cd619f4bdbdfc2019b4e9f800b82ae6a13f3b Mon Sep 17 00:00:00 2001 From: Krzysztof Date: Wed, 26 Oct 2016 22:17:58 +0200 Subject: [PATCH 131/149] Sample cleaning of markup --- components/driver/include/driver/gpio.h | 148 ++++++++++++------------ docs/api/gpio.rst | 7 ++ 2 files changed, 81 insertions(+), 74 deletions(-) diff --git a/components/driver/include/driver/gpio.h b/components/driver/include/driver/gpio.h index 9b47c88e6..001be2a39 100644 --- a/components/driver/include/driver/gpio.h +++ b/components/driver/include/driver/gpio.h @@ -157,39 +157,39 @@ typedef enum { } gpio_num_t; typedef enum { - GPIO_INTR_DISABLE = 0, /* disable GPIO interrupt */ - GPIO_INTR_POSEDGE = 1, /* GPIO interrupt type : rising edge */ - GPIO_INTR_NEGEDGE = 2, /* GPIO interrupt type : falling edge */ - GPIO_INTR_ANYEDGE = 3, /* GPIO interrupt type : both rising and falling edge */ - GPIO_INTR_LOW_LEVEL = 4, /* GPIO interrupt type : input low level trigger */ - GPIO_INTR_HIGH_LEVEL = 5, /* GPIO interrupt type : input high level trigger */ + GPIO_INTR_DISABLE = 0, /*!< disable GPIO interrupt */ + GPIO_INTR_POSEDGE = 1, /*!< GPIO interrupt type : rising edge */ + GPIO_INTR_NEGEDGE = 2, /*!< GPIO interrupt type : falling edge */ + GPIO_INTR_ANYEDGE = 3, /*!< GPIO interrupt type : both rising and falling edge */ + GPIO_INTR_LOW_LEVEL = 4, /*!< GPIO interrupt type : input low level trigger */ + GPIO_INTR_HIGH_LEVEL = 5, /*!< GPIO interrupt type : input high level trigger */ GPIO_INTR_MAX, } gpio_int_type_t; typedef enum { - GPIO_MODE_INPUT = GPIO_MODE_DEF_INPUT, /* GPIO mode : input only */ - GPIO_MODE_OUTPUT = GPIO_MODE_DEF_OUTPUT, /* GPIO mode : output only mode */ - GPIO_MODE_OUTPUT_OD = ((GPIO_MODE_DEF_OUTPUT)|(GPIO_MODE_DEF_OD)), /* GPIO mode : output only with open-drain mode */ - GPIO_MODE_INPUT_OUTPUT_OD = ((GPIO_MODE_DEF_INPUT)|(GPIO_MODE_DEF_OUTPUT)|(GPIO_MODE_DEF_OD)), /* GPIO mode : output and input with open-drain mode*/ - GPIO_MODE_INPUT_OUTPUT = ((GPIO_MODE_DEF_INPUT)|(GPIO_MODE_DEF_OUTPUT)), /* GPIO mode : output and input mode */ + GPIO_MODE_INPUT = GPIO_MODE_DEF_INPUT, /*!< GPIO mode : input only */ + GPIO_MODE_OUTPUT = GPIO_MODE_DEF_OUTPUT, /*!< GPIO mode : output only mode */ + GPIO_MODE_OUTPUT_OD = ((GPIO_MODE_DEF_OUTPUT)|(GPIO_MODE_DEF_OD)), /*!< GPIO mode : output only with open-drain mode */ + GPIO_MODE_INPUT_OUTPUT_OD = ((GPIO_MODE_DEF_INPUT)|(GPIO_MODE_DEF_OUTPUT)|(GPIO_MODE_DEF_OD)), /*!< GPIO mode : output and input with open-drain mode*/ + GPIO_MODE_INPUT_OUTPUT = ((GPIO_MODE_DEF_INPUT)|(GPIO_MODE_DEF_OUTPUT)), /*!< GPIO mode : output and input mode */ } gpio_mode_t; typedef enum { - GPIO_PULLUP_DISABLE = 0x0, /* disable GPIO pull-up resistor */ - GPIO_PULLUP_ENABLE = 0x1, /* enable GPIO pull-up resistor */ + GPIO_PULLUP_DISABLE = 0x0, /*!< disable GPIO pull-up resistor */ + GPIO_PULLUP_ENABLE = 0x1, /*!< enable GPIO pull-up resistor */ } gpio_pullup_t; typedef enum { - GPIO_PULLDOWN_DISABLE = 0x0, /* disable GPIO pull-down resistor */ - GPIO_PULLDOWN_ENABLE = 0x1, /* enable GPIO pull-down resistor */ + GPIO_PULLDOWN_DISABLE = 0x0, /*!< disable GPIO pull-down resistor */ + GPIO_PULLDOWN_ENABLE = 0x1, /*!< enable GPIO pull-down resistor */ } gpio_pulldown_t; typedef struct { - uint64_t pin_bit_mask; /* GPIO pin: set with bit mask, each bit maps to a GPIO */ - gpio_mode_t mode; /* GPIO mode: set input/output mode */ - gpio_pullup_t pull_up_en; /* GPIO pull-up */ - gpio_pulldown_t pull_down_en; /* GPIO pull-down */ - gpio_int_type_t intr_type; /* GPIO interrupt type */ + uint64_t pin_bit_mask; /*!< GPIO pin: set with bit mask, each bit maps to a GPIO */ + gpio_mode_t mode; /*!< GPIO mode: set input/output mode */ + gpio_pullup_t pull_up_en; /*!< GPIO pull-up */ + gpio_pulldown_t pull_down_en; /*!< GPIO pull-down */ + gpio_int_type_t intr_type; /*!< GPIO interrupt type */ } gpio_config_t; typedef enum { @@ -199,10 +199,10 @@ typedef enum { } gpio_level_t; typedef enum { - GPIO_PULLUP_ONLY, /* Pad pull up */ - GPIO_PULLDOWN_ONLY, /* Pad pull down */ - GPIO_PULLUP_PULLDOWN, /* Pad pull up + pull down*/ - GPIO_FLOATING, /* Pad floating */ + GPIO_PULLUP_ONLY, /*!< Pad pull up */ + GPIO_PULLDOWN_ONLY, /*!< Pad pull down */ + GPIO_PULLUP_PULLDOWN, /*!< Pad pull up + pull down*/ + GPIO_FLOATING, /*!< Pad floating */ } gpio_pull_mode_t; typedef void (*gpio_event_callback)(gpio_num_t gpio_intr_num); @@ -224,20 +224,20 @@ typedef void (*gpio_event_callback)(gpio_num_t gpio_intr_num); */ /** - * @brief GPIO common configuration + * @brief GPIO common configuration * * Use this Function ,Configure GPIO's Mode,pull-up,PullDown,IntrType * - * @parameter[in] pGPIOConfig - * pGPIOConfig.pin_bit_mask : Configure GPIO pins bits,set this parameter with bit mask. + * @param[in] pGPIOConfig + * pGPIOConfig.pin_bit_mask : Configure GPIO pins bits,set this parameter with bit mask. * If you want to configure GPIO34 and GPIO16, pin_bit_mask=GPIO_Pin_16|GPIO_Pin_34; - * pGPIOConfig.mode : Configure GPIO mode,such as output ,input... - * pGPIOConfig.pull_up_en : Enable or Disable pull-up - * pGPIOConfig.pull_down_en : Enable or Disable pull-down - * pGPIOConfig.intr_type : Configure GPIO interrupt trigger type - * @return ESP_OK: success ; - * ESP_ERR_INVALID_ARG: parameter error - * ESP_FAIL : GPIO error + * pGPIOConfig.mode : Configure GPIO mode,such as output ,input... + * pGPIOConfig.pull_up_en : Enable or Disable pull-up + * pGPIOConfig.pull_down_en : Enable or Disable pull-down + * pGPIOConfig.intr_type : Configure GPIO interrupt trigger type + * @return ESP_OK: success ; + * ESP_ERR_INVALID_ARG: parameter error + * ESP_FAIL : GPIO error * */ esp_err_t gpio_config(gpio_config_t *pGPIOConfig); @@ -246,12 +246,12 @@ esp_err_t gpio_config(gpio_config_t *pGPIOConfig); /** * @brief GPIO set interrupt trigger type * - * @parameter[in] gpio_num : GPIO number. + * @param[in] gpio_num : GPIO number. * If you want to set output level of GPIO16, gpio_num should be GPIO_NUM_16 (16); - * @parameter[in] intr_type: interrupt type, select from gpio_int_type_t + * @param[in] intr_type: interrupt type, select from gpio_int_type_t * - * @return ESP_OK : success - * ESP_ERR_INVALID_ARG: parameter error + * @return ESP_OK : success + * ESP_ERR_INVALID_ARG: parameter error * */ esp_err_t gpio_set_intr_type(gpio_num_t gpio_num, gpio_int_type_t intr_type); @@ -259,11 +259,11 @@ esp_err_t gpio_set_intr_type(gpio_num_t gpio_num, gpio_int_type_t intr_type); /** * @brief enable GPIO module interrupt signal * - * @parameter[in] gpio_num : GPIO number. + * @param[in] gpio_num : GPIO number. * If you want to set output level of GPIO16, gpio_num should be GPIO_NUM_16 (16); * - * @return ESP_OK : success - * ESP_ERR_INVALID_ARG: parameter error + * @return ESP_OK : success + * ESP_ERR_INVALID_ARG: parameter error * */ esp_err_t gpio_intr_enable(gpio_num_t gpio_num); @@ -271,78 +271,78 @@ esp_err_t gpio_intr_enable(gpio_num_t gpio_num); /** * @brief disable GPIO module interrupt signal * - * @parameter[in] gpio_num : GPIO number. + * @param[in] gpio_num : GPIO number. * If you want to set output level of GPIO16, gpio_num should be GPIO_NUM_16 (16); * - * @return ESP_OK : success - * ESP_ERR_INVALID_ARG: parameter error + * @return ESP_OK : success + * ESP_ERR_INVALID_ARG: parameter error * */ esp_err_t gpio_intr_disable(gpio_num_t gpio_num); /** - * @brief GPIO set output level + * @brief GPIO set output level * - * @parameter[in] gpio_num : GPIO number. + * @param[in] gpio_num : GPIO number. * If you want to set output level of GPIO16, gpio_num should be GPIO_NUM_16 (16); - * @parameter[in] level : Output level. 0: low ; 1: high + * @param[in] level : Output level. 0: low ; 1: high * - * @return ESP_OK : success - * ESP_FAIL : GPIO error + * @return ESP_OK : success + * ESP_FAIL : GPIO error * */ esp_err_t gpio_set_level(gpio_num_t gpio_num, uint32_t level); /** - * @brief GPIO get input level + * @brief GPIO get input level * - * @parameter[in] gpio_num : GPIO number. + * @param[in] gpio_num : GPIO number. * If you want to get level of pin GPIO16, gpio_num should be GPIO_NUM_16 (16); * - * @return 0 : the GPIO input level is 0 + * @return 0 : the GPIO input level is 0 * 1 : the GPIO input level is 1 * */ int gpio_get_level(gpio_num_t gpio_num); /** - * @brief GPIO set direction + * @brief GPIO set direction * * Configure GPIO direction,such as output_only,input_only,output_and_input * - * @parameter[in] gpio_num : Configure GPIO pins number,it should be GPIO number. + * @param[in] gpio_num : Configure GPIO pins number,it should be GPIO number. * If you want to set direction of GPIO16, gpio_num should be GPIO_NUM_16 (16); - * @parameter[in] mode : Configure GPIO direction,such as output_only,input_only,... + * @param[in] mode : Configure GPIO direction,such as output_only,input_only,... * - * @return ESP_OK : success - * ESP_ERR_INVALID_ARG : fail - * ESP_FAIL : GPIO error + * @return ESP_OK : success + * ESP_ERR_INVALID_ARG : fail + * ESP_FAIL : GPIO error * */ esp_err_t gpio_set_direction(gpio_num_t gpio_num, gpio_mode_t mode); /** - * @brief GPIO set pull + * @brief GPIO set pull * * User this Function,configure GPIO pull mode,such as pull-up,pull-down * - * @parameter[in] gpio_num : Configure GPIO pins number,it should be GPIO number. + * @param[in] gpio_num : Configure GPIO pins number,it should be GPIO number. * If you want to set pull up or down mode for GPIO16,gpio_num should be GPIO_NUM_16 (16); - * @parameter[in] pull : Configure GPIO pull up/down mode,such as pullup_only,pulldown_only,pullup_and_pulldown,... + * @param[in] pull : Configure GPIO pull up/down mode,such as pullup_only,pulldown_only,pullup_and_pulldown,... * - * @return ESP_OK : success - * ESP_ERR_INVALID_ARG : fail - * ESP_FAIL : GPIO error + * @return ESP_OK : success + * ESP_ERR_INVALID_ARG : fail + * ESP_FAIL : GPIO error * */ esp_err_t gpio_set_pull_mode(gpio_num_t gpio_num, gpio_pull_mode_t pull); /** - * @brief enable GPIO wake-up function. + * @brief enable GPIO wake-up function. * - * @param gpio_num_t gpio_num : GPIO number. + * @param gpio_num : GPIO number. * - * @param gpio_int_type_t intr_type : only GPIO_INTR_LOLEVEL\GPIO_INTR_HILEVEL can be used + * @param intr_type : only GPIO_INTR_LOLEVEL\GPIO_INTR_HILEVEL can be used * * @return ESP_OK: success * ESP_ERR_INVALID_ARG: parameter error @@ -350,9 +350,9 @@ esp_err_t gpio_set_pull_mode(gpio_num_t gpio_num, gpio_pull_mode_t pull); esp_err_t gpio_wakeup_enable(gpio_num_t gpio_num, gpio_int_type_t intr_type); /** - * @brief disable GPIO wake-up function. + * @brief disable GPIO wake-up function. * - * @param gpio_num_t gpio_num: GPIO number + * @param gpio_num: GPIO number * * @return ESP_OK: success * ESP_ERR_INVALID_ARG: parameter error @@ -365,13 +365,13 @@ esp_err_t gpio_wakeup_disable(gpio_num_t gpio_num); * Users should know that which CPU is running and then pick a INUM that is not used by system. * We can find the information of INUM and interrupt level in soc.h. * TODO: to move INUM options to menu_config - * @parameter uint32_t gpio_intr_num : GPIO interrupt number,check the info in soc.h, and please see the core-isa.h for more details - * @parameter void (* fn)(void* ) : interrupt handler function. + * @param gpio_intr_num : GPIO interrupt number,check the info in soc.h, and please see the core-isa.h for more details + * @param (*fn)(void* ) : interrupt handler function. * Note that the handler function MUST be defined with attribution of "IRAM_ATTR". - * @parameter void * arg : parameter for handler function + * @param arg : parameter for handler function * - * @return ESP_OK : success ; - * ESP_FAIL: gpio error + * @return ESP_OK : success ; + * ESP_FAIL: gpio error */ esp_err_t gpio_isr_register(uint32_t gpio_intr_num, void (*fn)(void*), void * arg); diff --git a/docs/api/gpio.rst b/docs/api/gpio.rst index c12c991ce..3c5c12292 100644 --- a/docs/api/gpio.rst +++ b/docs/api/gpio.rst @@ -16,6 +16,13 @@ Reference `Instructions `_ +Enumerations +^^^^^^^^^^^^ + +.. doxygenenum:: gpio_int_type_t +.. doxygenenum:: gpio_mode_t +.. doxygenenum:: gpio_pull_mode_t + Functions ^^^^^^^^^ From 1263cd340eb492581f4f179e28eb7c6df3fec644 Mon Sep 17 00:00:00 2001 From: Krzysztof Date: Thu, 27 Oct 2016 21:20:30 +0200 Subject: [PATCH 132/149] Add missing annotations --- components/nvs_flash/include/nvs.h | 42 +++++++++++++++++------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/components/nvs_flash/include/nvs.h b/components/nvs_flash/include/nvs.h index 912ea2210..d0c9908af 100644 --- a/components/nvs_flash/include/nvs.h +++ b/components/nvs_flash/include/nvs.h @@ -28,23 +28,27 @@ extern "C" { */ typedef uint32_t nvs_handle; -#define ESP_ERR_NVS_BASE 0x1100 -#define ESP_ERR_NVS_NOT_INITIALIZED (ESP_ERR_NVS_BASE + 0x01) -#define ESP_ERR_NVS_NOT_FOUND (ESP_ERR_NVS_BASE + 0x02) -#define ESP_ERR_NVS_TYPE_MISMATCH (ESP_ERR_NVS_BASE + 0x03) -#define ESP_ERR_NVS_READ_ONLY (ESP_ERR_NVS_BASE + 0x04) -#define ESP_ERR_NVS_NOT_ENOUGH_SPACE (ESP_ERR_NVS_BASE + 0x05) -#define ESP_ERR_NVS_INVALID_NAME (ESP_ERR_NVS_BASE + 0x06) -#define ESP_ERR_NVS_INVALID_HANDLE (ESP_ERR_NVS_BASE + 0x07) -#define ESP_ERR_NVS_REMOVE_FAILED (ESP_ERR_NVS_BASE + 0x08) -#define ESP_ERR_NVS_KEY_TOO_LONG (ESP_ERR_NVS_BASE + 0x09) -#define ESP_ERR_NVS_PAGE_FULL (ESP_ERR_NVS_BASE + 0x0a) -#define ESP_ERR_NVS_INVALID_STATE (ESP_ERR_NVS_BASE + 0x0b) -#define ESP_ERR_NVS_INVALID_LENGTH (ESP_ERR_NVS_BASE + 0x0c) +#define ESP_ERR_NVS_BASE 0x1100 /*!< Starting number of error codes */ +#define ESP_ERR_NVS_NOT_INITIALIZED (ESP_ERR_NVS_BASE + 0x01) /*!< The storage driver is not initialized */ +#define ESP_ERR_NVS_NOT_FOUND (ESP_ERR_NVS_BASE + 0x02) /*!< Id namespace doesn’t exist yet and mode is NVS_READONLY */ +#define ESP_ERR_NVS_TYPE_MISMATCH (ESP_ERR_NVS_BASE + 0x03) /*!< TBA */ +#define ESP_ERR_NVS_READ_ONLY (ESP_ERR_NVS_BASE + 0x04) /*!< Storage handle was opened as read only */ +#define ESP_ERR_NVS_NOT_ENOUGH_SPACE (ESP_ERR_NVS_BASE + 0x05) /*!< There is not enough space in the underlying storage to save the value */ +#define ESP_ERR_NVS_INVALID_NAME (ESP_ERR_NVS_BASE + 0x06) /*!< Namespace name doesn’t satisfy constraints */ +#define ESP_ERR_NVS_INVALID_HANDLE (ESP_ERR_NVS_BASE + 0x07) /*!< Handle has been closed or is NULL */ +#define ESP_ERR_NVS_REMOVE_FAILED (ESP_ERR_NVS_BASE + 0x08) /*!< The value wasn’t updated because flash write operation has failed. The value was written however, and update will be finished after re-initialization of nvs, provided that flash operation doesn’t fail again. */ +#define ESP_ERR_NVS_KEY_TOO_LONG (ESP_ERR_NVS_BASE + 0x09) /*!< TBA */ +#define ESP_ERR_NVS_PAGE_FULL (ESP_ERR_NVS_BASE + 0x0a) /*!< TBA */ +#define ESP_ERR_NVS_INVALID_STATE (ESP_ERR_NVS_BASE + 0x0b) /*!< TBA */ +#define ESP_ERR_NVS_INVALID_LENGTH (ESP_ERR_NVS_BASE + 0x0c) /*!< TBA */ +/** + * @brief Mode of opening the non-volatile storage + * + */ typedef enum { - NVS_READONLY, - NVS_READWRITE + NVS_READONLY, /*!< Read only */ + NVS_READWRITE /*!< Read and write */ } nvs_open_mode; /** @@ -129,24 +133,26 @@ esp_err_t nvs_set_blob(nvs_handle handle, const char* key, const void* value, si * It is suggested that nvs_get/set_str is used for zero-terminated C strings, and * nvs_get/set_blob used for arbitrary data structures. * - * Example of using nvs_get_i32: + * \code{c} + * // Example of using nvs_get_i32: * int32_t max_buffer_size = 4096; // default value * esp_err_t err = nvs_get_i32(my_handle, "max_buffer_size", &max_buffer_size); * assert(err == ESP_OK || err == ESP_ERR_NVS_NOT_FOUND); * // if ESP_ERR_NVS_NOT_FOUND was returned, max_buffer_size will still * // have its default value. * - * Example (without error checking) of using nvs_get_str to get a string into dynamic array: + * // Example (without error checking) of using nvs_get_str to get a string into dynamic array: * size_t required_size; * nvs_get_str(my_handle, "server_name", NULL, &required_size); * char* server_name = malloc(required_size); * nvs_get_str(my_handle, "server_name", server_name, &required_size); * - * Example (without error checking) of using nvs_get_blob to get a binary data + * // Example (without error checking) of using nvs_get_blob to get a binary data * into a static array: * uint8_t mac_addr[6]; * size_t size = sizeof(mac_addr); * nvs_get_blob(my_handle, "dst_mac_addr", mac_addr, &size); + * \endcode * * @param[in] handle Handle obtained from nvs_open function. * @param[in] key Key name. Maximal length is determined by the underlying From 234739f51a917ae3b27c551471d906d548678b09 Mon Sep 17 00:00:00 2001 From: Krzysztof Date: Thu, 27 Oct 2016 21:22:35 +0200 Subject: [PATCH 133/149] Draft of non-volatile storage component documentation --- docs/Doxyfile | 2 +- docs/api/nvs.rst | 68 +++++++++++++++++++++++++++++++++++++++++++ docs/api/template.rst | 9 ++++++ docs/index.rst | 1 + 4 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 docs/api/nvs.rst diff --git a/docs/Doxyfile b/docs/Doxyfile index f905de74c..bb55b8ba3 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -4,7 +4,7 @@ GENERATE_LATEX = NO GENERATE_MAN = NO GENERATE_RTF = NO CASE_SENSE_NAMES = NO -INPUT = ../components/esp32/include/esp_wifi.h ../components/driver/include/driver/gpio.h ../components/esp32/include/rom/gpio.h ../components/bt/include/bt.h +INPUT = ../components/esp32/include/esp_wifi.h ../components/driver/include/driver/gpio.h ../components/esp32/include/rom/gpio.h ../components/bt/include ../components/nvs_flash/include RECURSIVE = YES QUIET = YES JAVADOC_AUTOBRIEF = YES diff --git a/docs/api/nvs.rst b/docs/api/nvs.rst new file mode 100644 index 000000000..227a1c1f7 --- /dev/null +++ b/docs/api/nvs.rst @@ -0,0 +1,68 @@ +.. include:: ../../components/nvs_flash/README.rst + +Reference +--------- + +Enumerations +^^^^^^^^^^^^ + +.. doxygenenum:: nvs_open_mode + +Functions +^^^^^^^^^ + +.. doxygenfunction:: nvs_flash_init +.. doxygenfunction:: nvs_flash_init_custom + +.. doxygenfunction:: nvs_open + +*Note: the following nvs_set_X function are "the same" except the data type accepted* + +.. doxygenfunction:: nvs_set_i8 +.. doxygenfunction:: nvs_set_u8 +.. doxygenfunction:: nvs_set_i16 +.. doxygenfunction:: nvs_set_u16 +.. doxygenfunction:: nvs_set_i32 +.. doxygenfunction:: nvs_set_u32 +.. doxygenfunction:: nvs_set_i64 +.. doxygenfunction:: nvs_set_u64 +.. doxygenfunction:: nvs_set_str +.. doxygenfunction:: nvs_set_blob + +*Note: the following nvs_get_X functions are "the same" except the data type returned* + +.. doxygenfunction:: nvs_get_i8 +.. doxygenfunction:: nvs_get_u8 +.. doxygenfunction:: nvs_get_i16 +.. doxygenfunction:: nvs_get_u16 +.. doxygenfunction:: nvs_get_i32 +.. doxygenfunction:: nvs_get_u32 +.. doxygenfunction:: nvs_get_i64 +.. doxygenfunction:: nvs_get_u64 +.. doxygenfunction:: nvs_get_str +.. doxygenfunction:: nvs_get_blob + +.. doxygenfunction:: nvs_erase_key +.. doxygenfunction:: nvs_erase_all +.. doxygenfunction:: nvs_commit +.. doxygenfunction:: nvs_close + +Error codes +^^^^^^^^^^^ + +.. doxygendefine:: ESP_ERR_NVS_BASE +.. doxygendefine:: ESP_ERR_NVS_NOT_INITIALIZED +.. doxygendefine:: ESP_ERR_NVS_NOT_FOUND +.. doxygendefine:: ESP_ERR_NVS_TYPE_MISMATCH +.. doxygendefine:: ESP_ERR_NVS_READ_ONLY +.. doxygendefine:: ESP_ERR_NVS_NOT_ENOUGH_SPACE +.. doxygendefine:: ESP_ERR_NVS_INVALID_NAME +.. doxygendefine:: ESP_ERR_NVS_INVALID_HANDLE +.. doxygendefine:: ESP_ERR_NVS_REMOVE_FAILED +.. doxygendefine:: ESP_ERR_NVS_KEY_TOO_LONG +.. doxygendefine:: ESP_ERR_NVS_PAGE_FULL +.. doxygendefine:: ESP_ERR_NVS_INVALID_STATE +.. doxygendefine:: ESP_ERR_NVS_INVALID_LENGTH + + + diff --git a/docs/api/template.rst b/docs/api/template.rst index 8b1dfd4c5..0f2623c47 100644 --- a/docs/api/template.rst +++ b/docs/api/template.rst @@ -6,6 +6,15 @@ Overview INSTRUCTIONS: Provide overview where and how this API may be used. For large number of functions, break down description into groups. +Use the folowing heading levels: + +* # with overline, for parts +* \* with overline, for chapters +* =, for sections +* -, for subsections +* ^, for subsubsections +* ", for paragraphs + Application Example ------------------- diff --git a/docs/index.rst b/docs/index.rst index 5c4d7025c..9b62885bb 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -37,6 +37,7 @@ Contents: Wi-Fi Bluetooth GPIO + NVS Template .. toctree:: From fa002b490966ba3e4112a0d6f459ced22c7aa0ca Mon Sep 17 00:00:00 2001 From: Krzysztof Date: Fri, 28 Oct 2016 14:43:48 +0200 Subject: [PATCH 134/149] Fixed headers to match python doc standard --- components/nvs_flash/README.rst | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/components/nvs_flash/README.rst b/components/nvs_flash/README.rst index 2f1c46913..ade5518aa 100644 --- a/components/nvs_flash/README.rst +++ b/components/nvs_flash/README.rst @@ -7,14 +7,14 @@ Introduction Non-volatile storage (NVS) library is designed to store key-value pairs in flash. This sections introduces some concepts used by NVS. Underlying storage -~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^ Currently NVS uses a portion of main flash memory through ``spi_flash_{read|write|erase}`` APIs. The range of flash sectors to be used by the library is provided to ``nvs_flash_init`` function. Future versions of this library may add other storage backends to keep data in another flash chip (SPI or I2C), RTC, FRAM, etc. Keys and values -~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^ NVS operates on key-value pairs. Keys are ASCII strings, maximum key length is currently 15 characters. Values can have one of the following types: @@ -32,12 +32,12 @@ Keys are required to be unique. Writing a value for a key which already exists b Data type check is also performed when reading a value. An error is returned if data type of read operation doesn’t match the data type of the value. Namespaces -~~~~~~~~~~ +^^^^^^^^^^ To mitigate potential conflicts in key names between different components, NVS assigns each key-value pair to one of namespaces. Namespace names follow the same rules as key names, i.e. 15 character maximum length. Namespace name is specified in the ``nvs_open`` call. This call returns an opaque handle, which is used in subsequent calls to ``nvs_read_*``, ``nvs_write_*``, and ``nvs_commit`` functions. This way, handle is associated with a namespace, and key names will not collide with same names in other namespaces. Security, tampering, and robustness -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ NVS library doesn't implement tamper prevention measures. It is possible for anyone with physical access to the flash chip to alter, erase, or add key-value pairs. @@ -59,12 +59,12 @@ Internals --------- Log of key-value pairs -~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^ NVS stores key-value pairs sequentially, with new key-value pairs being added at the end. When a value of any given key has to be updated, new key-value pair is added at the end of the log and old key-value pair is marked as erased. Pages and entries -~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^ NVS library uses two main entities in its operation: pages and entries. Page is a logical structure which stores a portion of the overall log. Logical page corresponds to one physical sector of flash memory. Pages which are in use have a *sequence number* associated with them. Sequence numbers impose an ordering on pages. Higher sequence numbers correspond to pages which were created later. Each page can be in one of the following states: @@ -101,7 +101,7 @@ Mapping from flash sectors to logical pages doesn't have any particular order. L +----------+ +----------+ +----------+ +----------+ Structure of a page -~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^ For now we assume that flash sector size is 4096 bytes and that ESP32 flash encryption hardware operates on 32-byte blocks. It is possible to introduce some settings configurable at compile-time (e.g. via menuconfig) to accommodate flash chips with different sector sizes (although it is not clear if other components in the system, e.g. SPI flash driver and SPI flash cache can support these other sizes). @@ -133,7 +133,7 @@ CRC32 value in header is calculated over the part which doesn't include state va The following sections describe structure of entry state bitmap and entry itself. Entry and entry state bitmap -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Each entry can be in one of the following three states. Each state is represented with two bits in the entry state bitmap. Final four bits in the bitmap (256 - 2 * 126) are unused. @@ -148,7 +148,7 @@ Erased (2'b00) Structure of entry -~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^ For values of primitive types (currently integers from 1 to 8 bytes long), entry holds one key-value pair. For string and blob types, entry holds part of the whole key-value pair. In case when a key-value pair spans multiple entries, all entries are stored in the same page. @@ -200,7 +200,7 @@ Variable length values (strings and blobs) are written into subsequent entries, Namespaces -~~~~~~~~~~ +^^^^^^^^^^ As mentioned above, each key-value pair belongs to one of the namespaces. Namespaces identifiers (strings) are stored as keys of key-value pairs in namespace with index 0. Values corresponding to these keys are indexes of these namespaces. @@ -218,10 +218,9 @@ As mentioned above, each key-value pair belongs to one of the namespaces. Namesp Item hash list -~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^ To reduce the number of reads performed from flash memory, each member of Page class maintains a list of pairs: (item index; item hash). This list makes searches much quicker. Instead of iterating over all entries, reading them from flash one at a time, ``Page::findItem`` first performs search for item hash in the hash list. This gives the item index within the page, if such an item exists. Due to a hash collision it is possible that a different item will be found. This is handled by falling back to iteration over items in flash. Each node in hash list contains a 24-bit hash and 8-bit item index. Hash is calculated based on item namespace and key name. CRC32 is used for calculation, result is truncated to 24 bits. To reduce overhead of storing 32-bit entries in a linked list, list is implemented as a doubly-linked list of arrays. Each array holds 29 entries, for the total size of 128 bytes, together with linked list pointers and 32-bit count field. Minimal amount of extra RAM useage per page is therefore 128 bytes, maximum is 640 bytes. - From 2d56953ee458266e00bba2788ec6821823eddd8d Mon Sep 17 00:00:00 2001 From: Krzysztof Date: Sat, 29 Oct 2016 20:54:58 +0200 Subject: [PATCH 135/149] docu makup update --- components/bt/include/bt.h | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/components/bt/include/bt.h b/components/bt/include/bt.h index 1e89f96aa..f476334b1 100644 --- a/components/bt/include/bt.h +++ b/components/bt/include/bt.h @@ -28,37 +28,36 @@ extern "C" { * * This function should be called only once, before any other BT functions are called. */ -void bt_controller_init(); +void bt_controller_init(void); -/** @brief: vhci_host_callback +/** @brief vhci_host_callback * used for vhci call host function to notify what host need to do * * notify_host_send_available: notify host can send packet to controller * notify_host_recv: notify host that controller has packet send to host */ typedef struct vhci_host_callback { - void (*notify_host_send_available)(void); int (*notify_host_recv)(uint8_t *data, uint16_t len); } vhci_host_callback_t; -/** @brief: API_vhci_host_check_send_available +/** @brief API_vhci_host_check_send_available * used for check actively if the host can send packet to controller or not. - * return true for ready to send, false means cannot send packet + * @return true for ready to send, false means cannot send packet */ bool API_vhci_host_check_send_available(void); -/** @brief: API_vhci_host_send_packet +/** @brief API_vhci_host_send_packet * host send packet to controller - * param data is the packet point, the param len is the packet length - * return void + * @param data the packet point + *,@param len the packet length */ void API_vhci_host_send_packet(uint8_t *data, uint16_t len); -/** @brief: API_vhci_host_register_callback +/** @brief API_vhci_host_register_callback * register the vhci referece callback, the call back * struct defined by vhci_host_callback structure. - * param is the vhci_host_callback type variable + * @param callback vhci_host_callback type variable */ void API_vhci_host_register_callback(const vhci_host_callback_t *callback); From 905d27815c4889be2250e408e9194fd3ae70d058 Mon Sep 17 00:00:00 2001 From: Krzysztof Date: Sat, 29 Oct 2016 23:00:30 +0200 Subject: [PATCH 136/149] API samples - Logging library - Virtual file system component --- docs/Doxyfile | 31 ++++++++++++++++++++----------- docs/api/bt.rst | 2 +- docs/api/log.rst | 20 ++++++++++++++++++++ docs/api/nvs.rst | 4 ++-- docs/api/vfs.rst | 27 +++++++++++++++++++++++++++ docs/index.rst | 4 +++- 6 files changed, 73 insertions(+), 15 deletions(-) create mode 100644 docs/api/log.rst create mode 100644 docs/api/vfs.rst diff --git a/docs/Doxyfile b/docs/Doxyfile index bb55b8ba3..9c53aff1f 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -1,13 +1,22 @@ PROJECT_NAME = "ESP32 Programming Guide" -XML_OUTPUT = xml -GENERATE_LATEX = NO -GENERATE_MAN = NO -GENERATE_RTF = NO -CASE_SENSE_NAMES = NO -INPUT = ../components/esp32/include/esp_wifi.h ../components/driver/include/driver/gpio.h ../components/esp32/include/rom/gpio.h ../components/bt/include ../components/nvs_flash/include -RECURSIVE = YES + +INPUT = ../components/esp32/include/esp_wifi.h ../components/driver/include/driver ../components/esp32/include/rom/gpio.h ../components/bt/include ../components/nvs_flash/include ../components/log/include ../components/vfs/include + +WARN_NO_PARAMDOC = YES + +RECURSIVE = NO +CASE_SENSE_NAMES = NO +EXTRACT_ALL = NO + +GENERATE_XML = YES +XML_OUTPUT = xml + +GENERATE_HTML = NO +HAVE_DOT = NO +GENERATE_LATEX = NO +GENERATE_MAN = NO +GENERATE_RTF = NO + QUIET = YES -JAVADOC_AUTOBRIEF = YES -GENERATE_HTML = NO -GENERATE_XML = YES -WARN_LOGFILE = "DoxyGenWarningLog.txt" +WARN_LOGFILE = "doxygen-warning-log.txt" + diff --git a/docs/api/bt.rst b/docs/api/bt.rst index 16f30dc4e..72ab9fbd1 100644 --- a/docs/api/bt.rst +++ b/docs/api/bt.rst @@ -19,7 +19,7 @@ Reference Type Definitions ^^^^^^^^^^^^^^^^ -.. doxygentypedef:: vhci_host_callback +.. doxygenstruct:: vhci_host_callback Functions ^^^^^^^^^ diff --git a/docs/api/log.rst b/docs/api/log.rst new file mode 100644 index 000000000..8e0f2d85d --- /dev/null +++ b/docs/api/log.rst @@ -0,0 +1,20 @@ +.. include:: ../../components/log/README.rst + +API Reference +------------- + +Enumerations +^^^^^^^^^^^^ + +.. doxygenenum:: esp_log_level_t + +Functions +^^^^^^^^^ + +.. doxygenfunction:: esp_log_level_set +.. doxygenfunction:: esp_log_set_vprintf +.. doxygenfunction:: esp_log_write + +.. FIXME esp_log_timestamp + + diff --git a/docs/api/nvs.rst b/docs/api/nvs.rst index 227a1c1f7..fc2bba5a1 100644 --- a/docs/api/nvs.rst +++ b/docs/api/nvs.rst @@ -1,7 +1,7 @@ .. include:: ../../components/nvs_flash/README.rst -Reference ---------- +API Reference +------------- Enumerations ^^^^^^^^^^^^ diff --git a/docs/api/vfs.rst b/docs/api/vfs.rst new file mode 100644 index 000000000..97ea1a584 --- /dev/null +++ b/docs/api/vfs.rst @@ -0,0 +1,27 @@ +.. include:: ../../components/vfs/README.rst + +API Reference +------------- + +Defines +^^^^^^^ + +.. doxygendefine:: ESP_VFS_PATH_MAX +.. doxygendefine:: ESP_VFS_FLAG_DEFAULT +.. doxygendefine:: ESP_VFS_FLAG_CONTEXT_PTR + + +Structures +^^^^^^^^^^ + +.. doxygenstruct:: esp_vfs_t + +Functions +^^^^^^^^^ + +.. doxygenfunction:: esp_vfs_dev_uart_register +.. doxygenfunction:: esp_vfs_register + + + + diff --git a/docs/index.rst b/docs/index.rst index 9b62885bb..72c5df13f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -37,7 +37,9 @@ Contents: Wi-Fi Bluetooth GPIO - NVS + Logging + Non-volatile storage + Virtual filesystem Template .. toctree:: From f6118c67b09f479297a959ce6b0714d717e2ebbc Mon Sep 17 00:00:00 2001 From: Krzysztof Date: Sat, 29 Oct 2016 23:00:46 +0200 Subject: [PATCH 137/149] Fixed desription of logging library --- components/log/README.rst | 61 +++++++++++++++++++++++++ components/log/include/esp_log.h | 76 ++++---------------------------- 2 files changed, 69 insertions(+), 68 deletions(-) create mode 100644 components/log/README.rst diff --git a/components/log/README.rst b/components/log/README.rst new file mode 100644 index 000000000..d378179c8 --- /dev/null +++ b/components/log/README.rst @@ -0,0 +1,61 @@ +Logging library +=============== + +Overview +-------- + +Log library has two ways of managing log verbosity: compile time, set via menuconfig; and runtime, using ``esp_log_set_level`` function. + +At compile time, filtering is done using ``CONFIG_LOG_DEFAULT_LEVEL`` macro, set via menuconfig. All logging statments for levels higher than ``CONFIG_LOG_DEFAULT_LEVEL`` will be removed by the preprocessor. + +At run time, all logs below ``CONFIG_LOG_DEFAULT_LEVEL`` are enabled by default. ``esp_log_set_level`` function may be used to set logging level per module. Modules are identified by their tags, which are human-readable ASCII zero-terminated strings. + +How to use this library +----------------------- + +In each C file which uses logging functionality, define TAG variable like this: + +.. code-block:: c + + static const char* TAG = "MyModule"; + +then use one of logging macros to produce output, e.g: + +.. code-block:: c + + ESP_LOGW(TAG, "Baud rate error %.1f%%. Requested: %d baud, actual: %d baud", error * 100, baud_req, baud_real); + +Several macros are available for different verbosity levels: + +* ``ESP_LOGE`` - error +* ``ESP_LOGW`` - warning +* ``ESP_LOGI`` - info +* ``ESP_LOGD`` - debug +* ``ESP_LOGV`` - verbose + +Additionally there is an _EARLY_ variant for each of these macros (e.g. ``ESP_EARLY_LOGE`` ).These variants can run in startup code, before heap allocator and syscalls have been initialized. When compiling bootloader, normal ``ESP_LOGx`` macros fall back to the same implementation as ``ESP_EARLY_LOGx`` macros. So the only place where ``ESP_EARLY_LOGx`` have to be used explicitly is the early startup code, such as heap allocator initialization code. + +(Note that such distinction would not have been necessary if we would have an ``ets_vprintf`` function in the ROM. Then it would be possible to switch implementation from _EARLY_ version to normal version on the fly. Unfortunately, ``ets_vprintf`` in ROM has been inlined by the compiler into ``ets_printf``, so it is not accessible outside.) + +To override default verbosity level at file or component scope, define ``LOG_LOCAL_LEVEL`` macro. At file scope, define it before including ``esp_log.h``, e.g.: + +.. code-block:: c + + #define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE + #include "esp_log.h" + + +At component scope, define it in component makefile: + +.. code-block:: make + + CFLAGS += -D LOG_LOCAL_LEVEL=ESP_LOG_DEBUG + +To configure logging output per module at runtime, add calls to ``esp_log_set_level`` function: + +.. code-block:: c + + esp_log_set_level("*", ESP_LOG_ERROR); // set all components to ERROR level + esp_log_set_level("wifi", ESP_LOG_WARN); // enable WARN logs from WiFi stack + esp_log_set_level("dhcpc", ESP_LOG_INFO); // enable INFO logs from DHCP client + diff --git a/components/log/include/esp_log.h b/components/log/include/esp_log.h index 8ca6e241d..2878dc501 100644 --- a/components/log/include/esp_log.h +++ b/components/log/include/esp_log.h @@ -24,76 +24,16 @@ extern "C" { #endif /** - * @brief Logging library - * - * Log library has two ways of managing log verbosity: compile time, set via - * menuconfig, and runtime, using esp_log_set_level function. - * - * At compile time, filtering is done using CONFIG_LOG_DEFAULT_LEVEL macro, set via - * menuconfig. All logging statments for levels higher than CONFIG_LOG_DEFAULT_LEVEL - * will be removed by the preprocessor. - * - * At run time, all logs below CONFIG_LOG_DEFAULT_LEVEL are enabled by default. - * esp_log_set_level function may be used to set logging level per module. - * Modules are identified by their tags, which are human-readable ASCII - * zero-terminated strings. - * - * How to use this library: - * - * In each C file which uses logging functionality, define TAG variable like this: - * - * static const char* TAG = "MyModule"; - * - * then use one of logging macros to produce output, e.g: - * - * ESP_LOGW(TAG, "Baud rate error %.1f%%. Requested: %d baud, actual: %d baud", error * 100, baud_req, baud_real); - * - * Several macros are available for different verbosity levels: - * - * ESP_LOGE — error - * ESP_LOGW — warning - * ESP_LOGI — info - * ESP_LOGD - debug - * ESP_LOGV - verbose - * - * Additionally there is an _EARLY_ variant for each of these macros (e.g. ESP_EARLY_LOGE). - * These variants can run in startup code, before heap allocator and syscalls - * have been initialized. - * When compiling bootloader, normal ESP_LOGx macros fall back to the same implementation - * as ESP_EARLY_LOGx macros. So the only place where ESP_EARLY_LOGx have to be used explicitly - * is the early startup code, such as heap allocator initialization code. - * - * (Note that such distinction would not have been necessary if we would have an - * ets_vprintf function in the ROM. Then it would be possible to switch implementation - * from _EARLY version to normal version on the fly. Unfortunately, ets_vprintf in ROM - * has been inlined by the compiler into ets_printf, so it is not accessible outside.) - * - * To override default verbosity level at file or component scope, define LOG_LOCAL_LEVEL macro. - * At file scope, define it before including esp_log.h, e.g.: - * - * #define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE - * #include "esp_log.h" - * - * At component scope, define it in component makefile: - * - * CFLAGS += -D LOG_LOCAL_LEVEL=ESP_LOG_DEBUG - * - * To configure logging output per module at runtime, add calls to esp_log_set_level function: - * - * esp_log_set_level("*", ESP_LOG_ERROR); // set all components to ERROR level - * esp_log_set_level("wifi", ESP_LOG_WARN); // enable WARN logs from WiFi stack - * esp_log_set_level("dhcpc", ESP_LOG_INFO); // enable INFO logs from DHCP client + * @brief Log level * */ - - typedef enum { - ESP_LOG_NONE, // No log output - ESP_LOG_ERROR, // Critical errors, software module can not recover on its own - ESP_LOG_WARN, // Error conditions from which recovery measures have been taken - ESP_LOG_INFO, // Information messages which describe normal flow of events - ESP_LOG_DEBUG, // Extra information which is not necessary for normal use (values, pointers, sizes, etc). - ESP_LOG_VERBOSE // Bigger chunks of debugging information, or frequent messages which can potentially flood the output. + ESP_LOG_NONE, /*!< No log output */ + ESP_LOG_ERROR, /*!< Critical errors, software module can not recover on its own */ + ESP_LOG_WARN, /*!< Error conditions from which recovery measures have been taken */ + ESP_LOG_INFO, /*!< Information messages which describe normal flow of events */ + ESP_LOG_DEBUG, /*!< Extra information which is not necessary for normal use (values, pointers, sizes, etc). */ + ESP_LOG_VERBOSE /*!< Bigger chunks of debugging information, or frequent messages which can potentially flood the output. */ } esp_log_level_t; typedef int (*vprintf_like_t)(const char *, va_list); @@ -143,7 +83,7 @@ void esp_log_write(esp_log_level_t level, const char* tag, const char* format, . * * @return timestamp, in milliseconds */ -uint32_t esp_log_timestamp(); +uint32_t esp_log_timestamp(void); #if CONFIG_LOG_COLORS From 079f5faad88353ec64d44bbe08d612e514fc0819 Mon Sep 17 00:00:00 2001 From: Krzysztof Date: Sat, 29 Oct 2016 23:15:27 +0200 Subject: [PATCH 138/149] Fixed confused Sphinx Sphinx failed to phrase esp_log_timestamp reorderdering esp_log_write and esp_log_timestamp fixed this issue --- components/log/include/esp_log.h | 23 +++++++++++------------ docs/api/log.rst | 3 +-- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/components/log/include/esp_log.h b/components/log/include/esp_log.h index 2878dc501..6716f6e10 100644 --- a/components/log/include/esp_log.h +++ b/components/log/include/esp_log.h @@ -29,7 +29,7 @@ extern "C" { */ typedef enum { ESP_LOG_NONE, /*!< No log output */ - ESP_LOG_ERROR, /*!< Critical errors, software module can not recover on its own */ + ESP_LOG_ERROR, /*!< Critical errors, software module can not recover on its own */ ESP_LOG_WARN, /*!< Error conditions from which recovery measures have been taken */ ESP_LOG_INFO, /*!< Information messages which describe normal flow of events */ ESP_LOG_DEBUG, /*!< Extra information which is not necessary for normal use (values, pointers, sizes, etc). */ @@ -60,17 +60,6 @@ void esp_log_level_set(const char* tag, esp_log_level_t level); */ void esp_log_set_vprintf(vprintf_like_t func); -/** - * @brief Write message into the log - * - * This function is not intended to be used directly. Instead, use one of - * ESP_LOGE, ESP_LOGW, ESP_LOGI, ESP_LOGD, ESP_LOGV macros. - * - * This function or these macros should not be used from an interrupt. - */ -void esp_log_write(esp_log_level_t level, const char* tag, const char* format, ...) __attribute__ ((format (printf, 3, 4))); - - /** * @brief Function which returns timestamp to be used in log output * @@ -85,6 +74,16 @@ void esp_log_write(esp_log_level_t level, const char* tag, const char* format, . */ uint32_t esp_log_timestamp(void); +/** + * @brief Write message into the log + * + * This function is not intended to be used directly. Instead, use one of + * ESP_LOGE, ESP_LOGW, ESP_LOGI, ESP_LOGD, ESP_LOGV macros. + * + * This function or these macros should not be used from an interrupt. + */ +void esp_log_write(esp_log_level_t level, const char* tag, const char* format, ...) __attribute__ ((format (printf, 3, 4))); + #if CONFIG_LOG_COLORS #define LOG_COLOR_BLACK "30" diff --git a/docs/api/log.rst b/docs/api/log.rst index 8e0f2d85d..dc76a3047 100644 --- a/docs/api/log.rst +++ b/docs/api/log.rst @@ -13,8 +13,7 @@ Functions .. doxygenfunction:: esp_log_level_set .. doxygenfunction:: esp_log_set_vprintf +.. doxygenfunction:: esp_log_timestamp .. doxygenfunction:: esp_log_write -.. FIXME esp_log_timestamp - From 19af5b7a764ae33a29001cb392f5a20ce4400432 Mon Sep 17 00:00:00 2001 From: Krzysztof Date: Sat, 29 Oct 2016 23:35:12 +0200 Subject: [PATCH 139/149] Changed title of docs --- docs/conf.py | 4 ++-- docs/index.rst | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 5ba76d7f2..8a8821fb0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -57,8 +57,8 @@ source_suffix = '.rst' master_doc = 'index' # General information about the project. -project = u'Read the Docs Template' -copyright = u'2014, Read the Docs' +project = u'ESP32 Programming Guide' +copyright = u'2016, Espressif' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/docs/index.rst b/docs/index.rst index 72c5df13f..e19499208 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,6 +1,10 @@ ESP32 Programming Guide ======================= +.. caution:: + + This is DRAFT - mind your step + Contents: .. toctree:: From 12efd96fb489ea3e697c32f24406aff2f96d7ff0 Mon Sep 17 00:00:00 2001 From: Krzysztof Date: Sun, 30 Oct 2016 19:37:45 +0100 Subject: [PATCH 140/149] Corrected documentation style This is for better visuaization with Sphinx --- components/nvs_flash/include/nvs.h | 23 +++++++++++++++-------- components/nvs_flash/include/nvs_flash.h | 2 ++ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/components/nvs_flash/include/nvs.h b/components/nvs_flash/include/nvs.h index d0c9908af..841895979 100644 --- a/components/nvs_flash/include/nvs.h +++ b/components/nvs_flash/include/nvs.h @@ -62,12 +62,13 @@ typedef enum { * underlying implementation, but is guaranteed to be * at least 16 characters. Shouldn't be empty. * @param[in] open_mode NVS_READWRITE or NVS_READONLY. If NVS_READONLY, will - * open a handle for reading only. All write requests will - * be rejected for this handle. + * open a handle for reading only. All write requests will + * be rejected for this handle. * @param[out] out_handle If successful (return code is zero), handle will be * returned in this argument. * - * @return - ESP_OK if storage handle was opened successfully + * @return + * - ESP_OK if storage handle was opened successfully * - ESP_ERR_NVS_NOT_INITIALIZED if the storage driver is not initialized * - ESP_ERR_NVS_NOT_FOUND id namespace doesn't exist yet and * mode is NVS_READONLY @@ -90,7 +91,8 @@ esp_err_t nvs_open(const char* name, nvs_open_mode open_mode, nvs_handle *out_ha * @param[in] value The value to set. * @param[in] length For nvs_set_blob: length of binary value to set, in bytes. * - * @return - ESP_OK if value was set successfully + * @return + * - ESP_OK if value was set successfully * - ESP_ERR_NVS_INVALID_HANDLE if handle has been closed or is NULL * - ESP_ERR_NVS_READ_ONLY if storage handle was opened as read only * - ESP_ERR_NVS_INVALID_NAME if key name doesn't satisfy constraints @@ -168,7 +170,8 @@ esp_err_t nvs_set_blob(nvs_handle handle, const char* key, const void* value, si * zero, will be set to the actual length of the value * written. For nvs_get_str this includes zero terminator. * - * @return - ESP_OK if the value was retrieved successfully + * @return + * - ESP_OK if the value was retrieved successfully * - ESP_ERR_NVS_NOT_FOUND if the requested key doesn't exist * - ESP_ERR_NVS_INVALID_HANDLE if handle has been closed or is NULL * - ESP_ERR_NVS_INVALID_NAME if key name doesn't satisfy constraints @@ -197,7 +200,8 @@ esp_err_t nvs_get_blob(nvs_handle handle, const char* key, void* out_value, size * implementation, but is guaranteed to be at least * 16 characters. Shouldn't be empty. * - * @return - ESP_OK if erase operation was successful + * @return + * - ESP_OK if erase operation was successful * - ESP_ERR_NVS_INVALID_HANDLE if handle has been closed or is NULL * - ESP_ERR_NVS_READ_ONLY if handle was opened as read only * - ESP_ERR_NVS_NOT_FOUND if the requested key doesn't exist @@ -213,7 +217,8 @@ esp_err_t nvs_erase_key(nvs_handle handle, const char* key); * @param[in] handle Storage handle obtained with nvs_open. * Handles that were opened read only cannot be used. * - * @return - ESP_OK if erase operation was successful + * @return + * - ESP_OK if erase operation was successful * - ESP_ERR_NVS_INVALID_HANDLE if handle has been closed or is NULL * - ESP_ERR_NVS_READ_ONLY if handle was opened as read only * - other error codes from the underlying storage driver @@ -230,7 +235,8 @@ esp_err_t nvs_erase_all(nvs_handle handle); * @param[in] handle Storage handle obtained with nvs_open. * Handles that were opened read only cannot be used. * - * @return - ESP_OK if the changes have been written successfully + * @return + * - ESP_OK if the changes have been written successfully * - ESP_ERR_NVS_INVALID_HANDLE if handle has been closed or is NULL * - other error codes from the underlying storage driver */ @@ -255,3 +261,4 @@ void nvs_close(nvs_handle handle); #endif #endif //ESP_NVS_H + diff --git a/components/nvs_flash/include/nvs_flash.h b/components/nvs_flash/include/nvs_flash.h index ce98f3940..1cade0e95 100644 --- a/components/nvs_flash/include/nvs_flash.h +++ b/components/nvs_flash/include/nvs_flash.h @@ -22,6 +22,8 @@ extern "C" { Temporarily, this region is hardcoded as a 12KB (0x3000 byte) region starting at 24KB (0x6000 byte) offset in flash. + + @return ESP_OK if flash was successfully initialised. */ esp_err_t nvs_flash_init(void); From d66f05c61b561c04d46cbe2aeb1cdb54fd0c2fb9 Mon Sep 17 00:00:00 2001 From: Krzysztof Date: Sun, 30 Oct 2016 19:38:44 +0100 Subject: [PATCH 141/149] Guide on documenting code --- docs/_static/1.png | Bin 11412 -> 0 bytes .../_static/doc-code-documentation-inline.png | Bin 0 -> 52513 bytes .../doc-code-documentation-rendered.png | Bin 0 -> 93186 bytes docs/_static/doc-code-function.png | Bin 0 -> 194398 bytes docs/_static/doc-code-member.png | Bin 0 -> 75946 bytes docs/_static/doc-code-void-function.png | Bin 0 -> 68465 bytes docs/documenting-code.rst | 126 ++++++++++++++++++ docs/index.rst | 7 +- 8 files changed, 132 insertions(+), 1 deletion(-) delete mode 100644 docs/_static/1.png create mode 100644 docs/_static/doc-code-documentation-inline.png create mode 100644 docs/_static/doc-code-documentation-rendered.png create mode 100644 docs/_static/doc-code-function.png create mode 100644 docs/_static/doc-code-member.png create mode 100644 docs/_static/doc-code-void-function.png create mode 100644 docs/documenting-code.rst diff --git a/docs/_static/1.png b/docs/_static/1.png deleted file mode 100644 index 4920892781f90c3db323f82033866c3f7116c073..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11412 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%0DWzu{GG6s#(R>PU8MZ5tw{ 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 literal 0 HcmV?d00001 diff --git a/docs/_static/doc-code-function.png b/docs/_static/doc-code-function.png new file mode 100644 index 0000000000000000000000000000000000000000..758971ce7271bb175d27d063643f21c03e00b8df GIT binary patch literal 194398 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 z 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 z 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

;I(ha`GRE$HxxVa*M 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�L0f{4vdw2UZT)Uzrn{kD2V*|iq0ge6`hc<+&Xn6wVqg@28oA(tzF6#w36}D+7=riRKhC-ON#4ZUkQD4? zS9WQyOI8{3an9^pDlc=|#(6NPujQjex=&SIK);(m=s&ibFTRtl<&|cA6K>S-?gJCB zi<5IvB+@fO;4jDV1^s#p24Fy{*ACy^t=OAIZn-KI11m3Pw*phn%T2&(6&gl>F(&|w zTt%c?jVV$V*c(jHV~u+*R3&OsrhM|}qSOzHzOe~z_Kf|tS z#+{j17c}J~u+1&N2MF0j4`l4sOIC}6^D&KOQFGP#;68A3>>G0tVgNn91}QuC6fA6n z&qArp*|^0Uq$t#h9647A!3I&Hg?Qfalh|iu+&if^ZeSL*Xr1^;fd3EK zOtR>HziH058op$}FaX-^w7>st`oAV^w->{8zS`U6JbX$?Pba$5UHi|vI4m;`Y8$mP z@DTXwu{b8i>De%aOC+|52gI!DO36JLtqXxTMD48N(bNJ=Fe`%;>jU8=aFjDrI4%oG z^c?JpBs+8d;~z8rO-SJs$Sc>AhUX%792T^O9O{2FlE8&H`C?_S-<(|nAev=ws)rL+ zJ(g=1HQXVeA+cnma(8Hkk^i)w<*Hea3Q1cp_~(+Y1UA79rLkAHyK8CKNikr7~yPL zx#7E8D1TPW2<-Y7H+ot02~G`}C~1<0?)!!4fEUS#-?Y)6CeuNb=_>GwJ*eex78MHt zKgM>tJ(W0b{J$fz3UoKHnw(c3Uw_yn@;dw`vC!(W_i*+<`)7NoabX}Dmz@UExBLj@ zm2tel*KMaaQlyOWo|2{DBh-V9j2e@sRMvDd$Mj0vkNh2>#vQ1yCC%NdC;EGE!7uxP zw^hbEY-tk0g#yKr-8;R|Iar7eTnuXXv+-7Gim->*`Mcey?C*o!4-a;R>LU^-dIWEg z6#q zn&t0t9E-%CcfuUtARLlnZ5~;v;t0{0)!cOi^24XLn1;pIBWr6SoSso{MX_D?<_+mY z-iMnU3$XTBscpHV;2n*y8{hW% zLE+ME%Myyy@|DAUDr>3!#Sj8;lKukyX|Jc#FfhfYJv0k8Ob9FR)|G$-nXH7w zk2Zl!!H8x)emfYL&3#xn)UUc3z0{tTmlr=?pUG(kjT5uFm+Dd5s#$Js`5>N9gR>D5 zx-~gxW=VhwW)n?L-^?O!r|#dMR?Ulutj&mw#F~oX4-y)v1G3Tv?08>m~{M=-X#r5F|PcqeTWGl*$ewT*q9M%U zjl%H-m=X0enW{JqFCdv_Rp7;M>w2%5=ET`SDz3B^n2d$=*n?O9_+ojL+FTe$-vWG{ zLKm*Teq4@132x+_nM$7;$zDp%v~Mu6QFb-)qhqK(WQ!h7@01&#E|ruQVGlljDq`12 z@JzYbsaD@ibToks+K}-fuVEK>*D~f3vI%3vG-fRhz1k_r4X{AyWvtgL+KDA~au&7> zn!xe4#DaAZC=rspg_FGKd3`pEjZyQV{~tG>5=y5F3;y$TTjN$b>%?*M zQN0h~I4F`EP4C?6U}cxrVu_!K9%12!l~#=Aa;%3))wr!ENg>*q+lTAvB%*%?5Up8B zf8yohOAdJOL14*VK%%qHC2vYL@3Ns_b^M|4xW{^#6pLZW?ygVoyFPYMxfW!mZ?hGr zq*?Q1CNf+qUVvDXM5Ih`M($c6PMoGTAy?gmp2a5-2*BxjmSpB&9_gST$IdD{IGx0; z=aH9e71HAZm&HJ5l-|bQ#}LcOa&^Y6q+TqBwxeImQntIxRf7I}l|NOL#m$P4U0f8Q z{5?}H|Ngu>9f0=Tpo+ozl#FjW{46NlGKb0h5$EH6m4Tpv!k%NN_V9~bEa$El59|4$ zyBYE_KJA&w__?dLhr8R@A4wx=xxO+%;d?P8ZyRSjtbe&{<1IT4`r3DbIAeM5P67m3 zIp#7Za!uh7AguhtcV_d`vl`MS2{fu`%$A+#rL*=>`;?Z3L)kU4>mM2${EI4%BpkAu^3Xw%#{k7*QZYrxLFPQT8d6&p2X-h zu#0%g{8E^c@zlk*jDSnquFY&QR?bu8KbqfoQK|1=6`h0eyz4VlerJx`+E8!5Nf5Ua zx%_068I-=(<`{m_%p0aKQP%e9xz!ytqdDj5wz_vv1hud*mcV0LW)%V2!tPA-yB1M0 zv;9+pjk5&v;`M*UE$eX+TtoC!E`7D4r1c071_^_DL#ycGd&4FZ)i|uq{~xNpGANEN z+BUco+}#}pcb6a`xP$>_aCdii4ek~sK*$UPC&1tqBzOq!Ft}Ut`0lH^uipMKzox3I z`|Q(a@4Z&9wS@EDooXFQkvcx;wK^=kc7_6zPFsUrDCJ2?m4&O;p-vkm6PY(odDixM8~5kZJh8Pyn*&Mh5Gn7#=3Q(ejZaH0s^=E-iY7sCCE4;!*0`| zNOfDDHH)ch`blblwMFu&5LZ_QeIK!;GUiNXGuC+Wxr9anr0jIb6yy`DF_^xFcvs4g z_T=j^`S1Dmyf{|-qs$tTUE!+5oJJ*?>XpX=O$#m_{4cr3-Jev~)crzMIQC*$X6ZCn zf7rbz3;49;X_0~M$?QtIJhYi07o$YjoJ)z(L#An0$XkabvRs1BmC~ua8#h}=ZKL+< z$CCR4Kf5N(g`BNV22S?mLBS`EWd#u;?$L5hS#(HWzwaQlDq_O?v^BgN;>VR!>~c3!F2uesH#-PZKup~@0;KJ_oD zvhxXAZ{qt$ZqQqDQVh+sGZI5&;bJm%^9H}BvD@sm$~mK!ejUnD)Uv)Z2s`imH#WV| z*BSRm9rUq`K??eAxUC!5wE;=kB<7p->-LlAt;2m8HSkW~>MIv4pY}bmClGa=8HeR= z3@VA_tfEhHeyLRD^Y36X&olJl1)|IOI)jjOP zxZ4MTX;-qQ;%7~~A}9wmK3+!s)lGhK|B#mWyl+QY{_2(Swgxr-c|pE-UmZHOS0FR7 zuOh>>nMGbigt6&Fs8hw+8X4X6arB5^KyPWxQTI0+K8<%#B>dh6fl@Ph+OkBCkQ%6j z75W@NKHjQ-F|0c8g#K$u0whuI5M=hv;&2jo@=>JG+Fhv5M-b|#gSMC-v((yMXx@oa zAwN6-=}qA^uV5 zj**f&V)X>VN^eRLBjmipast|GH=^*WIPmfYVqWem`cSCqqkJ|54xL1a{2lyy9siwd z?yH_KNt(8){6}MS2~C=QfjZnDcz@>~SeKao(D2kSwF2;au8O(dYPJnycc!ly0K!E5 z!;p-Ptrm$hay<*JRZkOP|2634xE;e^Q?C4Hlbsn4e)j{`_H1Ri1+QtM#q;p-|F~(?L4H z3(?a<|4gX`A-ywjo6U6Sqpq=?=C*l!4cqyhZ_tlNp1Va8iHriasl>pnmsYb>=UXSP znVD3j`huSa1*ZcuCZ}$e^m=7K!!al z{;SW=Bx#>0{_)Vj z*SvB-=w~2l*Kx#ZIlW&XbrZ?mr;_QRg;q7AljYGe6n_m&048-A>k$aWym(fjbL`#e zGJkR5k|sEZ(N#G8Z;BH!sc+Ur))~?Hoa5KeE$gNS%k{#TKR91y*)d!Yis~Pn>8F0I zI)~L&(PqcOC%bRiE<27ktgH9T|>SV$7vEC zRk>8Xw4{_`N2aAC3UhWWITVL96%)^5`g@uR*m4$nGuWPwt>ghJKK(?Xj_MGBoc7X1 ze{rtQH|ZbiH+3pB9GK&*NW!hfSTh~^&=Q~G(N!O>Y~)>)`R~PpM<#b!Q@ba#CzU*# zo+2?Kmj`7h&YcRuQSPfjMd!)yG#*z{xUeUdJ2cQ39$I+S{Ax7`vJ6TpKT@|^DMsl! zYEDs&6#+a97+Xl_$%B@rZ<(R)>pEsK-}9oYmF`&=uliz?oTk21n^Y<3PDU>&Oo(+S z4puK*>?a>D7Xrg)fJ*0;k@ZR39?~cW6?8Y)j!0)vCL6D`KZ0+~Fp{d(PHMOc8X^y{ z>dx8h%Wc2MzmcJzZmQExl%0q{y!8)+@>XL$00+a}kvnBwmC#S+o-WStn(MwWF)S2N z8=v?VnbKlCTs}^x)nu$t#PVT(hHk^%HT7F${=2DmfHz9hjsXBifvg+c3l8)gER{{x%io$&Y_UMxgkbVNzkb#<&r_wF{ev2qir3t~Ej{a*ZvHr43(`6prH)=R& ztT~>Ia%<1;cOK_2V$FpJjDHPgo0Bv#%-E4iSb_x(MiX3jvRD(A0^}mc>}lT$*QK8i zT2B-&DL0EY+y#__eqYJ*wgEjt#P|ku6 zFRtvx56WW<4iA%md_J8Vk~B0kBL8L1+0VJz=K>hJtzNc&p0;f#DBT^cCP+A^?Ch0U05}(`IU`X zsm#tZxQc-bTNOe^WWqzwmrX-Xq(j&`Jw5gJckw@a-tW9Ky(FMX z-qKhBw~+d}_}F1oR(NL`ONZ~DH$APOZ|aCOU zm&C0eb}i5Q@$db$h&nXB%63DN7=-X_vO}@HxRf`Y_it-M?hH(QRmC&PND5AV@!9)C z^Yxh=Wkg;m5JE(A^XVA3q&xsuIaE=QV4df=G&09I(I!y}B+^V+e+4$$+syu-%%ql~ zNduXY98^HE#M^NWd}GyNdpA2LDw=N>#Scup!ZNlU&r9m!ov-z9oW$)%is<83(KJpHy6$(r-9 zxR8DFR3(1Zb$_mK7ZwW~TW*G1Y@UM1rf5*j_w7J!0VkQU-FwuaN&hV^3!Yjo1C0E9 zjlzrNq|r_hknblx)DR>xEp0^}faZA_ame@f^Q*$}2F+sh`GYc^x4jI*(M!cZhLlST zt2+YfFY$ek+=-B#AO^cv@vhTIlw&wJls=jXErrH3k$pHas*+WRD_Q@T#WHHx->UJ% zac}&yVn97-jEfKKP)DrOHhEev#^T2;P?UwZ{C8Tk)MTfdXtQi?s)bgxjLPey+7AqT)XffFoVxd7kUNZydAQZ z_1?zZx5KpSv*!yejf&0q)k5|y<}0caND=vbBT~KqB<}H938iy7U4%m$-h>z2ku+Ep z21FOo}sJIS7>MUW={VlvT;4K4Oq_x8+jgcKo4 zJVk@sJcY8a(T5XxV>h?IZ5b6aOk2N`7B-(Q&2Mh8$)hSz9@?61?VtJkBpP&~z@RLl zN7p?Ub^YTDnT#k(rw*F|qmEBy>CbhZtshY0G;Qjl5&1+vpA>#0vNMTxr0B4?$*>WQJl*+f8wH$g$qnpmgt zj-1gUAGPkJBEzDE^P6CD0(n!w_PH5c<6Cz_sBrS$>nEn5havBCHUP9C3gL*~Rl{5i zM?k?ttIV*R4~8Sq$~|nCL!7(Qg6R|cfw4=izqW%wuA37uc(2@ZqHY|!#lMxA zEw~GRl(2|32MSv{`t-9O{NudbF65iPVwxmbzn5F6)z)EXtaTS@+?_53lsUIQkF8gY zyvqdwk6xBck_0)v#&Y?;9lAY=X)nI0`8*tD43}JBpWToOMMwSM6(J)fGl5B@64>wk zN|T@wMvg>%KhU^Jn#O|-;hT%uKB&bYlRj&MIMsb&BVZMIKiC)Su0^)2Tbvb3O!ScB z0~)&KMrYG*>P(iZ{;ofQD;@Q|>4eW>1e8Q3gu5V=Fws!G80_>r4XBI`VH9)kx5xk4xL zzG?Sd{HHi;cEr5>b=(yXEQLGT&4a^&Ysklynb2jN$PGyQ^ zo?SKbSw&8R_?DV2{E8%@70={OkB;emVj~fGO4L!*XahmgN2$iXTKu94rM_JUDtAuU ztuxmLlLY*<6^L1uJ%6DacVG5Tl%W38GOXj!ZT6@6fwkPa^mEk&2&!H* zKedQB-9uc-{{RypS-Zod?L=tu%{M%ZG2(hv<9ONXw4WN6xadNIBY8-Ur9&}Y|25*0 zjvZb7?U9S`8rX)c!^0}9$5_m5Z$O8Xb?JFUQ9B;X%_?!mk;VBXNp1$Di)VznrL&pz zLX6ZX@G871J8u5j3OpcPWCHob-bbctWSGP?S4vDtfBKTu4hHeyfVVSCIl=QL`PSR@ zKY|0cq~$gB9}C|&)GKNF@JU7nyGyeYv+oLx{N%(}>9hYQ1e?s~phSR@ZjAoAPKj42 z-t7>~Gb%I|V@s=3)E+uwkmAJ3n-?U{TYWot(T2AfyFEh>r$HZx=QF>rv@LsQtEyWP zxxH35?f8#*B-Zu(85ux@+`eB){g@Yq(`!Em|DbY*OOHIKFH8Fjm! zLJ5v37vB<1IrSafRG*1UOqAb7m=h7zS1N@91id2gz zY`OM*+33eg>S_}d{aOrr>I(d>tZR%B;;Tvf%;$lmxi_oZR4E}=SXk)G_*&SwuJb%X zeUEJbf25z+cmPz~7y=xKRD9k4Nbl>n72#NHF_$(%G+s4dCzKV%2^$vi8K}4se53`W zpcB@N!mOMw)9Ln@x%D$S4D{TD{`I;+QzbvqXxh9ZUJ6_lb4VWDbaYvYswz$0!|4|) ztAy>WKIZQ{ea&Is+{iUMa5)Q~!_-+&CTscu@w%qdSiuhig7ZKm4OK*ym6bK~CO}i2 zx1iCTncUV?X?BBHxBw-egz8frs}?SZY( ztQKC5bqw&CwEi`1|Uo6$mxqq~rg84uBmj@YSM&Dgg4&Hy6zdOMZ+v!!;Qi}9Li$Kb&V z>A@Oxu0)Rh>HCF-8xFwxLNAAwAjqhsb;9}WV1T)L2JqdH-Y&Y)17$#xm+ZTAR4v=> ziD!B;K5I@MB6NqMc`U4NITdV=*thlOVfKV9Ab1$hz5hLxXmK^U+-}=}Me1fhX@AeB zEcu*REED*Q?FdQKydAge4tC9`csOw|8ff>yVs7k{yPrI1(37(WHkA|&;ZQ2PJ6IpQzWj}0HqFHH0w2{E51&tTShy1D&%Km96$NZ?O)3XLE6K5??q776vPyT3WI z9?!M=aZ6aO`IThBh?Iixw{%D$hQ*71vPNQtINRe#tM(5AVQW}!l66Gn`vzb|nLyE^ z@zx;IWpwgR)K;FB;jVcJ_a%`x;o&L}pCH#~;Q*Y;X}FU$(VrZRKDwv=k@==eB9&g} zH9qW*?&P}^9*-7$h83c{2ntUVvA!wT^W_7R&q>G#Lnf0v}W$|+8p%?kyxgvok!^lv|=;s?Va57Rtcx%?Ej5YH_^(;A$KQqu-+GMzv-R)%q2#0H}Lz za>JGb38OG}{G}-Yp4UW92^Q3NEv7WP2v0me_ySZAt?Ki>Wb89|+)*V_&EW)#D3!|V zqFI;87+fEQ&p1vos4CYMOZ#O}bW|&L zWUj$vCJ#h@%xiVWu?yO5LVU8?MX2kgY*sZj?$wgozbUMCryCPUWM(->p)%J>=&2oX zVSjY$Qc+f^1_bSiQbv9?ExA%Vq?ZsQ{fpuPpWK_!_Tc1lrhlS}P9#U^6Tjd_6K5Sn zPZf*Q{er^gD%i_a++$)~trO(1vtv~Otn2i7_STO}#^@isMr1S2B^mtpQo>exs9r5n zU{E66d;ATlB$%av#1d#FUG!><=zw8$;~g`2+^B_RM3P1s;xEnlCA z?9#U~Cge^VT$OdRNPy2NrYN;|{A}mG<=ppZBkz>w>qK{-aVu=#iVt37i7Na3%Gw+Kpn zf)scOjij`_S=Tia4S|cM(A*8hL!8*Wb^2`dm`sP-XV=h2Ya$xSUA@j;Wm%@__Y~=~ zUBAf^1aAF`G&8O5RXQW^L*Z~cmBm23(%>_*S9qURsc$j(i(fGlDqiyiwx^UwCc=ZX zJydNu(c201-&0iwtO140mDu| z>XCEh)d-Yvea34d-2v}27Yz<)oNzY4ZuOTTlv9^|mjdnq?0uJqt5EXg`f%|*-9)Oo z+Z8Uu&7F1!ze-0fa?|jivjPAxFh8v%InbJo;FCZZOC5n`hH=nrBcH1Q3*6|B0I5Em z@#L6$*&ENQ#%mT^%hP%%jh69VqfQO3JDoCl!`SC5W+O4i$ieZkzA&R*($sLya<8NHOP%qFh{$OPL{N4M$O#xE896!X)eui{CPOi+6UJf;yB%y*6z$)+9 ztvLK=1ja{iVIH6)VzsV|(eV#76JDUa!}blgD}}#ks0}5{sdzL|i}$XMlj7Xh*w^br9CS zPhdgN1r^~!hUy#gUfS1p$9bPl8qwyzRZ=$;$AFWXy$QG>I9)90i=|DNx*E#RJUpxC z8b_^5+_IPh$hp>mAEfN8s6S7njkn?DCaYb7!N~<}sfwK>#N_oz{+Dbieoq@79vD{h z5AUWl6)>ele+rF!h?jNKqu^NX&!;FtmseE{Tf@` z2MJ{f7W!2&xp{q2!Sx5@9+)8S(`;}sl54!*BhE?^9-7gt4p|Y5d4Rwl*5U%B$(x|66(V%lbWbwCZ zFxPd(jThH#D1T;DJ;vd1)68A^`2BZB0#xjGLt%f(a~qpcvm!tu3Y><0&ay;np)Ul* z1qR_do1CJkM5ihJ5+#b8u`Z)zb0Qcg=D5S(t`hJ~tA_Kw%#exycoS~WKNd3}j}4X& znuw<;EGQ1U`%92u{@BF-Ocr_9ozg$?2I+cFT(8;?v|8%-JyZhy?u8SPx}zEdzvdqp z#~!&Dg$;l)4j%mZP6PRnOS~=oX+9+^)2I~J~oh#_dVPp;RS zpH1R{g}1-MnXBcstf1eiYSZ!dV`uGAYV77Vr!beJP|$8SBeoVYJ14c0@6kWN&%Fne zsE;AP97TkIm)AGxQ72Jx(Ms|U4;t&`l0uQx(%~kLMrnr+C%wE`ytU6^F7!iveLiE}!Lz2QvcE1v z0B|iWyv*4%-R9=2&q7E%-+?w>VdS!a+U9maCbr9?#ON^VO-hVm2i=&)XH?<319+LD^8in}VprSVDp@-zxE+Jm~iBPTH9hc(A z<{3TcwZSvv3{S?id&P0L9G%8Uh0Fr<0-3jOU?_2)nv(b_zxvm+*Y(0%2{zo+R!hMZ zmM%0LM7FfitGK=cBgeAhQ&u%{4$tIN=bl3Y&gy`FQ4&5XEcCkIymsYwKHKf{5D}_4 zHI?MS-*0L)3a?DxN_}8Lv(0)Lv?Jodfso}XBu#0W^Dz$Po2=_=Hg@#EkoCx=kOlHv&2hfN zX@4t-)Z+LjV_p@TK0w^ZCYkpN>aV7Uknw@#{2`-IG1v= z7gqlKh{oIAQvo!{sw)_=XvdkcQSd32AQ{yf zg+7i~BjRqC(9LFd*`FPioC7**P!xy}izEqQoTkmetkZIgA27;D!2-tuT=zAwy?}AX ztdg64V}!*QU)V?*Xr&y+E8M0RsZD}zEq#s9bt-3rsOQKX)6+K+Bmb@d?4NXA(J)$` zO^kOrCZX+FgRUA;;{Z~pu3Ux=@z?Udu@MqL$|jYx@a-FIuK%5>&aojydS~K|eCMcD zvej$~c(dW~7sKgxdEsk25xV;m>y}d1Bnz|W=hbH06sa^q4n9?J6hW4CJQvnP0J(+K z!uCd9Zl^j#U?$Ut4s9Gwd7~9$nQXQW-JhR0$W&*>IvsmH+M}M5G<i;;@e%EHh?SH^a=?r5cR|n=l56W1)SCINUkUGm?93B1KH@G%T)8@t#Q({6>D!ti%x~XTk)a&jtPK09ZDem!=ahWB1$LJuN|3=zxBcqZGM$iVF zF8OWpm)BNeH@WR`QT=YEd}<(o+stJTY~8D}PDpj3sR%faJt%`Bek{>Cwu)}*aTn2| zjBT}A&iVL8HHy!W*QF7hM!z2}gkGN;aEUb%V5Jr-gS5Hz;J-P?uftbH-x~#av1a>m z^<}=qZjN$jO<8;*E{VJfwW4WeDsEX;4xh^m8u6+eK{rmNL7QYWHc0Nx$N&VK8BBPt z$N0O{@j1LOJ!D~eKg=^83#gj%`hhZqi1)F$j5me3<^pDDXN8|YJ%Q%xW_)DRW4CUp2 zpzIq|@|?$LOcOSYJMlC|+nxm7yRBOe&WJ6}NG(nXDKwSXMoFz?87v4>lKsU13+5FE zEm5)8Fp1&jSxP>_{0uxF!oLe%I&PtybX@4W(Zf#g8sh9L(=?CF?bL=82#P|PsJBDL z2SXOAChn#2dCZ92@=4-Ld>|2XwQhdal4eC_@f-`PqZniIPpQA$xn@Q_3zX(?x;yDm z1cVaCv_yJB9^JSx?WLf{;X*@)XuoF8uJD4c)VZYnB*{wdcq0UN4NLTs^&=I|ulM5* zmNRk@JH@Quy-GTN#dSy11$b~1uIW83E0H}01;#!~}x@%}gtSH55 zj}DOl2g4YoUY7{|a;+uQvFG(V+$5(AcW>a&R71+DFZ{xDasQqAc;h)g$?V6c>54$% zKZrto3AiFCO)2r}p%W|ZNC^54o4`1bDFtEhh+_QbOd=vj9Js4b1`uCXugVMVzKoeO zZ3RZG1_|jiG7nPX#!^8ZfykdSSvF5#$zWOiWn(TA3CgBPKiJJ5aJ_UJ4W%W2sl~p! zK6Qe=BMEpizF!n?@9k9SDp{)^1Bsa${;I|V|7RQ2O|3ZEm3`FCdG$Qml*r#e#1dUc zzU6+>(Ey*4+;KeejRnOVvJ8mV>O-!lDAkxaay2y{D338EYp&OBBPPQc5TQ*gE(B%!v&TSu7VlAq(Qa)EadO2J6rZ#$6jHFxwjKt96Lx z78&r2#=8xTF~+*9PM+f2c9o{i<>t+$;`sCr=yPu3!BZ>1)vr?v3v6HurV5A4`5Oy& zOvXwZlux?L{#sw69TknR{vc)uzYUaX0c{Vy(-Vf$hRn8QnTwmY6OajELYk0BxFBTY zMlZ%6m*~H}c&drF8^&X9MyjR~s2@)RijFpNxXUSZJ$nZ7en?J>b%l;^t^NGd}NL zzZSEB?aBghne5XyKP3Q=az|zh@LR-jBE|PMbdsC71R2&Cu_Db>VNeg2&s+xhHhPtM zpqYJ)C7Sx~OC{Z=c3kNu#_3>tF@pNNAFBj*iFn>2LNO(3^3unm!qj?IYKpPZ^b9?4 zwP6_0Fe;H$q(5X`*eQ8TF(g9<*Gks=L5#~-h&=KY?)f}R^n9>^Swg_f*+bs^FboK} zLQGnM5(wOV?Y|B_1{{}Cc6o5pi*Rb=Z-+;Bl3E@#G&}ucxCVBRsq-@PLJG2H3PeDj6FpO6~d)Y6A$o7}hVak-?Z1A=LNlvofEeJvLT6|In62flAh zK$++e%4~Bif`c#|<~X7rCnGK8*=rm8Yl+Y$n~~Ln5b;+_MQr7DAcWa8KEq<;QEmuB zK%nGVSi>=i#Zh;2E=+XZnbF{>$ddZ!9`J2T*~D#cy#g9UK*467+zBlC%=c;%jJO^= zjQM-+_>AIyBl6K!9BSIhS#h)g`QHq5?F1QC`uDj%2;WzZA4S_2A+oE8Y@9D6>j_nS zeON^alw-~ofN6C9wy8jr=83;QGXapTup@i(qg0+EJh)8q(}4i@s?Km`ulj1mSGputjiYGnWXRgCVN8 z7nkCizC{aeyKU3O?lHM2UR@8v-cK81pPQuNONCla#^qL2mbq+2@Zx6jIfnqcdXEq_ z7ep?m@O2ihKdQZc#vhp-G4cL#h-yD8*L#o4qAq_6Q%)NG*Qm*wQ*+U->!KoPcELI~kOw2C zLlG|Z9btBgCGjp-92f152j+LzBytWGDP>#15-@Ct_DwIWw3RhJqDeCCL>X3dRD_*& zQL5-*MNPKI5vSm2KwMKm5vJ5h^PPDI$ppLY?Uw}OQx+4K$!!g9C9ntM;>Ds6o~AbX zDkIOwaEC13zV&OH>igTJ#^(JI`DtQsD7UYUC37Lt8~r5*eACYMSty{mGTM2tPvOaJ zG5|-@88_&KiZAG&vB4DF&fuHH>aT)y8&kYw+2LAzFBgdx)L6#xz2r4{HZH5Tk%*;T zh1T0PW4-6MD>qYKTQdJJcJ@tt7Wxe$`$k`5nZL^))*T>p{&Lq_UGfnd2h7i=ou3|d zzeJ{}6t#7)i3S%Xh(r9?1J#WGbZs>dZQh6f%6Ry`js7fOk*2MOGe8 z=5`Ju|8tjRvHe-}`Pq>Lx9Z@Sb$waZR2u`XTxn65A&=L3dCrhTNfaC65|E9A!&Co$ z@Q#<|Kd6hUwCzVNeny3gyBj+kb*PX1x1V*qY7;Q!t5iRqS0}b3n#xB1DY;S0*TP+P zO2(e#VbTmWd=pFM$pxe9zZ_&gin2z8k2hL{u}sZUqF#zIiE<3UyK`1a?s&xM!X4;| zF)C$KnnD`h$~%gBeU^sOEIz5dGSaHBfIE&TtER*czliD?@qaMlzx5uCbj{ZDSh!*1 zg{6^N^7(nt0N=*n>`<4>FV74Kj>#s7qp5XyB}VY>lgU#o|E+ z98-HbA8KISDC-oxT2*JbV-k&ZbT!m#^&=IBfP$+*spupIQa1Nc)r7~$eEazYFe)!r zbHh!8Y?3u*)9ItI+LDr9_51$tU@U<9KOR3O#q9|t6>Jkx>Z|ANGDV?s#1?SJF^N+$ z&S0Lb?b6r6c=x9ul*KQOwyLQKKy$7oL8Pmxr{2`c{Qx_X0B+zgawm3xf;g~6bQ`2o zJZ(HvClC4M&bWYDEr4Ji;kkCqcN0eG!wQU8d`Kp1@e{TwX@{Xp?2M?1h0$~7fR`WG zG99qoCM?;9#Oh%em}2U>$%$*T5dOwDyN0Vr0hy#M^n#3QPa5d_C=V(IRF%x_5yuwUC>%oP-@i z$%sr?iWUwN@%`a%fFjP};I9sD%aWruxc?vLuog64Th%u~6PjSwW* z;X%L;{Bz9N7MoQ;P8j&(&e@!hP)8b;GlRy-FbaB!*^8?w=>2V-D2Xdk_hEX<*KW+u zg%iW2^YbIkm#=i3SL&27wdUXEr5;h7;1H_^$d2urHnT$%e{NbR2W16|75D>krM>dP z3=o?-EgT0yJQs@t0@lT)LgHrCs2o@h_Rrm_0)^IYHnCuwbSJ@D*9W(UaRK*Lo|q)X z3w?B^zV%n1+_=yp3mwz6xyg;Cm`T9TX~;o~(%JL)+l$%<&Gl4(tZce}gx57=KjMTW z_AD&bqUV3#zekaf&4}5yy|r|8x)oZB$_1w}_rF{M+N$){Kv`sr4t3?5{(V?-LR$r- zO#mE~RM$uaMH&txZ%QxKrBEkEcwr8x@Yo~CT_#-XodvzP1%5Sc(O3U@M9U<#yBJ7Zh!*Y`(soy{$EidvS`nA zb~aLrZjz)Lo*&qTchpM-Fb@|!1}O^}5*o2YQRd_G-0$yFmLe^Uvuc_{>=|Xl+weqS z+;;UVA2n8+FbRPqU~4TZ{%`jy`gHv8H#XwD10&9y&_{W6eq`+i*^u8DT)*3i2!W)Q zpX(S6keSNH{I=1ynzgDq2iq$IRTn7%-+#}XC|WS2rO`EHgfub27fOjO8xTxzFke}I zthVEjR|!%&!~^}N=!N(3=h3pGE;k~tL|s{~W@gvU#e#fJtmslL8& zrbh*2(0#UlFhLyrpgb=9*x8`{M?OC}l`-wTcnV5dN)xUFh6M_M2R4LDltK)@!E?^` z;Q!=Cyj9&pX%!_er13#AmztBBH!Jexedk|aHmx)tl7b&R4D$}ZJU!`(UB^n=g5PWX zEy!K2ivS=kGj&3$1Uq?|gbR_?Z$@4AIE7<4JMC$)T$hPJ#PHvsLv?#&F2mlfE;FJ` zLvVS6b%9$y6Z;v?){hX1o6-P_8>cUXY9>YoAMRHO6yL05kIm9iw+vh496>hSe@4n> zAl_{cZ{`8rta*l@n0#Iny)dLP2cP-(szR*@s;U(fUwv6uf_i`1|nVz zDl{vU;L#3+=e1F-zWR&{bUedh3%bA8k(mA*fk0BZ1=H44R4w2J%PFQ?Q1h~Y3;6kA z3JCH1uNkmd>jJqhe4aH)TwqyhuFNu8W2GqxB8yLzNolbllK#ttF`mj+3W?g?*w|=` zXm%XjCA&MZq>f#g1^>Ttt{v19Df{jEMx0QPVnvY5J}EacGIIMa_A6?1#%nZL`&~u6 z_$s~v_*A~aJK{)Nob`)nI~#2ZTH_7sM;fM(~r-VZFF>;J+THMgH}! z?(WcpT}gu3oO^+`VI5TjyUa?jhU4;JZ9qbuULR|p(m`nrmx4B!K0%G6bUI#LS{TI; zw0H*}#_mqw#Gj&7OHbi*gRM$KgDn@ufEg5k>o{o1dL5}eC zqE-l1Of*F1T($Z?5cF$4_i2B_x1VyoDa1z5AZCLGvuJ(CMaxJ-?ApBCs^-r)mH6jA zt4Z@t|1K*jH)t}$FIgPQUd@u@ogK&VPP>UJ)~MgKVn2Ax$PdgAjKuald3c$0(L!G@ zjqWpipy6d@@uRSG@7q9yP)oz7^Oduxp|1PL}OB-m>pmGD=LM%%5DYE>M zKQk+*LcR(ixMZLvgZ?5)rf*AEThAc;Pg=j5m*kuE%(1$~(3Wt=2Z&=vD$ILM!YJqq zfnhMlKMt)o)DCsTTI_1+ruoqWT~0e=#x%<`87-(N`ua6<^LGpy0b(2u#$r*f3Ats4 zhV%lwNEq#h@3=VR;}i6qbbo7Ppbid-y_--~kq^ODYqq%Sb6QX^_!`yNg57St%(bRr zwZH~`AR$zs*1IJ5yay3nhLYFPk8Z+>`Z@u14Xt<=1UnpZa3kndnX zBR2klo}ZJb<`Xe}CleY9W|1|!Dq~_R`lz0AHf?RmW4?$&MZIi*3+g8SC#_(Lf?8Ti|WXs5R#OODwkCT}h&lu68#IIykb6fd;8R>lo0qYs~B%El& zKX6{5f)l2%Fy8c*FM#c?&ZYap*VHmA@S6IAzDNoe%vVSUGBOar_X`cTw8#kC)Ns5E zXzTF}l(dhHln0zGXOu+0ts&e^BLbhE0&d!}L59pU;dQHyxxQtM7>w{tKgkxZl`v*a zAG@A53A3YhTw2k9EeoM>^C9VLGeI-~9vkyfU)3GliKPy7%3t=Jv^knQy|(=lo6YI# z+gZ!EMnw8pt2jsVP79?>tJINIw|!WVzqbeR*DXs8u|1Z+x#JLF*9O*A+FxE+QEH3VarO$&lE*H*p=4=uwUfs625lq z;&VqHLM1Zq+3pj^%|lS<+nBb&E)=G6eQ%r^kK2FVylQ)}LzChqRacQeZq(_5Y)1J^ zXh!i1cirhGtY=sMU6C-c%B^Pq%1h`#qGfl#<*LMHqL50O;*-YiyN)h=nUs@+MdjzX zJDb%0gb|0D~?fDtiC5?-l)f{&dnzwh&R&r&T92k$qv()jv9#5*S}b~oBu!F6)& zrk`zl)G9mVf0*%pM=A6rJ(T<{-t)Tu46pW3o^VQY_BYCpIqHp@a2*$1dxvluUO67I zzpe0{47Fm%4lI|x-w{Q4W{L>)HH@AQnmx6Ub#h|owKJz3e%l|{3}k@VsrgE}^x#g$ zlgA}4g?$_TFxZxuH%(7ylASW7#p*hMm7clLw(@bwSv*RVy7b+0sy^kD#h)*HQ8rDM zS65o4T{GyNBtqn90H-~s32D;A4LH-lu&Cw6b$l*uW}<_oxbPG_h4myGq^m)pqniru zgKv^G-Z5ZmsNJ8r_wMMMKzW6!{sO9vA9bfg(>cqHDzXULC}@4ol4(=X7`Y_g1hys* zt2g;DY6TYY2J*vbLg#R&QVPSD5jy#7EJT7j`^XzywgLAW7{D>?i2lb>GDtsRw=sE~ zrbM{E`fx?bmA%#C7km#3l7K1Ws2c!z+*D84iKgd_^)mivh1BoU3Xl>gRY5<`HW01^ zzxo)n4&Sr&ftyAl+fw%h~1#_en9GI-w1+DyRoN<=pml>BYs zfDte;FmXw_n^oDlFcE|FlAUP+#TM%G(CX)qAVqkOGA+1!_0WN-=>LA8&hagsHV?a= zRFEke==lp9lcNX3ds1@ZCPn@WkRg954POOuLn#DP$=eOI5rdAQEJFCss!~D~x2~pg80cCK9 zAvQsz(eO?hX^P)B*wpa?$h%$x+qFUAG0!-+WflSUMSBV2jVexy4sF{d+A80txc(TD zidAo`vD&B%W^Yttk5%zRZ#_!AcU7+3&CpA%QAt!Ml`Oeb9Uk>pGnIq zZ23YYsjy>5G!nC37Yykuo`OrfdC2JLRG@JlTf3&dJ_^Xw%8o5|rtwNz z8eNwBpNb(iBZ+VC3A{p};>T*3h&c5+6N`9zD*z4QVxr{t=&@sHU|#YT%h_nJCuDKH zQPT3FP!n1-@sIhH*r)c??4~{vo1jsP1u1EM)AX_>OVHeK%S9sYJdlux5qG|xbZFAD z@%=lM2w^9&HF?}*Tht4h&BnHU#>|r<|IW~{V1B+wR6q(WF=aTdE?YAUBmC(}07et< zr_a+e@iCGbW}ID#Pb}IKNEYKP5q6%$HUX5uIccF$0NEhQ>ulo}7sp&o)b9LdQfPkNmHu1e zv8|%>@z3l=C0c{xC;_>gK>+t5Hx03z7y<k%n zu(BK9$p-?V1+_&eFA2p-v?8vuYWANZvfO?tX6soXEi#TQ*Y%vaZAzW-RzT+{;P>AY zrf*kEIg><0ft?a%I72<$-0&zwSXvW-kXGFPP5mC2I2_fQzE7$M>O6~DzkU!WWRgNB z*hD2>#fx*v^y&seb(^sd8m!`8I{HptAY9*Hrf$#X7~$4g1^OT!BQ?7Q`;?fawXOwC zWQ5n*e2ef*Vp^KA+=HX!?vf5O@xicl7ciE1ol^I6#J1gvcZQwh-M8?UrjAomt8fQw zX9KoZ2Ad#U5X^b+#X2LBl5Q$(R?^K?_LuY1`{H}MKCPr1T;+WEw-xl{1*F(R5=bGi zr`e~?{&cCC(LqX5Ydr%2FYA4X{We>dK&^;${x8b24+W9zA&1|~Uu{YM@C9E;D`FjH z^21KUCf`$lOQLhAQ#Ysx;kd6GAq@FxH;XKA8q0QkUTd(u{Omcz&s&~0Nbs+S*UBEk zIzLLC6k+X_BuJ!((u`__x~Qnia^vVdS< zX`e@j;LX-4NqY9YD=qoU8LjcO)VR9BC^lI-^O}}1Y8H54RyWD=7w@QR>`8Rw0Fz^|@QT3QWZw?hvdRUkuF+Q^liR_J+< zZjQHJSz9UcE=LjFy0&3rL<9rT*xXI`84I(=B_W=%5zBpZBAa1?gd&;W$B4F-Y;{&-}Q)cB2Mf zx2>ntaW*G|gG@yRPAsi^S|*>Oz8ZQ`7g?AhS*yJ`sY-uUm|w2reh@_aWlpDfGebV$ z7qf*^W&q~w}c8d*+9T%s%6uhu9FGA`@-+z6s@(yjefwJXSHC}f9{ zjU?b=^zgIlZDXpi)ApOqEuZI?+^A+k$(OPHc*}lKBMl$c-=(jG4_hrgufk9xGqsxY z1r;iIqh0T~x*~e%9_ZUymXIyLo55WkiCCT*BfkOOjc)^M&XTby3&KffrKg@~u5$_X z2uCWG>-tD2N;@I>rOV4aDf~Qv0DEafuk6*E-6wt>t}GX15H2Fs+HeD+xU>{9t2hgZ zXWY$M@-(l9My!{r7$D-#G9h<8Y2r;oN_%sWmN&j1nM3l|amTQGf zT>G+8wZN1t%dnzlJ{_(AE#{xvEjtTpoEU{Kz3lDFh>sN-@!4n88?QLH)##SEevDQ8 zAt2-`waBAOZyDxFV6alIBIwy4dRm34Mwafj-w*QjiV}?p3R#aI+#LQo5M%i)-Mq{w zmK*D@dqPjPUmfz-lNU}9;Xxeie`L$WpLyE-vo#%091Z>$ZCzq;Y*IFUo@;K!E9^GF zh#T*V(@dE<5BrVj*(UciOa~*ZGf5rRE0ASc5c=nf&8BrGCp>aql{7)8!qU+wqKmIY zLK)7<;k?Jo<$s-aPg>9Ws)UK`G#{F@j0&IoVbjNNID>Ye3-!qdC#eaiqx#1Vwd3|X zQ=PxG3iA_cjPZbwl##Sq0#+R@z~g_6UkT1xH?XnL38J6w@xW7X)lU0oF)~S8*cwsL zS+$hLMkx}O1#8(c8sJC&U?qFO8CI~K%51=Gt*Zz(3L^{s@*;LxZzew+LtXX`^Y*i% zY*oX$T|K4V%(u)$yLm1g25I7O3^!S>hE}K7hLlJgG@n{6^4VKUEC@H4eJUghTS?c; zZ0wr2q*PO8i)~cOvEWmBMd7od{6-U1CCBOfF(;93I*Zy4iU`tPElH>kY4Zym+R&Z~ z0S1hK;S)JOxh@?suGOme_28V8hMkSzoRB^w%9lsJjB@z<$eJ&VssNw-DRYa0)Xo{^ z=1a>jsTIe0{Md`qj}!u)=5px^*hOFprQdk)G5c!>r!>`(pKyPrW zgFh?O>Y&*zhvVEvbB>LB$%DTn{D(EQ_O!abjhCa(^sATO1^@}*IXqjZxzTSuB`nh{!vQTUk+ z6<^IMZ|v!CP_I^0tN_(`?YaxO7yD<70F{7J;v0JU!j&({k0 zsn_Q7jK6B(t zU5jHHv>1VBy}TqTy1f~5yg+I2+yJ+WFw9E4?oR~T$pc#6dK3J{+Ch)S7EI*c6o$Sy zMe%qwY;xrh&g9u~1iLVj9BWqh#hxDmbJT;USoa4vj>HSMjt5--NW}YBDiuv`E!$ir z3w-07W*Bfr2TQop9r|8zV$bC~#UYMdmLR0NJJ4@*Qw!=fjTfEK_SzjySe+Fj(;oO! ztZfWtek~SVbZNby6bK9Kw!j_L6BG2oiN4z^S|my+I>_{oWS^EPtCN!cN7!Tl!X^-| zQbtYPf*B4L2#}k1-T|tK~wJoZ^%QS6Q+FKl2-x?}YBCI56 zpoG-JUdol73E^u;`)-wlxn70h-v)*4iqBg=8Dfp2dgFs`DY)*g9PjG-xb}Gp-lyQz z9=fcpk_8T=4maW&Qpo+j?41vMz&2Z~*Y1g|!ayYKTK-+Ki99d=Q$3TgiKu z*23$m#2iiR!8?RS&|HF->(eQ2V9riVDxgJ@(|_mr*jr>V+FLM%ZE8MFo6tQ@^j?!J zM%#(8uA*d9B{qaqpIUKzOk~+-{>w28Oy6j+D!Pi1h(?AQ`0NXJ9 zWAZbR?Nd`@ll|cY)ja?az&9~J>kP$2MQc$0Y68pmpbk}n5dY=QxaSAmgCezEaYr}Y zu-OdP{FnO}G1WRa3=c?s*)%(Qforl}Eo39SrXJznJWdT8KtRn}_w%w-ka z#kS=J(*Yzj5y8Yv)TsNCIoxhyK;vMP^KN=v%RS?2r0(<9= z{1W)e-Y5E53I^=oQ7+xqaJTzk(Aa}Y8Q&if`<*KgfjVB|rWuFrHWFoCZc3l{z)M6o znn2Q>58!}If7BCw)a@zeF{)|2e|b0YFPf_*Pq5>qN$2dqUsXb(H6Iwpcq}cUpT@Vxxqe)ehRK zcORR2s*j<>d)xBi`$6vmnj<%8x2(F#O ziE!$~<+WXJyvOre-QnRo*CP*}zEqkQ>48^$R4xCMPt2;7&dhWb$~nzL9aZB@+k5}$ zN3}dE9=9=MT>fj~HGiBV!TyVG!?BAOcgryrMUVRx+8}IaU$)SeM513P@nVHZqwHOZi`LP4G)MG`>Mw<25k3R<`(7J?B!1)cq zERnNw%23W>0>$9_9y*)!2BQ7Bp6{SrN>+r&E0AxjE?J{@0XOQ~7^SlytE8GYIGTcCPmjhM?)UQ z|2C8y?2!rzWGwRszQWu-SQT4vfqhW1EJS{$jx+P(#&=)`J~V8nUu|ZXLa`c2_iGE^ zCzObqJXb;tX+Ul#QL}@yQ&& z0JDS=+@*3y*kBYM`<`?B^cw9rqs+cfyo~T!$jwc#*|Ak1YT%vKb6kCL?NuR47s-2x z0NY?`i{V0Mm{bqiQb0a;d;6xb|I-d!a1-U&2K`q@dh`2X>Epu>lH(s_T-8dgJ4pR3g9niisWhDqT;7461M??Z*Y#oZOTNB{orkgyun--9~!kH z;;ph~c;(TD-o~$t8~Ze|o;lrv-#+SWGd5fQ;L%*seDtSIkhrAi7Ry9UOS24ouS@?W z?HR;KDSX%TS%+y!n4I=Eur$x=Nl#$#r-%vd(s9{Eh?r8;e_P6Jc;axg*icL{Fb&3l z=F2M0s2>f0EIs7+`-5>j=Ooh;@ONYh_bvEfOU?uuqyp8Sp*T!Z4?IpahkZ6ZF9*B5 zSS{7J*PwnI7W-cFs9s{~jGfv+T9L&*aN-rT-J}1UV z(HLeDE;_=}Twg!wz#rOW_1nR_x(w`rWg?5o!Ta>QF)u8-V-mnTAuil|(0$5C&z^v= z0*4_(GzQvRwt%?625|dP3D4M)8R5RC*JjeIDX>9xfJgv!PwVr4{Cn zR}15g&u0n`eu_9Id5jA?r9*D}==ziVf@@v;vpHWy3tvD71x6#AM{-_J6*y>)UtF!r zq;1NJQVCDd1@31;Vqz*SD4qYk=2TTb zD$x=+-`~av_a;b3H)ohz<0-{KOd87{u1>dJQ2D<#t^b+DHup^_jncl}|1t-{giXse z_i)aWpD)wc);dMkqqoy2A_FD^FY`QtZ_Pn_$+WW#_Ri>Qu>> zNQoPgv18a@wxHX(I4GV6OXWU0HG&IiPbQ0`yI=VqU5BiO9RBUtT&hydDm&+~W zVdQ7)Oh_VJ+M2d7waLwV^zIaoH2&GL7zTrs6iyrIGKOm;2PVC{2yo@3tD^d+#B0 z6VRJ2b`D2Fi4qQc^qEg)DO4{(GWU{gyuWLJFr6K6&OO{(oD|7|+8>M8ce~(CPe{=eA!V9k5Y_m93-a=AOHOMI)P|F;~WwUYHy$6~>KKq(FQR#bg}4Z5sT% z?@!L|7`41vsD2>_?S}0}ywS5DET{eMS?y4%H!_d@k0I0m@^uDzws<2g-}7R@>obR@ zN>>vXUwH-pnx&hM3wGiXm)6_*0;0~I#lTCtZ^Pq*mEogSqbhnood~|L+L819QBM_A zR}^k@?6P=#%0mS06(JyLr1cH~wk@WoGfV~1bB%xKB!EP<{@QSWWOeFCv)7%b=K5CH zQ+9b0xh)U){N+7Kg5MlY-(dV8`trXB(UcF1oDNblD4O4=o(!@q~MOdU6h}ceiz*Ky@X2tc`7xR(hKqxTU!`hFS(`r zY9akV(x(*LU!KIo8xSoSuS8O0>>9-YGmlI;HR?X}MI*XkEexG)1$^~RA%G{R5yvdF z#7xM-;KWT?J&?+5T1Dj!Rt#n?UGhyZMjmhWs)I1Wo&(;xzD~BzdWF`LQE8qzAeGQL z^eM7JRug2Qz3Jgfw@krAN5?-z%C{N9l&Hl<)%7}^*CrfzGB=%du4-T^o~AHvH#`(H zTP4n#oauA^%Lk^h_6nRE3JwCZC&y9xAz^s#jwM#xD2*$UOBy%c-!2y#vg|LK1a?m4 zJJ>c_ebaxB%qu z^;ThA0LxfFy|x!P-gQmEKYL`PFfK|#;v^J8C8*p;;B$uW%+t;8DTqED0@5i<9O9fU zr$yJ}(UZ1qI3(7QOg>N?o&NWRn*@n7&8N@&rniPG?k^rigzB*D6%8lR`H+ie{OCKY*u{HM}t%u$!agvj_b1`B$m1|H_M+stzlb{DW++!*`0(sYoJQP$4H9i2%)DIrKeT%B$NOVdZpq!2|($~9`U1uKxxGLI`|^1pa#LV3-yzrYb*G``U*H?Fw$`H#wp zDidTsh&lF&j(JHuN)VVKRg*Az;tMV_c*kpq(}NaWkWp>*+uL3uIJs~36f7*Z$E9g{pdAXg@%wY}|0fAq+ zk8FE^BM7$_binR4JK03#HcY>|rZCu_lMb+MTI@~{Ky0&yR9woL$G!>ug4gySwE^37 zH|7m-t)QI7-yNFzN@am?n>OEXLXyD)bH}{EhQpuL6QotPm0{8Ery87fD7mYwn_DGb zdv*~V&m#K_5D4wbe|zeFG%4_LH#8T6aGRT8GpfJr4E5)tnMCeH@=~T2T3CFEXvqjJ z5dk@$oP;@H3y83nI2|1E5`>{P5s?F9d8cjA+~=j$@q+= zF9e@&-gLzQjI9M+VIwG3VPcdU>5^VqgsMi~J=@6;rPQ;Jvrnq)NrjlJzSoGzc>U47 z`|&(#$auPLhMap;A$S&q?BA4f@m+rmdOOe`U*IQHjA5UH`Yg=Y{e4A4@hc`9Bt&uq znDxeJ4m17Ym%1B`yXy3g78Syx3Y$y4j_4~Gxr!-&LOb{6Smh_fY4U+iHC}DwDk(!+ zJ2^2!%GWfzH6Z@Ja(!9YS|L~)GJC=*fc;wRi_>eZ$|*S>+sL%I0nPIw%WFttI7+fw ztkx@ju_?K3L(|@f{}HBq=%$NQHnwdWR7zP_>o;r|Cg z`bn^lz*HS$TSx)Pz)xu_THlds6%@!)*ZN3y*Ds`Q{hHA)L_u1_B&DNWw{Er_@s5BXa zQulCQKp#qCVp;+eDfx{3FZ%?=H%9AeTRW-#HB40W9dE$iG|gg%i1nz{cKfR6^Q~WB zWs0Waq^R9%|95L<@nM+Vd-DFkf#|b7`V?fx9b8;i#v~?2;ppfXe5G5V*E=IDc7!|e z%A{f)fOFU!CIRFNE*bQLk_AkMHJ}+0!gp$22!Ip7+k1hxcQ^`CI?w%+AxH%*wN^H$ z6r--;U>(4scqZfnFepOS1H=BC!w9scR2ea3so;AlW5oggzO{ZGkt{0u_9@Vvza+tI zZh4@)#*(CHWovAsmUphA=owg*Tn4}>X9fLdD~e&p5deG>EdH|RA7UCB{3p<#f9Ep* z16HKpC*D9VZK611Gi3XB^#5P~7wy$)!9MsOe`1Hkdi>w+__e0>njt`xnE9&qNcBg{ z-=N}Kl>h803q;U=tb*OjJx4*@f+9R*eft0C1Y=F7f#LfcsZ@8~k}-g!wbH8pzxknC zkTmj5DyAyz$$4>V{%Y9&?GBGf?%IKt!weKyH#_^Uf9^;F+GsS;k)O?M?rkFHP&c_S z`8l743vh)SR7P{Cf`EWi2P$g=Mal>qt9uVvHK969BlQu$WEcSbo1k5;N2=qdn9=VGMeAK|rmjEO7A@`p#Ezl~EZCSVK zo!i5@-Ug#7#Q-aC;tTsrJ)rK>A70V~inwtmC2Qh!<-rqOQtQbF{EeaWmyIn8<$^+^ z;f3hgOOkk%qqg!6f_-n87JeqHeo+>~*b-7(@p^{#8SE2dDb59yt~mN<5s*xWmSGKx zdIIDrzqYx5KKs|-&whsKY=gt^Z&0xHA%}17MDTf-0=>jOzu!WO_4Kh7xyU0g;cG$q z@0`(|gzYc&vRLL^a~z?0G!F9QlUZ_eO1$>hFpmHS?4`6P)dai^iXCoV@9gU0Cl^FD z=6I;#e;i0Ob|Lt-tIwfyb7<=ym}OtEnw;Q=-Oc}{kv6jfYaAjpRySDQr9>ZafepKA zaN`wRUHiSVafUBfq) zXWh2*!8x0gKh~zqqI9vkl0fbHl)zb{g*K{lJRLY*=SXKF3~XK*1HfY31}sYSnN zB1bs0ljd=Ue8#XR@cE{*z+3OD$#p&)Ave~^y3I-G<}>>4N46+#5nN-?dL~A>ngFjb z=|iUeH!N^;^(m`WZ)P32zUzixh~%qr3$vUS9R7m57fh>&PNHnFHAy^98*1 zi!zxRWvK40b4?0EU!chJk5936Tq9AFWPP*JDEg?B1tJZBs?0RY*e#Q=$S9oN@$(CE zX=}mKYDCB>$FIa_W`|(^2LwZ=8$Fsnz^Ll>#K?{>lUH}sGA7P>hR4T716^PNU8{yb zS~dW>b%Wh^6mc}*QfdICu%mt_BVxwV^3uwX5>PtviWxX#Y4G0nv&v`W?X(ZG@5q0$ zLY@`XxqQcdX|Wo)7!kjIwJM3Ib7!@85syRYP-fN2t9Rnzw!a-LmCP0Oykozp{uHDl zF!PnW(G-8=^Bq35sURkk;%M0VlKiohH@2_HkHMAhdtT4LXJ@_2-RuaIx|&MG-IL8s zeru$3ahk6LJet&qW~52Bm%R(f+U$ujcCDR0_%|Q+B0go|u5Cw69!s^NU=3pTu3Y=N zGFWH^(I*h1+`6+O(*7t@XU~&$!JS`Z>SX`WTXpFEBrUQw?qp} zJ5X5bU%JJZggaPrZ-Ev2<`B)DM&fBo+0T#n3n-c5AEfNL--UnBtK;2$JN+GhONt7a z^-b?5J+YK#5UK2U6)w6S;*L>!^rG$0$uEnyNaTGU9SpMJr4{RZ)(0=9>hbH)e%oy1 zpSWUw2{l&k!TmLk2Aw(-iCxNiLt$0c$ybLhxj|6|b$9*na?aZ@yb|8F!L%rfb4K;)Xzn1TgL7 zlm%Y?vF^VZS0v7ohP4* zy)KUGNX?7dw-=(B&un_L-&lv#3`xd+TD-Lx9|!5lMzF)jxA`YYwodn`{B)zKNJl?i&;t%#6(L3I^BBZG%XyFOO(_s-r!CrO$*%YYL$)H`gZzzpIZzYy4c~u$ zDR*#=9|FytDI@bWrof7wvRrQ2cNdwaTec6p69ccgkjSRxiL_ zsBA=w9=_jz=$Hz(6iF|-ntJDc)PT#@-~`H`Y=HJ*d+c~uQ66*JA7U!i86|B0v>Ee= zCekQ#3KQj+2``q+6!9fUrx_)UuO{2)SbN6jkruhs@uctO$43VZlA&Nc>ybzbL{>p) zZq#{risRoJ1fPMW>661=<0p~!Or!4veWz2 zdfhaG>f^oIjpa*Zn1&kXbRm}|(anb}&`c&KA6>f51_zqejQ^8#Z?-~G9|f?vJan`@ z-LeedzC1ah*KG~|rTGEsD9t3_yH%A9rXG@6zy>po6Tqw|VYg zzq-lSUA$}Kg{8-MXpGEj0w&qtVuGIm=D%uX;h69={r#EF*;RpveI7?pv|{T{-s}^j z1@j+=OSC;drDIcHBggJigIb@%v2WW9TL+)UQ;Ap}45k_G_+i$CHBPCb6yiSbZ9K8z zvl(E0Gv7pZyVzm{`&a%#kvD)WjwIOG&=v$FEtuz4Eec?X_l&neQ&da8I>M{++; zjxmrCqR^xrR`}J-&|ZYLfvmPqQ7QfR+NVqjXTIj_VTH8(QOC4Q*j2y}RId_4j0-Qa zmw9gzyd(Huz-XIk1R~CQ4INJURMsF}1Zf!NnfH4$+kz7qNkXUwo>H@^DW{hJc?g)P zf6tZ2IEZMf{0DmfcnI88N_Pa*u#L;uT1-y-?ay*Tm+yU|@r$B;?C@vI7NW5mqy~VM-RCUGa$4 z>8H;lo-=Xq(6y;-xiL0lqskXaV8#>yXAK6D=8ES)-R^xv-_B#@wGu+URfn;>=C4em zTZ;=R>q|5!Afxm*E*&={je8%kMUZI9g6^qKFfZjsEH!S&)gu`FJQ~@<^NDFG_F(EF zg%&xa-mypm$OKvV!tW!cfJ^Lg?V> zkdz~my}u=AqyDWFFX%yT>>%N3|t1RaV6G4xDP z@P1G9v6d`NFncnJgp4gXd#2h6SobX{BHW14&GhvQCWh_pzp2OHy+z)Ctxz6LxgY#% z7B|x-=g!#Ygbr^=k+n*fFkkNl0esu!xlkI(f zH;kOYrFlX1H=%jfn7#esn*g%g>myvRfnkW(lANCT|gZ@DgFBQ*`aSBq_*)frR1w2-t6oi9v8se0}{MeT05Ye3<-b}n^lbQ z#kA`EyN3tB9R8ckMT__@4&7V43pg^q z)PEH+FbA{$e>Cw^+ypwB1=n%--gBNlV2M&hQ2qr;5GXu`TJB05?-f%pxs(6vHMa@% zOz`X*`!Z#wyLWdFfX_U(cb;B7jn+Q0-QDRlj7Z)3EVNB=xf-$dzW%*KOx-m=SOYTH zPDg&{a3k{kblyY;+9#HZ1T0=mDkv4E73xAYGXRN6yL(c_?*edOVACz!S zsq230)mBpBw+oT4Ec{7;a=N5g@S~`B8u2waU0~yzCMi7w1I)|Kk57>gM>MRp60c0D zBoNlEPx%G@0yz@)4}Smgeoh1v(|}@Rt4HO)x8q@+kF~YzUmf#8khQd)_?@6MO{dFX zPZ{<+2KZCY&Zg6d;8Aw@p8ga$sSUi}t06#1)uc3Y9>VBJBNuzeT={UDn33U7=W2@Q zuB#IPEdde=HCK8b?`!QU&kM@S(H|c!drmepESAiIyL8!Beo)9fC(C=~G=nrS@O3K> z+0HIqMtEhPEBtXX?_)Og|Iy8gg5>_@Da#yD?Pbw83Z@5*%qMs2bcKtPy4Y;1lbU<$ zTI^hdS@1=6gs!fxk%wQjS%;?)R|}dcR0Izh4Yyyjd5L|I(G?bewsy7?KH7vv`(%Ir zV+1YXf$ya;(%e^F%_u9xDZ0pNA^hJ<@Ta&gH02f~WITnR)l`P zu?IuBrHMXsA3xXhF(+8|MmxR)nhDCxynzz28OJ_S{SDdzlBB`g0rTSiHc0wT_Dy7A zA(tZR5LX-A?^NAi{v0li2yZc2BuNN ziiS)5b+s=-+r-2P!$SzdzYvB+26dhnzPi3Hh-S?&Vu;ubT1)(HOB#corpkhF475>D zR9stVd!NodFFFbq7|!5v_~lCHb9orYq!hn0RiP_v##3nMFD@>=4%8DCp6sCA4#vd8 zBO6k>W;E4=R{naOE$K@--{L|xG}gK>oe$M>c>nu{tf67%z_Z}pt+4|6@|v1v!(9R@ zs+bru9^>YI30mm@>bADFUQahSzUf*EJQtTERHOvILkGRdY#N~_`V^d_xcsW>$zcy2 zqLZR3jP!u{ta`km48mrP#q1T1U4f@`cOWvXF`o#!mB*80jzlFao@Um5i}S^nFl*vL z`hG{V_XxR)8aPzuRp^?%0cm}0e!Wo;7}@q{x;kq>2d{pzc*Loov{Vt_k+_Dh8s&N0 z4=uFN{Zt-~AN^58D6*bRW2(Fsf%oTW>yZ{vTp|zrZ5Jmim2+7nBTTkm@W=b*M~uQ> zlzJk}8YR#`JDO)V{_AlfH5&j{?Gb#i0t@MF7RvSeqrwn?0Yu8KhAkd~PuAfUVuyGy zsmG7j6QrM~Hl2u-_$7HYqDJ+gV3DHVa!7GlgrMTQravxuugVBjN}zqU#vE|D_#s$} z%`jv9GjT1b-Kr!$!{hV)%{h@mu~2!+pO&8cYFX1M?V=mV7t6BFu7{hw-t*0)Bc6VS zUnFKsQxy!W+&R)wapH;YxFt?kqk#_sw0_63t4G3BMd~@27#Yt2ZUS?t!_UtwcibhH z7xOMy9vV4#bLq-kRfUDUgz{aMk9Vh|wNq)tXmxH&+s4Ib5V{B{I zlpJtostbqIu9r4-UpNj?^G>$1n_zcBEe0LkNgI>o%(hR-)?DBN)0`38&a<(vUmHfF zZ>1j))_2SQrTYHL9bF~`YAI89-8xhvm8vvirBwDGH|I~{zQs`~$$w0jA>|EnXQ>&? zC*XZoeSN#U*VpL8*4`&bC>_Uu;i-b2$gZZUN~ma40Snl#gHuLY5i$k>!t!L=#07(C z^wXzLK$-eJG&(6Nla8PA^}MM)BJHs*oU4Gc(ibaZMW52OT>KhPv5-u%vT35CXxaucqxG04j$ z45^>naw95ozg^5aXm}` z`p87Nc2Ow*7{txWs(_w&=O@KJ1z6ePy8>GQw)|02uxK|6gxR`Eg<~OD7I`=NxRV#McNjG)9!xn zLE;#8yVP2ydZfDu6{HN)59QC{u@p?En(j;NT))KPO4agD)mK%fCa#NG2$}s<&ZJL& z2&8MtxVl2|4_71TNdKtjgkD{F71+pMd|2{U5)EIn*Se3Tl{AJXl>Ex)N(6~7Dm5m6 z-Sh%9H8l$wWM+arwgzD^S#$>%7ar51R1v~0_r-3d8c$L!rQ}}iPdj&7W!bywOQvH& zTm!N@ljkxtrCl?pk}lYkDh`_tT|gPFMxQ3ps9e7}=bt@R=iH%8^-fpz|Fg<8p(!7e zC>dQ`V#ZMoZN5*T8CE*;A)dsgs=2AqYip{>s(P~Og%Ny-L|6MrB@b*2Oi1VgCG+$* z4J5^0T}{b>;$hA*lid4GowaML@}&tF!>06)^s!M_z-KkncpZUC&!iU zwC*Fde=y%db|FOjqO?)^W}8V?LTJeW=iNAcfnxCdO>iLrqAV}KIC92;}~+Zdj1FW`Vd@hC4R)8U95Mc;~eDjpV9#|c~Oc6()ZLR36ju733z zt?B#`l-VpMgbH(tSBc0I0AK^na@6chmYGyBZdYT@vSctJl{j#!R|qhBbTY#)P!y#0vqr`v%p#1)HuW|20iJH;WrP)aXYX<#M)^1cW>vI`YJ1u;ANqPH>3K8fQ^Sz*+o;NWT#F~xg~u{;7^9D<8{ zuTDcmY2||Lvo3q0mY)#glhUyi zdL|q;ku*{4zMb`i`HQ1q?$F5)XDxaSW89W9{$E{!uq=u#bZ#DDGnzTae3ronsv#n8$P+W!1FWD? zP}2SB!UXzjF16q+#YKqk_hgn{Pn$%q{Ch#SWSJUJ<9*gZLhb@KB&(mOO*;`$+@pe? z#GBd=k8|aQX)vl%ayNVMjcrJQ<5?%oumqnb^d@r3+6d;lKf6XsF^S+FgYX@&C90v{ z`!i)^KfZu|IGAgKwH4J{s6o|gWC_1@g*H1UDcW&J?!<*MBfgWA@_pM3zA#!h4mOL1 zla>goKqvdPd^ly-#^=yXLNz}`xJDB|0>DQQ6!98Uq}jBso^ zKc0D3x|u=a`%LzQ3Seb48AKI8C&WrsiW{9{+_}d*CJdn#ds6hT1bk?@&v?5gnsXCX-}0S# z19Z(r5MWoTt!WePPuO;OpZ>;}j~j1xHnDP_@@c|&S4mG*02uha!dAb3dACOkA+rbcQO6}tHH|pD|GK}H9h(EK=T!<1si{Yc(-LxTPdg(yS z+r8Qz%=ww%4PEuh3_(RTbWatDls)V=rLpZQp$d(X_Rd_(K)~pr>Q@6AE+a8y=jw87 zGx*H>rea;`;eJ%|GJCATDR!P4QPRS5x;096*GILL#olGsZWf$#C>G4zyQ7Xl88A--pPYF056M5R%48;dMIS7@aWut$fFfwXRNB)sc^IplPl zFpI#YsfoNPm3g-g;bQzo_X)&>Az|{IPp~a{qV6Kc1$$1(ZE^!KxV_xd1JwkEi_0-< zL6kCNH)Mh_0~aYu6H|?dt`^O4fWqVua|7vJB}xY{Mu(ara_>>Z)9#N13EByipXef- zJnopa#Mp8F{7k>wu9mgF08U_Se+9`hsBa^9TVh!TjjMI2z-9Ts_1!E^5$+ONitdRa znDOz@=}%n7QuZSM7|7Q^DxJde&I?!~|D&6fpt^mUarh^wg~i|Ve}OYytuW)J@&*v- zRfJlmrah_5C&N74;?6~4!8!EY$TMDTN|YH+5Ea#WT?*y}Oqhlp^SBz(>+dL= zK9CyAjFIZ|XPh7(WTv?I`F9?5s2Ez&MJLy(Bc}(%`yd~Ag=VU1^#D#3@mb1_g7FjE zvZiamMJT=P^>I`-Y0b54VHv6*(r{aT2h(`#plANsbrihXg8AP<=sn2y@!>pJOz4+& zCx#a6Tbe?2LlP8<@u#4S-~&}r&ot0PhIy0;)_ND%VEm<~f}TOX(q*h)|MQfRwIX4b9;(X57$#FAHl05-9K;nh zKUf(jh-#+1;h+JZj21W}pxNPm%Dt%EGaa0>M4Tv96Jh@3346SXf~?*nzX#}>ez2)g zu}6!e-b8z+JN0IW@-XGZ7YRYIrd74L%!I0lpls?ynGd6^O3_ad@m>w0aE`Z5%1(YM zS=Qt@8Hd5OXSP z4yR9y>|5RmpX=taAxx~9UcY7uADQBc`K;B$Z4WYI9YR?useOh`ZxcPLRORFqwNNu^i zb-aJde&sFkzB`R-vGW(Fk?DcKt?ZOjbJj&75jpu6ie@-TX}_gnuH9i!s&_9$j8m8;2#jEy3mAn+4rLC0(L zp$MVv>@l&|*gh$;ZpI<;EI}%AMye^wa`QKF=+_T`_v0LA1<3cz)P9z69||Salr;wF z5027Fe-^xYxiRb=6+S7?5arnTWZCSsH=zt+spmHy9xKxascylHGl>jpOgV~Y5E@%~ zOCQHPST>{5FB{E`BGa{)#Xan)LiUr7+Jq=;SLD@VCx%q?G}>R4S2gC1aJTg$d{)M7 z&DuxVvlYO#6SjuI?ldCie^O-Ls}^r6MWvFqsR?ba&T)-VVUqU9R5m@hpd7LSwuNd4 z>xQ|~sD69%b~O=>I}kygnCGCp%0G491Fvj!&LC2SO9~1hp(c!ooxPA`y0$b#8%uuD zh{>Ys5_7R;?V~w8-TYq86;<2w1@vjA4fUYvO*H2q+O-Me^%*3gjp@^;Z_&z;m1u&f z+P(;)F^^PBcmE=1F6H<+SR)W8>tQch=*RXEG<`ng$tjE1R%$+~x0Tvb%9ZjOi<$Sc z?|}%Gfc+GfCud8pNl*u%w7m;kRL%&%yMmK<)bAV%HNx1Sw?Hps8w91Ll~Kz^ex^#%K?E+30N z8(UJ7`(~LvsJdl{P3_pT*q`^Y25y454$dyFvHg`7GcGZyy~dA=i8#IU2p*`cX|wm@ zIt5j+4a4l;zE-jQ3&6M(HLkSG_)e@vG0V4~r(G5-ho6oKiDk{)JsT^Z8l!mHpp93O zePDCz^yy<-Wg~Cq58gVPjmGMHS5{K2kXcYy_}c5t=_d9{ec4wI_xg6}Szwwe)pQHO zDxr7m?mmL%w{V~uO<6I&mIRl@I~GCkGmKk@2|1OM6olPv8YOZi*>DiHy@0qu^~da? z;XgGFZaYn?Rwh5lZuKFC6mh)U8&!T_@+yI^i~_q&THiSy`KP{-Xzi8LQvI!mRc*P6 zzgAC91>jg%=jN4Dg@<2w)BR@g5B@3=VW8N@Ux)_y#4<|A6VU@Zr`wARk3L= z1;1B^GqZWsfy%jfw`nQP3n=8whdvi}2v)<|J zv&Z5Mgvq1TEjzPiw}|%*09E>M^JE~NIK+C-S*J;a?7l@x#mOH=xK8n)gmF2y1^2%d zQq>7r@z|%o*nbxk(|~nBBbm?|Yz*<>It5pRuCTs!lTWi1R0tZg9DM) zsi}jEcXtfb3rFEJT>dj%a5pJoB0E(fJuVzOeW_&R1k0_F97NL$O$2URpYsfE>w$Dc z|JpM>(m`0@@^c2~8A;k>OS{E%9_Y15e}tO5Qr6=Yxo6`QYKHDwn- z+3G-epfp8L$PVka-fKuKf3fdN*)lfT!3Y5jrXC53jBE$1u>^~?N#P?Pxvt&nOA%V| z=@%o)=)Fi!N^b*MU3b*?Qa+TI(#M(-Cxd8^Qx&StYe%{th~E){pJ-W$8N324{JfU^ zy;e?Uj?J_1j$yt>1TEOM@|w_e>A2%lBSavmz%WzDG7IgOkcNfzk$6&GS#BWGw*JDGJ~oyw0J$PY^(P(U;s_4~1J6IB)%2g5haHhA zBLgiOu$tHb*u~K*3v2k1uMN6EF1Iz37=cN9PfUw61;%ADad!3{O0M5;lb51NXw^&T zMf}K1r{MLHh?nUtE-$R!kF$Mk1_2M|2*6XzU!?=|^I%Z|1mbP|$^p|H--;`UQ z6^y@PI{#t(0UG-u>R*dVB@^GQLLF94}LP&CA|<~Ozz?nq%B562{| zcJYZ5uD_ysHx~%H>DVk_ehv2Nx=`wzT*OEkGEPMi zv%E4HU2UqJS$P+S`|RCv!3}qOF#P}N?JL8g?7Fr=x&?-m?rx9q zX;lz()JK=Be`$u3V{9D!euvhB0pVglc6>2nrO>$ssp7J+jjz&)Q7TX#qw)2-{sFlF z&v~e%_3I7wn_61N?7T0T2ZsOgG;2=?&Q2-+l<@kzJ!|UlJW^P&alZMIEGpb$GF;8# zY)MaGOuu*A+d(p;=){<3>%9q{Zvo%@uH5}>11Pc7zQ=FI*+OQQ6dVh`w^fq4Ab%Zh zD`csJsdC1Dw8AKuWV-My$*8QOOgAwg+mkoF=$p7$ZJ4O!4WmT~9v$4GXZh8Tyo4rN z3o7?sTY7B<>W+D<;~VDX%kB{R6s>6w_Sb1A+pX+Dw=ec@xZlcb-1DVeG>P(yZ||(t zw+yf3ha9fux7plAxEVg!wT}PX;n1Pwb(Z<)EkY_Jl-{yul)L%wz5Z~cInFrk>FNz>4K+#y>B&RXVb`0;0F4OT`p4N zerftzJ|_gs*&^USc22K0zjl+v{RI!sVmSLK@epHP!I&yL)hJ3dpa~BTQ0)xTSoA)NRG4@J=Fh}aDmR3 z%-fs-5#(t-*Q~pefRn8@yDwTb{)>?1DT3LcIG0HQ%$qPBPuXs{7L1iGABVHB9T`zB zk1N?iy!!19zb)FmbK63$q|YXV@0!SQKLCqS7aj?+8(s=&pt=@>vNUVsv6DCSw?x6k zj(lZjr|8FMHY*)YT)hxIM28~=iWOW+jZAPEV3sEjk7<}vg@Ag<#51p7N4mhaJ0at{ zYf_F8cwD23mG({b0}j5nchQII7w$`FgbJpS)kS7o=L?L72or-P@V;#98@jN41;1E- z-VrACT*?KO_$`U#p7x1vMC5xFpbTBo7o~nYv_QM{6y7?$-pJ*eXiVhibble50$P4C zeEmu)Uu)@VhSz_llz0S>4Z3Yz)dty22@rk+Myc>{5ysUeX#|II6Qu?K69z!8@x zX4l(MRd6~osKJ@lYE_zyb)v6Gt&s2|%aHiX)}w{&1(;{aFtfNIBMMg0E6As08-rii zgx|TB9huDl!G=tFulmI^%9;>;e|G0X-1-ruZ)6Yz|3%rppArcsO1jtqhe)*qQe|H< zXz66PFUV)xtevQuicL`Tq@MLu&Rd$+)bdKtoOgz3`EbGPa;9d_S8ptbal?<>OS2a` z@(-fBvoNIy?K!&F+2D1fX3sp6Eob8 z&2mwj(!(9g$tc~7IZ8;_T{t6W<7&$RP_lF;hNg9~i>l@ds1{GmL$>EUA4d}4s!aPM zd|HV5;BJwi6od4geI~Z1af2rPKsq0drKV`1N|Q&+p8mei`;tccHu420yYDgcu)>Vw?dH_)_1!NpekNt z4XnVgCv=k}NVK)UWrpq4p(mGho%$X%?Ks^3l`{I7` zH(cX9Yoo;(OSNS0i4fm0o9ma@Hd>Bx_mN&=bSvxcL{Ksf-=fbv->hr)vpc*ln_~UI8ZEZe?zd;MR2E zeTqd|jI!4a_0;=b`0Xx0$vStwZ$76FqUUJCWgUrzX9GeS7<|wwqGzB{>qg-ga(J0p zjf}b{^hBF%ITBth(rp*&Ek`A+d8Xohpn6#Okk!!v$=*^{t_^(}4`8#9MPLhelEjCG zMD_&G?b!SsksguAbF~DxY)rBi?AQ(+S;;K>;{9V*9<5vB6VD0hgdIPv=zydOI6+8;TMsiWLu%j)oEWsO-;P%=99i z0&uPFHAI?XorS8>9vqqO*yZ7J2EWO05 z_;w|3oUQq2E%32{wT{_`nKi1op?WiNc{Wz!SHlRw!+Q4M$O?) zHi(!8I!cVD)zbNcJwoBP!PVoYS&@4BtQ(vPGDK3B1x3ZS_nsBS z`F;9q%T$s+UZ$eqguk+~k`SL9RQtn_xh$lU4`39@x(8;q5-|kWF|)Eh2(|qg^jw;% zI?@J3OJtJbMt4!7F@tm$GRtKDmd}<5{kQQp@I0iF&E*Jmwa7()eVBqVXFG&y)b6xE zEWjhf{`7Q{*Eynz&&_GNc!=vLeWankrfP0z$#&mlmlh^Qcyo9hA3KUve4{XFO6?#us-7xask(cN}4B z-9}?i>gOQFt0)6?f!iPrY4P-MSY^;XK{ufAjflro605&CwS zbR38j`_}tm?hHgeyG@CRu8xE|$Z7$tnUvcJuo&AVK3jf3;kT=7Vg4^yNxsLg%3&=I zHe9~|9yesK-b`8bJaBFqi+64fGx^-SqHRpHlde}d9Q3PE(h?;QU2U^`&bt4rsMb=1J%m@-&<4CWDYdC#(jM9D{3gHG|pi$ zsJh!9;syqap(2TjT2RgC{xKi_w+p~OP`iJt>!VEi=egg6kLBMG*Ecl(wD14#R~4L& zh3twayi6(sISCKF)Ut256uo&`Ei}6jF!1U*?DTa8-@1f2?m(aSLE;~%C~@pd6ry#! zskgI9gFw}cqhlmHba9*f5OEgN(C|>_5{pHmhStYkVThmWlNpAiVKNJ+6sG=H5o*({ zH_+xTv5djdDN1H>;d^6Pn$*#y0&hZ^z2B#+Oyx-nqBFOGB0Cv!aqB&5vd z#^Pn+{e*9pCeYz|U<;rzFLaWUI*|D1ApN1Lz&ypT+!;bbZ`q6V9dgIG^in9I=Us8~ zwX#DGhl>KUxE)+G$NFPFF}!qzc~v-phlsGYDX6Fk5W~Qcl`QJ7oPG7y52rN^@m(Ql zST#$NPJfWLix22+e2X?K(F{{~ z!n^_FgRRrcRC;yO7*^F{S#USRc$p*kaX2^WmHrXoqu}wXm3CKUUElYWt1jMf|1iNU zVi4D87HWA$!gn+jtVB)uQiw{ zblrWC=k(;gYuz^QbW-D@ufe^^bw#~eN`|JmN}#6+CQtS`wJj!Q(F+5Iq@^M!KxW^R z?kB`YB@d`qyCiPK_{0!3(YOp;mT*eOEAX}7Q*;1N>v zR~sZ4@C9p{1%E7MF+^!Z<_S^g=LX{Hax1cz7zUS8fAz-V(yv@iPT|s)ycd=sD*WaX zhK>ckina}pX0n?8$p^P0eocyj2V{X;$$md@Z6x_Iy zmFUMDF+K1xR$;d-FTS_(!JRydPxmFDj?Iu#a zyM+lm{t{N9DlrS;13J=>={mLh)jKZ-W6<}YT0SaB@SYW8IcW?%Hk5|t6N8?(`nY+2>A|L49+)VAU*kE zwfe!rV@GT@_R(B&t@l`ahTFS^Jgoii$Cm9x?JL{`9RzGgwJYP;Z1nRtns%IJ{N&P- z#o7#=5cVFVj;58v2K_Rz+594DSe;~Teuy=WTB?jkN@dj?ln35jF!{uaYzzEo#z-&KYP4JMD0Ib(shU z;rFXxZjtZK-a|W_@r(uEnk% z?QX0s1PS~SAhqWHDPN1Ux{%7p9f_sDcld~se3`BMXYQ_$&DPVss8Y0kUEpM;)2k_G zd0Cd~TpO2+kPE_I*=k&6^`XukEpeM3Rhlr2s7+w+AbIZ;ZKy~MFEJwe_-&RvAW5Re zuei5R+2s>XY8*qHBXB*w>cYHxDhtIZ`MV(VH~VLd1zt1a+>zrfv$UCoA?$YOymd4& zwKWR4XYAdbr_$Fc{S%A4lxmVlX{xHkWmJL8MR+XSMmusY&P?tXD#|A0F_TQPBxu#a zF!PoePa?nnpoxk=jj(5Q-A?Z{Psi6MNkVWZxnW~bVTU96hc7OHQ!Db+jsxu7radbY zN}tPAHFWUF$YJ+g-MFtjYJ#;)nN98Wuq)cr84Q+|KA?-MKBh5NwccIBt6cOVHoEeo zvy{Zg;FMvmh29I_E;i7$Ih`XVdTzt@q<%z?GKzcvnSXGl2PoOi5g_WAOz?nLyV65r z+(H8C3Ei9Q*_`O3Ne75o@{+sE+z2OdPP@~8rNzUf(fE`&6#0T~w#Wz>MF|`P`$P5~ zC50#tY=E{(4Yul1(jlUEG*Upd zmHC~e{Rby3%6bIxEDSvp$L8!vJ9%9l=6tH?w(As@sh^@v93@m-?E=;jblXKG1dzm? z?{05KE36bw4i>3e&EUW6CKN$??(a3%Lb3v9ufC?s1G%8*2T)=ww~j_}d9E+fu(7Tr zVQ|7jF!$3`oFL{+*)xG%5G-td5b^3=lPm^lk0)s8o(w#O?%nOY%u4eC6!grR*^mvc zoNj#}h4E};Gh9IK)9X4sI7k75;9;h^sym?mNk(J>??|5MXtPbz`&v=chVQ8Qr_dGI zCtrT`wq*faer{!#(U;K;p6Xssn6i7Ht&!_*k_VnvIdm%W%~J)kV3g#~v8GGBqJDW0 zXFdL5xj(!W-7L|%?FH#xza*W74GoY}fhkCyq>AC}q6xxzvbD%~gHw6+tH(cJH^veJ z3c476)sB-z;4wU@`QJMkL6S~0QCA{CN>D{pIUk|GY5>eRY3}}WZcUGT5dzN5^Y&hM zPSQmE!0|*f00l*E<0pXax{;oJbkRf_>!3GrH%a!pb)4&jcq=GkZ~`vrLXFKY6_|(M zqI<8vz?zn&LaN%2P!w9nUiywG!IHUjDA8hBmat`lKCy~YrXr6wNR1nRM?^j2D<*-p z_6+ZA`>k{u5x?r=NK^7SIZ-?7s#UCHqYxDFP9@sXmBF@lF|k}=g7E<*v0aed-HwsX zptNBDJ*+wqR5|^Sa+6N2u z#)NFoW?}H9RR6t)K*couw}b-s{g!~niVW3w%0A*xDh0ukabP29;z35}tw95`1;_ea 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 . You are doing it by inserting special commands, for instance ``@param``, into standard comments blocks like for example ``/* @param ratio this is oxygen to air ratio */``. + +Doxygen is phrasing the code, extracting the commands together with subsequent text, and building documentation out of it. + +Typical comment block, that contains documentation of a function, looks like below. + +.. image:: _static/doc-code-documentation-inline.png + :align: center + :alt: Sample inline code documentation + +Doxygen supports couple of formatting styles. It also gives you great flexibility on level of details to include in documentation. To get the taste of available features please check data reach and very well organized `Doxygen Manual `_ . + +Why we need it? +--------------- + +The purpose of this description is to provide quick summary on documentation style used in `espressif/esp-idf `_ repository. + +The ultimate goal is to ensure that all the code is consistently documented, so we can use tools like `Sphinx `_ and `Breathe `_ to aid preparation and automatic updates of API documentation when the code changes. The above piece of code renders in Sphinx like below: + +.. image:: _static/doc-code-documentation-rendered.png + :align: center + :alt: Sample inline code after rendering + + +Go for it! +---------- + +When writing code for this repository, please follow guidelines below. + + 1. Document all building blocks of code: functions, structs, typedefs, enums, macros, etc. Provide enough information on purpose, functionality and limitations of documented items, as you would like to see them documented when reading the code by others. + + 2. Documentation of function should describe what this function does. If it accepts input parameters and returns some value, all they should be explained. + + 3. Do not add a data type before parameter or any other characters besides spaces. All spaces and line breaks are compressed into a single space. If you like to break a line, then break it twice. + + .. image:: _static/doc-code-function.png + :align: center + :alt: Sample function documented inline and after rendering + + 4. If function has void input or does not return any value, then skip ``@param`` or ``@return`` + + .. image:: _static/doc-code-void-function.png + :align: center + :alt: Sample void function documented inline and after rendering + + 5. When documenting members of a ``struct``, ``typedef`` or ``enum``, place specific comment like below after each member. + + .. image:: _static/doc-code-member.png + :align: center + :alt: Sample of member documentation inline and after rendering + + 6. To provide well formatted lists, break the line after command (like ``@return`` in example below). + + :: + + ... + * + * @return + * - ESP_OK if erase operation was successful + * - ESP_ERR_NVS_INVALID_HANDLE if handle has been closed or is NULL + * - ESP_ERR_NVS_READ_ONLY if handle was opened as read only + * - ESP_ERR_NVS_NOT_FOUND if the requested key doesn't exist + * - other error codes from the underlying storage driver + * + ... + + + 7. Overview of functionality of documented header file, or group of files that make a library, should be placed in separate ``README.rst`` file. + +Go one extra mile +----------------- + +There are are couple of tips how you can make your documentation even better and more useful to the reader. + +Add code snippets to illustrate implementation. To do so, enclose the snippet using ``@code{c}`` and ``@endcode`` commands. + +:: + + ... + * + * @code{c} + * // Example of using nvs_get_i32: + * int32_t max_buffer_size = 4096; // default value + * esp_err_t err = nvs_get_i32(my_handle, "max_buffer_size", &max_buffer_size); + * assert(err == ESP_OK || err == ESP_ERR_NVS_NOT_FOUND); + * // if ESP_ERR_NVS_NOT_FOUND was returned, max_buffer_size will still + * // have its default value. + * @endcode + * + ... + +To highlight some information use command ``@attention`` or ``@note``. Example below also shows how to use a numbered list. + +:: + + ... + * + * @attention + * 1. This API only impact WIFI_MODE_STA or WIFI_MODE_APSTA mode + * 2. If the ESP32 is connected to an AP, call esp_wifi_disconnect to disconnect. + * + ... + + +Use markdown to make your documentation even more readable. With markdown you can add headers, links, tables and more. + +:: + + ... + * + * [ESP32 Technical Reference](http://espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf) + * + ... + +Wrap up +------- + +We love good code that is doing cool things. +We love it even better, if it is well documented, so we can qickly make it run and also do the cool things. + diff --git a/docs/index.rst b/docs/index.rst index e19499208..da03346e7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,7 +3,11 @@ ESP32 Programming Guide .. caution:: - This is DRAFT - mind your step + This DRAF version of documentation developed within `ESP-IDF 1.0 Release plan `_. + It is scheduled for merging with `espressif/esp-idf `_ repository at the release date. + Before merging it may be incomplete, or not fully in sync with espressif/esp-idf. + Please mind your step! + Contents: @@ -58,6 +62,7 @@ Contents: :maxdepth: 1 contributing + documenting-code contributor-agreement .. toctree:: From 73944e680053610e7718cdb21763e28dae08b246 Mon Sep 17 00:00:00 2001 From: Krzysztof Date: Sun, 30 Oct 2016 19:54:10 +0100 Subject: [PATCH 142/149] Typo corrections --- docs/documenting-code.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/documenting-code.rst b/docs/documenting-code.rst index f07f464ed..51a0dbf7d 100644 --- a/docs/documenting-code.rst +++ b/docs/documenting-code.rst @@ -4,7 +4,7 @@ Documenting Code Introduction ------------ -When documenting code for this repository, please follow `Doxygen style `_ . You are doing it by inserting special commands, for instance ``@param``, into standard comments blocks like for example ``/* @param ratio this is oxygen to air ratio */``. +When documenting code for this repository, please follow `Doxygen style `_. You are doing it by inserting special commands, for instance ``@param``, into standard comments blocks like for example ``/* @param ratio this is oxygen to air ratio */``. Doxygen is phrasing the code, extracting the commands together with subsequent text, and building documentation out of it. @@ -14,7 +14,7 @@ Typical comment block, that contains documentation of a function, looks like bel :align: center :alt: Sample inline code documentation -Doxygen supports couple of formatting styles. It also gives you great flexibility on level of details to include in documentation. To get the taste of available features please check data reach and very well organized `Doxygen Manual `_ . +Doxygen supports couple of formatting styles. It also gives you great flexibility on level of details to include in documentation. To get the taste of available features please check data reach and very well organized `Doxygen Manual `_. Why we need it? --------------- @@ -35,7 +35,7 @@ When writing code for this repository, please follow guidelines below. 1. Document all building blocks of code: functions, structs, typedefs, enums, macros, etc. Provide enough information on purpose, functionality and limitations of documented items, as you would like to see them documented when reading the code by others. - 2. Documentation of function should describe what this function does. If it accepts input parameters and returns some value, all they should be explained. + 2. Documentation of function should describe what this function does. If it accepts input parameters and returns some value, all of them should be explained. 3. Do not add a data type before parameter or any other characters besides spaces. All spaces and line breaks are compressed into a single space. If you like to break a line, then break it twice. @@ -76,7 +76,7 @@ When writing code for this repository, please follow guidelines below. Go one extra mile ----------------- -There are are couple of tips how you can make your documentation even better and more useful to the reader. +There are couple of tips how you can make your documentation even better and more useful to the reader. Add code snippets to illustrate implementation. To do so, enclose the snippet using ``@code{c}`` and ``@endcode`` commands. @@ -122,5 +122,5 @@ Wrap up ------- We love good code that is doing cool things. -We love it even better, if it is well documented, so we can qickly make it run and also do the cool things. +We love it even better, if it is well documented, so we can quickly make it run and also do the cool things. From f0cd38a0799f13357761e134b108c8ab8dc40abf Mon Sep 17 00:00:00 2001 From: Liu Zhi Fu Date: Tue, 1 Nov 2016 15:25:46 +0800 Subject: [PATCH 143/149] lwip: remove tx flow control code --- components/lwip/api/sockets.c | 30 -------------------- components/lwip/include/lwip/port/lwipopts.h | 3 -- 2 files changed, 33 deletions(-) diff --git a/components/lwip/api/sockets.c b/components/lwip/api/sockets.c index df658578a..1529382f5 100755 --- a/components/lwip/api/sockets.c +++ b/components/lwip/api/sockets.c @@ -382,34 +382,6 @@ static void lwip_socket_unregister_membership(int s, const ip4_addr_t *if_addr, static void lwip_socket_drop_registered_memberships(int s); #endif /* LWIP_IGMP */ -#if ESP_LWIP -#include "esp_wifi_internal.h" -#include "esp_system.h" - -/* Please be notified that this flow control is just a workaround for fixing wifi Q full issue. - * Under UDP/TCP pressure test, we found that the sockets may cause wifi tx queue full if the socket - * sending speed is faster than the wifi sending speed, it will finally cause the packet to be dropped - * in wifi layer, it's not acceptable in some application. That's why we introdue the tx flow control here. - * However, current solution is just a workaround, we need to consider the return value of wifi tx interface, - * and feedback the return value to lwip and let lwip do the flow control itself. - */ -static inline void esp32_tx_flow_ctrl(void) -{ -//TODO we need to do flow control for UDP -#if 0 - uint8_t _wait_delay = 1; - - while ((system_get_free_heap_size() < HEAP_HIGHWAT) || esp_wifi_internal_tx_is_stop()){ - vTaskDelay(_wait_delay/portTICK_RATE_MS); - if (_wait_delay < 64) _wait_delay *= 2; - } -#endif -} - -#else -#define esp32_tx_flow_ctrl() -#endif - /** The global array of available sockets */ static struct lwip_sock sockets[NUM_SOCKETS]; #if ESP_THREAD_SAFE @@ -1392,8 +1364,6 @@ lwip_sendto(int s, const void *data, size_t size, int flags, #endif /* LWIP_TCP */ } - esp32_tx_flow_ctrl(); - if ((to != NULL) && !SOCK_ADDR_TYPE_MATCH(to, sock)) { /* sockaddr does not match socket type (IPv4/IPv6) */ sock_set_errno(sock, err_to_errno(ERR_VAL)); diff --git a/components/lwip/include/lwip/port/lwipopts.h b/components/lwip/include/lwip/port/lwipopts.h index 67a62b822..b970ae553 100755 --- a/components/lwip/include/lwip/port/lwipopts.h +++ b/components/lwip/include/lwip/port/lwipopts.h @@ -523,7 +523,6 @@ extern unsigned long os_random(void); #define ESP_IP4_ATON 1 #define ESP_LIGHT_SLEEP 1 - #define TCP_WND_DEFAULT (4*TCP_MSS) #define TCP_SND_BUF_DEFAULT (2*TCP_MSS) @@ -550,8 +549,6 @@ extern unsigned char misc_prof_get_tcp_snd_buf(void); #define CHECKSUM_CHECK_UDP 0 #define CHECKSUM_CHECK_IP 0 -#define HEAP_HIGHWAT 20*1024 - #define LWIP_NETCONN_FULLDUPLEX 1 #define LWIP_NETCONN_SEM_PER_THREAD 1 From edf5b103449e4120c0e1b03e40f0e82f17c38daf Mon Sep 17 00:00:00 2001 From: Liu Zhi Fu Date: Tue, 1 Nov 2016 15:34:30 +0800 Subject: [PATCH 144/149] esp32: update wifi lib 146f5962 - Make the return value of esp_wifi_internal_tx consistent with LWIP error code so that the up-layer can detect the out-of-memory error and take action accordingly, such do flow control. --- components/esp32/lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp32/lib b/components/esp32/lib index 9d18fd1a8..b3090d885 160000 --- a/components/esp32/lib +++ b/components/esp32/lib @@ -1 +1 @@ -Subproject commit 9d18fd1a8f7610130e69f8be74ec68f6399221b1 +Subproject commit b3090d885413fb78c86e7b88116cdb5c8c5e9e68 From 8c1d1e19c230b5229fcb1463ceb3cd804728b9a8 Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Tue, 1 Nov 2016 15:41:10 +0800 Subject: [PATCH 145/149] OpenOCD doc fix, fix gdbstub --- components/esp32/gdbstub.c | 2 +- docs/openocd.rst | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/esp32/gdbstub.c b/components/esp32/gdbstub.c index d75fced4c..a43793f83 100644 --- a/components/esp32/gdbstub.c +++ b/components/esp32/gdbstub.c @@ -351,7 +351,7 @@ static int gdbReadCommand() { -void gdbstubPanicHandler(XtExcFrame *frame) { +void esp_gdbstub_panic_handler(XtExcFrame *frame) { dumpHwToRegfile(frame); //Make sure txd/rxd are enabled PIN_PULLUP_DIS(PERIPHS_IO_MUX_U0TXD_U); diff --git a/docs/openocd.rst b/docs/openocd.rst index 57ee93db4..cf1d25e60 100644 --- a/docs/openocd.rst +++ b/docs/openocd.rst @@ -2,10 +2,10 @@ OpenOCD setup for ESP32 ----------------------- The ESP31 and ESP32 have two powerful Xtensa cores, allowing for a great deal of variety of program architectures. The FreeRTOS -OS that comes with ESP-IDF is capable multi-core pre-emptive multithreading, allowing for an intuitive way of writing software. +OS that comes with ESP-IDF is capable of multi-core pre-emptive multithreading, allowing for an intuitive way of writing software. The downside of the ease of programming is that debugging without the right tools is harder: figuring out a bug that is caused -by two threads, maybe even running simultaneously on two different CPU cures, can take a long time when all you have are printf +by two threads, maybe even running simultaneously on two different CPU cores, can take a long time when all you have are printf statements. A better and in many cases quicker way to debug such problems is by using a debugger, connected to the processors over a debug port. @@ -84,7 +84,7 @@ Connecting a debugger to OpenOCD OpenOCD should now be ready to accept gdb connections. If you have compiled the ESP32 toolchain using Crosstool-NG, or if you have downloaded a precompiled toolchain from the Espressif website, you should already have xtensa-esp32-elf-gdb, a version of gdb that can be used for this. First, make sure the project you want to debug is compiled and flashed -into the ESP32s SPI flash. Then, in a different console than OpenOCD is running in, invoke gdb. For example, for the +into the ESP32's SPI flash. Then, in a different console than OpenOCD is running in, invoke gdb. For example, for the template app, you would do this like such:: cd esp-idf-template From 1d3626c119d2acbb164571ce01dc5326f418172f Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Tue, 1 Nov 2016 20:08:29 +0800 Subject: [PATCH 146/149] docs: fix typos, build docs with gitlab CI --- .gitlab-ci.yml | 14 ++++++++++++++ docs/eclipse-setup.rst | 2 +- docs/index.rst | 5 +---- docs/requirements.txt | 7 ++++++- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index aff9bea8d..25e4d4f17 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -92,6 +92,20 @@ build_examples: - cd build_examples - ${IDF_PATH}/make/build_examples.sh +build_docs: + stage: build + image: espressif/esp32-ci-env + tags: + - build_docs + script: + - cd docs + - make html + artifacts: + paths: + - docs/_build/html + expire_in: 6 mos + + test_nvs_on_host: stage: test image: espressif/esp32-ci-env diff --git a/docs/eclipse-setup.rst b/docs/eclipse-setup.rst index 32d60a17a..fbad93be6 100644 --- a/docs/eclipse-setup.rst +++ b/docs/eclipse-setup.rst @@ -1,4 +1,4 @@ -Build and Falsh with Eclipse IDE +Build and Flash with Eclipse IDE ******************************** Installing Eclipse IDE diff --git a/docs/index.rst b/docs/index.rst index da03346e7..c97395061 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,10 +3,7 @@ ESP32 Programming Guide .. caution:: - This DRAF version of documentation developed within `ESP-IDF 1.0 Release plan `_. - It is scheduled for merging with `espressif/esp-idf `_ repository at the release date. - Before merging it may be incomplete, or not fully in sync with espressif/esp-idf. - Please mind your step! + Until ESP-IDF release 1.0, this documentation is a draft. It is incomplete and may have mistakes. Please mind your step! Contents: diff --git a/docs/requirements.txt b/docs/requirements.txt index 188f51e62..debed2867 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1 +1,6 @@ -breathe \ No newline at end of file +# This is a list of python packages used to generate documentation. This file is used with pip: +# pip install requirements.txt +# +sphinx +sphinx-rtd-theme +breathe From 4f71d741ec79c1a155d4130a3936f2971dd6caf9 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Tue, 1 Nov 2016 20:58:47 +0800 Subject: [PATCH 147/149] docs: deploy built docs --- .gitlab-ci.yml | 27 ++++++++++++++++++++++++++- docs/conf.py | 22 ++++++++++++++++++---- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 25e4d4f17..931009f4b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -103,7 +103,7 @@ build_docs: artifacts: paths: - docs/_build/html - expire_in: 6 mos + expire_in: 1 mos test_nvs_on_host: @@ -173,6 +173,31 @@ push_master_to_github: - git push --follow-tags github HEAD:master +deploy_docs: + before_script: + - echo "Not setting up GitLab key, not fetching submodules" + stage: deploy + only: + - master + - triggers + tags: + - deploy + image: espressif/esp32-ci-env + script: + - mkdir -p ~/.ssh + - chmod 700 ~/.ssh + - echo -n $DOCS_DEPLOY_KEY > ~/.ssh/id_rsa_base64 + - base64 --decode --ignore-garbage ~/.ssh/id_rsa_base64 > ~/.ssh/id_rsa + - chmod 600 ~/.ssh/id_rsa + - echo -e "Host $DOCS_SERVER\n\tStrictHostKeyChecking no\n\tUser $DOCS_SERVER_USER\n" >> ~/.ssh/config + - export GIT_VER=$(git describe --always) + - cd docs/_build/ + - mv html $GIT_VER + - tar czvf $GIT_VER.tar.gz $GIT_VER + - scp $GIT_VER.tar.gz $DOCS_SERVER:$DOCS_PATH + - ssh $DOCS_SERVER -x "cd $DOCS_PATH && tar xzvf $GIT_VER.tar.gz && rm -f latest && ln -s $GIT_VER latest" + + # AUTO GENERATED PART START, DO NOT MODIFY CONTENT BELOW # template for test jobs .test_template: &test_template diff --git a/docs/conf.py b/docs/conf.py index 8a8821fb0..8a4a275c8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -26,9 +26,20 @@ import os # added by krzychb, 24-Oct-2016 # -from subprocess import call +from subprocess import call, Popen, PIPE +import shlex + call('doxygen') +# -- Function to get output of a command ---------------------------------- +def run_cmd_get_output(cmd): + process = Popen(shlex.split(cmd), stdout=PIPE) + (output, err) = process.communicate() + exit_code = process.wait() + if exit_code != 0: + raise RuntimeError('command line program has failed') + return output + # -- General configuration ------------------------------------------------ @@ -64,10 +75,13 @@ copyright = u'2016, Espressif' # |version| and |release|, also used in various other places throughout the # built documents. # -# The short X.Y version. -version = '1.0' +# This is supposed to be "the short X.Y version", but it's the only version +# visible when you open index.html. +# Display full version to make things less confusing. +# If needed, nearest tag is returned by 'git describe --abbrev=0'. +version = run_cmd_get_output('git describe') # The full version, including alpha/beta/rc tags. -release = '1.0' +release = run_cmd_get_output('git describe') # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. From e1f0c98ef531ed803bc5ad9100c0a971f8c272b2 Mon Sep 17 00:00:00 2001 From: Wangjialin Date: Tue, 1 Nov 2016 11:48:32 +0800 Subject: [PATCH 148/149] Modify gpio.h and ledc.h --- components/driver/include/driver/gpio.h | 436 ++++++++++++------------ components/driver/include/driver/ledc.h | 330 +++++++++--------- 2 files changed, 388 insertions(+), 378 deletions(-) diff --git a/components/driver/include/driver/gpio.h b/components/driver/include/driver/gpio.h index 001be2a39..7106489d6 100644 --- a/components/driver/include/driver/gpio.h +++ b/components/driver/include/driver/gpio.h @@ -27,43 +27,43 @@ extern "C" { #endif -#define GPIO_SEL_0 (BIT(0)) /* Pin 0 selected */ -#define GPIO_SEL_1 (BIT(1)) /* Pin 1 selected */ -#define GPIO_SEL_2 (BIT(2)) /* Pin 2 selected */ -#define GPIO_SEL_3 (BIT(3)) /* Pin 3 selected */ -#define GPIO_SEL_4 (BIT(4)) /* Pin 4 selected */ -#define GPIO_SEL_5 (BIT(5)) /* Pin 5 selected */ -#define GPIO_SEL_6 (BIT(6)) /* Pin 6 selected */ -#define GPIO_SEL_7 (BIT(7)) /* Pin 7 selected */ -#define GPIO_SEL_8 (BIT(8)) /* Pin 8 selected */ -#define GPIO_SEL_9 (BIT(9)) /* Pin 9 selected */ -#define GPIO_SEL_10 (BIT(10)) /* Pin 10 selected */ -#define GPIO_SEL_11 (BIT(11)) /* Pin 11 selected */ -#define GPIO_SEL_12 (BIT(12)) /* Pin 12 selected */ -#define GPIO_SEL_13 (BIT(13)) /* Pin 13 selected */ -#define GPIO_SEL_14 (BIT(14)) /* Pin 14 selected */ -#define GPIO_SEL_15 (BIT(15)) /* Pin 15 selected */ -#define GPIO_SEL_16 (BIT(16)) /* Pin 16 selected */ -#define GPIO_SEL_17 (BIT(17)) /* Pin 17 selected */ -#define GPIO_SEL_18 (BIT(18)) /* Pin 18 selected */ -#define GPIO_SEL_19 (BIT(19)) /* Pin 19 selected */ +#define GPIO_SEL_0 (BIT(0)) /*!< Pin 0 selected */ +#define GPIO_SEL_1 (BIT(1)) /*!< Pin 1 selected */ +#define GPIO_SEL_2 (BIT(2)) /*!< Pin 2 selected */ +#define GPIO_SEL_3 (BIT(3)) /*!< Pin 3 selected */ +#define GPIO_SEL_4 (BIT(4)) /*!< Pin 4 selected */ +#define GPIO_SEL_5 (BIT(5)) /*!< Pin 5 selected */ +#define GPIO_SEL_6 (BIT(6)) /*!< Pin 6 selected */ +#define GPIO_SEL_7 (BIT(7)) /*!< Pin 7 selected */ +#define GPIO_SEL_8 (BIT(8)) /*!< Pin 8 selected */ +#define GPIO_SEL_9 (BIT(9)) /*!< Pin 9 selected */ +#define GPIO_SEL_10 (BIT(10)) /*!< Pin 10 selected */ +#define GPIO_SEL_11 (BIT(11)) /*!< Pin 11 selected */ +#define GPIO_SEL_12 (BIT(12)) /*!< Pin 12 selected */ +#define GPIO_SEL_13 (BIT(13)) /*!< Pin 13 selected */ +#define GPIO_SEL_14 (BIT(14)) /*!< Pin 14 selected */ +#define GPIO_SEL_15 (BIT(15)) /*!< Pin 15 selected */ +#define GPIO_SEL_16 (BIT(16)) /*!< Pin 16 selected */ +#define GPIO_SEL_17 (BIT(17)) /*!< Pin 17 selected */ +#define GPIO_SEL_18 (BIT(18)) /*!< Pin 18 selected */ +#define GPIO_SEL_19 (BIT(19)) /*!< Pin 19 selected */ -#define GPIO_SEL_21 (BIT(21)) /* Pin 21 selected */ -#define GPIO_SEL_22 (BIT(22)) /* Pin 22 selected */ -#define GPIO_SEL_23 (BIT(23)) /* Pin 23 selected */ +#define GPIO_SEL_21 (BIT(21)) /*!< Pin 21 selected */ +#define GPIO_SEL_22 (BIT(22)) /*!< Pin 22 selected */ +#define GPIO_SEL_23 (BIT(23)) /*!< Pin 23 selected */ -#define GPIO_SEL_25 (BIT(25)) /* Pin 25 selected */ -#define GPIO_SEL_26 (BIT(26)) /* Pin 26 selected */ -#define GPIO_SEL_27 (BIT(27)) /* Pin 27 selected */ +#define GPIO_SEL_25 (BIT(25)) /*!< Pin 25 selected */ +#define GPIO_SEL_26 (BIT(26)) /*!< Pin 26 selected */ +#define GPIO_SEL_27 (BIT(27)) /*!< Pin 27 selected */ -#define GPIO_SEL_32 ((uint64_t)(((uint64_t)1)<<32)) /* Pin 32 selected */ -#define GPIO_SEL_33 ((uint64_t)(((uint64_t)1)<<33)) /* Pin 33 selected */ -#define GPIO_SEL_34 ((uint64_t)(((uint64_t)1)<<34)) /* Pin 34 selected */ -#define GPIO_SEL_35 ((uint64_t)(((uint64_t)1)<<35)) /* Pin 35 selected */ -#define GPIO_SEL_36 ((uint64_t)(((uint64_t)1)<<36)) /* Pin 36 selected */ -#define GPIO_SEL_37 ((uint64_t)(((uint64_t)1)<<37)) /* Pin 37 selected */ -#define GPIO_SEL_38 ((uint64_t)(((uint64_t)1)<<38)) /* Pin 38 selected */ -#define GPIO_SEL_39 ((uint64_t)(((uint64_t)1)<<39)) /* Pin 39 selected */ +#define GPIO_SEL_32 ((uint64_t)(((uint64_t)1)<<32)) /*!< Pin 32 selected */ +#define GPIO_SEL_33 ((uint64_t)(((uint64_t)1)<<33)) /*!< Pin 33 selected */ +#define GPIO_SEL_34 ((uint64_t)(((uint64_t)1)<<34)) /*!< Pin 34 selected */ +#define GPIO_SEL_35 ((uint64_t)(((uint64_t)1)<<35)) /*!< Pin 35 selected */ +#define GPIO_SEL_36 ((uint64_t)(((uint64_t)1)<<36)) /*!< Pin 36 selected */ +#define GPIO_SEL_37 ((uint64_t)(((uint64_t)1)<<37)) /*!< Pin 37 selected */ +#define GPIO_SEL_38 ((uint64_t)(((uint64_t)1)<<38)) /*!< Pin 38 selected */ +#define GPIO_SEL_39 ((uint64_t)(((uint64_t)1)<<39)) /*!< Pin 39 selected */ #define GPIO_PIN_REG_0 PERIPHS_IO_MUX_GPIO0_U #define GPIO_PIN_REG_1 PERIPHS_IO_MUX_U0TXD_U @@ -117,47 +117,47 @@ extern const uint32_t GPIO_PIN_MUX_REG[GPIO_PIN_COUNT]; #define GPIO_IS_VALID_OUTPUT_GPIO(gpio_num) ((GPIO_IS_VALID_GPIO(gpio_num)) && (gpio_num < 34)) //to decide whether it can be a valid GPIO number of output mode typedef enum { - GPIO_NUM_0 = 0, - GPIO_NUM_1 = 1, - GPIO_NUM_2 = 2, - GPIO_NUM_3 = 3, - GPIO_NUM_4 = 4, - GPIO_NUM_5 = 5, - GPIO_NUM_6 = 6, - GPIO_NUM_7 = 7, - GPIO_NUM_8 = 8, - GPIO_NUM_9 = 9, - GPIO_NUM_10 = 10, - GPIO_NUM_11 = 11, - GPIO_NUM_12 = 12, - GPIO_NUM_13 = 13, - GPIO_NUM_14 = 14, - GPIO_NUM_15 = 15, - GPIO_NUM_16 = 16, - GPIO_NUM_17 = 17, - GPIO_NUM_18 = 18, - GPIO_NUM_19 = 19, + GPIO_NUM_0 = 0, /*!< GPIO0, input and output */ + GPIO_NUM_1 = 1, /*!< GPIO1, input and output */ + GPIO_NUM_2 = 2, /*!< GPIO2, input and output */ + GPIO_NUM_3 = 3, /*!< GPIO3, input and output */ + GPIO_NUM_4 = 4, /*!< GPIO4, input and output */ + GPIO_NUM_5 = 5, /*!< GPIO5, input and output */ + GPIO_NUM_6 = 6, /*!< GPIO6, input and output */ + GPIO_NUM_7 = 7, /*!< GPIO7, input and output */ + GPIO_NUM_8 = 8, /*!< GPIO8, input and output */ + GPIO_NUM_9 = 9, /*!< GPIO9, input and output */ + GPIO_NUM_10 = 10, /*!< GPIO10, input and output */ + GPIO_NUM_11 = 11, /*!< GPIO11, input and output */ + GPIO_NUM_12 = 12, /*!< GPIO12, input and output */ + GPIO_NUM_13 = 13, /*!< GPIO13, input and output */ + GPIO_NUM_14 = 14, /*!< GPIO14, input and output */ + GPIO_NUM_15 = 15, /*!< GPIO15, input and output */ + GPIO_NUM_16 = 16, /*!< GPIO16, input and output */ + GPIO_NUM_17 = 17, /*!< GPIO17, input and output */ + GPIO_NUM_18 = 18, /*!< GPIO18, input and output */ + GPIO_NUM_19 = 19, /*!< GPIO19, input and output */ - GPIO_NUM_21 = 21, - GPIO_NUM_22 = 22, - GPIO_NUM_23 = 23, + GPIO_NUM_21 = 21, /*!< GPIO21, input and output */ + GPIO_NUM_22 = 22, /*!< GPIO22, input and output */ + GPIO_NUM_23 = 23, /*!< GPIO23, input and output */ - GPIO_NUM_25 = 25, - GPIO_NUM_26 = 26, - GPIO_NUM_27 = 27, + GPIO_NUM_25 = 25, /*!< GPIO25, input and output */ + GPIO_NUM_26 = 26, /*!< GPIO26, input and output */ + GPIO_NUM_27 = 27, /*!< GPIO27, input and output */ - GPIO_NUM_32 = 32, - GPIO_NUM_33 = 33, - GPIO_NUM_34 = 34, /*input mode only */ - GPIO_NUM_35 = 35, /*input mode only */ - GPIO_NUM_36 = 36, /*input mode only */ - GPIO_NUM_37 = 37, /*input mode only */ - GPIO_NUM_38 = 38, /*input mode only */ - GPIO_NUM_39 = 39, /*input mode only */ + GPIO_NUM_32 = 32, /*!< GPIO32, input and output */ + GPIO_NUM_33 = 33, /*!< GPIO32, input and output */ + GPIO_NUM_34 = 34, /*!< GPIO34, input mode only */ + GPIO_NUM_35 = 35, /*!< GPIO35, input mode only */ + GPIO_NUM_36 = 36, /*!< GPIO36, input mode only */ + GPIO_NUM_37 = 37, /*!< GPIO37, input mode only */ + GPIO_NUM_38 = 38, /*!< GPIO38, input mode only */ + GPIO_NUM_39 = 39, /*!< GPIO39, input mode only */ } gpio_num_t; typedef enum { - GPIO_INTR_DISABLE = 0, /*!< disable GPIO interrupt */ + GPIO_INTR_DISABLE = 0, /*!< Disable GPIO interrupt */ GPIO_INTR_POSEDGE = 1, /*!< GPIO interrupt type : rising edge */ GPIO_INTR_NEGEDGE = 2, /*!< GPIO interrupt type : falling edge */ GPIO_INTR_ANYEDGE = 3, /*!< GPIO interrupt type : both rising and falling edge */ @@ -175,13 +175,13 @@ typedef enum { } gpio_mode_t; typedef enum { - GPIO_PULLUP_DISABLE = 0x0, /*!< disable GPIO pull-up resistor */ - GPIO_PULLUP_ENABLE = 0x1, /*!< enable GPIO pull-up resistor */ + GPIO_PULLUP_DISABLE = 0x0, /*!< Disable GPIO pull-up resistor */ + GPIO_PULLUP_ENABLE = 0x1, /*!< Enable GPIO pull-up resistor */ } gpio_pullup_t; typedef enum { - GPIO_PULLDOWN_DISABLE = 0x0, /*!< disable GPIO pull-down resistor */ - GPIO_PULLDOWN_ENABLE = 0x1, /*!< enable GPIO pull-down resistor */ + GPIO_PULLDOWN_DISABLE = 0x0, /*!< Disable GPIO pull-down resistor */ + GPIO_PULLDOWN_ENABLE = 0x1, /*!< Enable GPIO pull-down resistor */ } gpio_pulldown_t; typedef struct { @@ -192,12 +192,6 @@ typedef struct { gpio_int_type_t intr_type; /*!< GPIO interrupt type */ } gpio_config_t; -typedef enum { - GPIO_LOW_LEVEL = 0, - GPIO_HIGH_LEVEL = 1, - GPIO_LEVEL_ERR, -} gpio_level_t; - typedef enum { GPIO_PULLUP_ONLY, /*!< Pad pull up */ GPIO_PULLDOWN_ONLY, /*!< Pad pull down */ @@ -207,254 +201,250 @@ typedef enum { typedef void (*gpio_event_callback)(gpio_num_t gpio_intr_num); -/** \defgroup Driver_APIs Driver APIs - * @brief Driver APIs - */ - -/** @addtogroup Driver_APIs - * @{ - */ - -/** \defgroup GPIO_Driver_APIs GPIO Driver APIs - * @brief GPIO APIs - */ - -/** @addtogroup GPIO_Driver_APIs - * @{ - */ - /** - * @brief GPIO common configuration + * @brief GPIO common configuration * - * Use this Function ,Configure GPIO's Mode,pull-up,PullDown,IntrType + * Configure GPIO's Mode,pull-up,PullDown,IntrType * - * @param[in] pGPIOConfig - * pGPIOConfig.pin_bit_mask : Configure GPIO pins bits,set this parameter with bit mask. - * If you want to configure GPIO34 and GPIO16, pin_bit_mask=GPIO_Pin_16|GPIO_Pin_34; - * pGPIOConfig.mode : Configure GPIO mode,such as output ,input... - * pGPIOConfig.pull_up_en : Enable or Disable pull-up - * pGPIOConfig.pull_down_en : Enable or Disable pull-down - * pGPIOConfig.intr_type : Configure GPIO interrupt trigger type - * @return ESP_OK: success ; - * ESP_ERR_INVALID_ARG: parameter error - * ESP_FAIL : GPIO error + * @param pGPIOConfig Pointer to GPIO configure struct + * + * @return + * - ESP_OK success + * - ESP_ERR_INVALID_ARG Parameter error * */ esp_err_t gpio_config(gpio_config_t *pGPIOConfig); /** - * @brief GPIO set interrupt trigger type + * @brief GPIO set interrupt trigger type * - * @param[in] gpio_num : GPIO number. - * If you want to set output level of GPIO16, gpio_num should be GPIO_NUM_16 (16); - * @param[in] intr_type: interrupt type, select from gpio_int_type_t + * @param gpio_num GPIO number. If you want to set output level of GPIO16, gpio_num should be GPIO_NUM_16 (16); + * @param intr_type Interrupt type, select from gpio_int_type_t * - * @return ESP_OK : success - * ESP_ERR_INVALID_ARG: parameter error + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error * */ esp_err_t gpio_set_intr_type(gpio_num_t gpio_num, gpio_int_type_t intr_type); /** - * @brief enable GPIO module interrupt signal + * @brief Enable GPIO module interrupt signal * - * @param[in] gpio_num : GPIO number. - * If you want to set output level of GPIO16, gpio_num should be GPIO_NUM_16 (16); + * @param gpio_num GPIO number. If you want to set output level of GPIO16, gpio_num should be GPIO_NUM_16 (16); * - * @return ESP_OK : success - * ESP_ERR_INVALID_ARG: parameter error + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error * */ esp_err_t gpio_intr_enable(gpio_num_t gpio_num); /** - * @brief disable GPIO module interrupt signal + * @brief Disable GPIO module interrupt signal * - * @param[in] gpio_num : GPIO number. - * If you want to set output level of GPIO16, gpio_num should be GPIO_NUM_16 (16); + * @param gpio_num GPIO number. If you want to set output level of GPIO16, gpio_num should be GPIO_NUM_16 (16); * - * @return ESP_OK : success - * ESP_ERR_INVALID_ARG: parameter error + * @return + * - ESP_OK success + * - ESP_ERR_INVALID_ARG Parameter error * */ esp_err_t gpio_intr_disable(gpio_num_t gpio_num); /** - * @brief GPIO set output level + * @brief GPIO set output level * - * @param[in] gpio_num : GPIO number. - * If you want to set output level of GPIO16, gpio_num should be GPIO_NUM_16 (16); - * @param[in] level : Output level. 0: low ; 1: high + * @param gpio_num GPIO number. If you want to set output level of GPIO16, gpio_num should be GPIO_NUM_16 (16); + * @param level Output level. 0: low ; 1: high * - * @return ESP_OK : success - * ESP_FAIL : GPIO error + * @return + * - ESP_OK Success + * - GPIO_IS_VALID_GPIO GPIO number error * */ esp_err_t gpio_set_level(gpio_num_t gpio_num, uint32_t level); /** - * @brief GPIO get input level + * @brief GPIO get input level * - * @param[in] gpio_num : GPIO number. - * If you want to get level of pin GPIO16, gpio_num should be GPIO_NUM_16 (16); + * @param gpio_num GPIO number. If you want to get level of pin GPIO16, gpio_num should be GPIO_NUM_16 (16); * - * @return 0 : the GPIO input level is 0 - * 1 : the GPIO input level is 1 + * @return + * - 0 the GPIO input level is 0 + * - 1 the GPIO input level is 1 * */ int gpio_get_level(gpio_num_t gpio_num); /** - * @brief GPIO set direction + * @brief GPIO set direction * * Configure GPIO direction,such as output_only,input_only,output_and_input * - * @param[in] gpio_num : Configure GPIO pins number,it should be GPIO number. - * If you want to set direction of GPIO16, gpio_num should be GPIO_NUM_16 (16); - * @param[in] mode : Configure GPIO direction,such as output_only,input_only,... + * @param gpio_num Configure GPIO pins number, it should be GPIO number. If you want to set direction of GPIO16, gpio_num should be GPIO_NUM_16 (16); + * @param mode GPIO direction * - * @return ESP_OK : success - * ESP_ERR_INVALID_ARG : fail - * ESP_FAIL : GPIO error + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG GPIO error * */ esp_err_t gpio_set_direction(gpio_num_t gpio_num, gpio_mode_t mode); /** - * @brief GPIO set pull + * @brief GPIO set pull * * User this Function,configure GPIO pull mode,such as pull-up,pull-down * - * @param[in] gpio_num : Configure GPIO pins number,it should be GPIO number. - * If you want to set pull up or down mode for GPIO16,gpio_num should be GPIO_NUM_16 (16); - * @param[in] pull : Configure GPIO pull up/down mode,such as pullup_only,pulldown_only,pullup_and_pulldown,... + * @param gpio_num GPIO number. If you want to set pull up or down mode for GPIO16,gpio_num should be GPIO_NUM_16 (16); + * @param pull GPIO pull up/down mode. * - * @return ESP_OK : success - * ESP_ERR_INVALID_ARG : fail - * ESP_FAIL : GPIO error + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG : Parameter error * */ esp_err_t gpio_set_pull_mode(gpio_num_t gpio_num, gpio_pull_mode_t pull); /** - * @brief enable GPIO wake-up function. + * @brief enable GPIO wake-up function. * - * @param gpio_num : GPIO number. + * @param gpio_num GPIO number. * - * @param intr_type : only GPIO_INTR_LOLEVEL\GPIO_INTR_HILEVEL can be used + * @param intr_type GPIO wake-up type. Only GPIO_INTR_LOLEVEL\GPIO_INTR_HILEVEL can be used * - * @return ESP_OK: success - * ESP_ERR_INVALID_ARG: parameter error + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error */ esp_err_t gpio_wakeup_enable(gpio_num_t gpio_num, gpio_int_type_t intr_type); /** - * @brief disable GPIO wake-up function. + * @brief Disable GPIO wake-up function. * - * @param gpio_num: GPIO number + * @param gpio_num GPIO number * - * @return ESP_OK: success - * ESP_ERR_INVALID_ARG: parameter error + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error */ esp_err_t gpio_wakeup_disable(gpio_num_t gpio_num); /** * @brief register GPIO interrupt handler, the handler is an ISR. * The handler will be attached to the same CPU core that this function is running on. + * @note * Users should know that which CPU is running and then pick a INUM that is not used by system. * We can find the information of INUM and interrupt level in soc.h. - * TODO: to move INUM options to menu_config - * @param gpio_intr_num : GPIO interrupt number,check the info in soc.h, and please see the core-isa.h for more details - * @param (*fn)(void* ) : interrupt handler function. - * Note that the handler function MUST be defined with attribution of "IRAM_ATTR". - * @param arg : parameter for handler function * - * @return ESP_OK : success ; - * ESP_FAIL: gpio error + * @param gpio_intr_num GPIO interrupt number,check the info in soc.h, and please see the core-isa.h for more details + * @param fn Interrupt handler function. + * + * @note + * Note that the handler function MUST be defined with attribution of "IRAM_ATTR". + * + * @param arg Parameter for handler function + * + * @return + * - ESP_OK Success ; + * - ESP_ERR_INVALID_ARG GPIO error */ esp_err_t gpio_isr_register(uint32_t gpio_intr_num, void (*fn)(void*), void * arg); /** * *************** ATTENTION ********************/ /** - * - * Each GPIO has its own separate configuration register, so we do not use - * a lock to serialize access to them. This works under the assumption that - * no situation will occur where two tasks try to configure the same GPIO - * pin simultaneously. It is up to the application developer to guarantee this. + *@attention + * Each GPIO has its own separate configuration register, so we do not use + * a lock to serialize access to them. This works under the assumption that + * no situation will occur where two tasks try to configure the same GPIO + * pin simultaneously. It is up to the application developer to guarantee this. */ - -/*----------EXAMPLE TO CONIFGURE GPIO AS OUTPUT ------------ */ -/* gpio_config_t io_conf; +/** + *----------EXAMPLE TO CONIFGURE GPIO AS OUTPUT ------------ * + * @code{c} + * gpio_config_t io_conf; * io_conf.intr_type = GPIO_INTR_DISABLE; //disable interrupt * io_conf.mode = GPIO_MODE_OUTPUT; //set as output mode * io_conf.pin_bit_mask = GPIO_SEL_18 | GPIO_SEL_19; //bit mask of the pins that you want to set,e.g.GPIO18/19 * io_conf.pull_down_en = 0; //disable pull-down mode * io_conf.pull_up_en = 0; //disable pull-up mode * gpio_config(&io_conf); //configure GPIO with the given settings + * @endcode **/ -/*----------EXAMPLE TO CONIFGURE GPIO AS OUTPUT ------------ */ -/* io_conf.intr_type = GPIO_INTR_POSEDGE; //set posedge interrupt + +/** + *----------EXAMPLE TO CONIFGURE GPIO AS OUTPUT ------------ * + * @code{c} + * io_conf.intr_type = GPIO_INTR_POSEDGE; //set posedge interrupt * io_conf.mode = GPIO_MODE_INPUT; //set as input * io_conf.pin_bit_mask = GPIO_SEL_4 | GPIO_SEL_5; //bit mask of the pins that you want to set, e.g.,GPIO4/5 * io_conf.pull_down_en = 0; //disable pull-down mode * io_conf.pull_up_en = 1; //enable pull-up mode * gpio_config(&io_conf); //configure GPIO with the given settings - *----------EXAMPLE TO SET ISR HANDLER ----------------------*/ -/* gpio_isr_register(18,gpio_intr_test,NULL); //hook the isr handler for GPIO interrupt - * //the first parameter is INUM, you can pick one form interrupt level 1/2 which is not used by the system. - * //NOTE1:user should arrange the INUMs that used, better not to use a same INUM for different interrupt. - * //NOTE2:do not pick the INUM that already occupied by the system. - * //NOTE3:refer to soc.h to check which INUMs that can be used. - *-------------EXAMPLE OF HANDLER FUNCTION-------------------*/ -/*#include "esp_attr.h" + * @endcode + */ +/** + *----------EXAMPLE TO SET ISR HANDLER ---------------------- + * @code{c} + * //the first parameter is INUM, you can pick one form interrupt level 1/2 which is not used by the system. + * gpio_isr_register(18,gpio_intr_test,NULL); //hook the isr handler for GPIO interrupt + * @endcode + * @note + * 1. user should arrange the INUMs that used, better not to use a same INUM for different interrupt. + * 2. do not pick the INUM that already occupied by the system. + * 3. refer to soc.h to check which INUMs that can be used. + */ +/** + *-------------EXAMPLE OF HANDLER FUNCTION-------------------* + * @code{c} + * #include "esp_attr.h" * void IRAM_ATTR gpio_intr_test(void* arg) - *{ - * //GPIO intr process - * ets_printf("in gpio_intr\n"); - * uint32_t gpio_num = 0; - * uint32_t gpio_intr_status = READ_PERI_REG(GPIO_STATUS_REG); //read status to get interrupt status for GPIO0-31 - * uint32_t gpio_intr_status_h = READ_PERI_REG(GPIO_STATUS1_REG);//read status1 to get interrupt status for GPIO32-39 - * SET_PERI_REG_MASK(GPIO_STATUS_W1TC_REG, gpio_intr_status); //Clear intr for gpio0-gpio31 - * SET_PERI_REG_MASK(GPIO_STATUS1_W1TC_REG, gpio_intr_status_h); //Clear intr for gpio32-39 - * do { - * if(gpio_num < 32) { - * if(gpio_intr_status & BIT(gpio_num)) { //gpio0-gpio31 - * ets_printf("Intr GPIO%d ,val: %d\n",gpio_num,gpio_get_level(gpio_num)); - * //This is an isr handler, you should post an event to process it in RTOS queue. - * } - * } else { - * if(gpio_intr_status_h & BIT(gpio_num - 32)) { - * ets_printf("Intr GPIO%d, val : %d\n",gpio_num,gpio_get_level(gpio_num)); - * //This is an isr handler, you should post an event to process it in RTOS queue. - * } - * } - * } while(++gpio_num < GPIO_PIN_COUNT); - *} - *----EXAMPLE OF I2C CONFIG AND PICK SIGNAL FOR IO MATRIX---*/ -/* gpio_config_t io_conf; - * io_conf.intr_type = GPIO_INTR_DISABLE; //disable interrupt - * io_conf.mode = GPIO_MODE_INPUT_OUTPUT_OD; //set as output mode - * io_conf.pin_bit_mask = GPIO_SEL_21 | GPIO_SEL_22; //bit mask of the pins that you want to set,e.g.GPIO21/22 - * io_conf.pull_down_en = 0; //disable pull-down mode - * io_conf.pull_up_en = 1; //enable pull-up mode - * gpio_config(&io_conf); //configure GPIO with the given settings - * gpio_matrix_out(21, EXT_I2C_SCL_O_IDX, 0, 0); //set output signal for io_matrix - * gpio_matrix_out(22, EXT_I2C_SDA_O_IDX, 0, 0); //set output signal for io_matrix - * gpio_matrix_in( 22, EXT_I2C_SDA_I_IDX, 0); //set input signal for io_matrix + * { + * //GPIO intr process + * ets_printf("in gpio_intr\n"); + * uint32_t gpio_num = 0; + * uint32_t gpio_intr_status = READ_PERI_REG(GPIO_STATUS_REG); //read status to get interrupt status for GPIO0-31 + * uint32_t gpio_intr_status_h = READ_PERI_REG(GPIO_STATUS1_REG);//read status1 to get interrupt status for GPIO32-39 + * SET_PERI_REG_MASK(GPIO_STATUS_W1TC_REG, gpio_intr_status); //Clear intr for gpio0-gpio31 + * SET_PERI_REG_MASK(GPIO_STATUS1_W1TC_REG, gpio_intr_status_h); //Clear intr for gpio32-39 + * do { + * if(gpio_num < 32) { + * if(gpio_intr_status & BIT(gpio_num)) { //gpio0-gpio31 + * ets_printf("Intr GPIO%d ,val: %d\n",gpio_num,gpio_get_level(gpio_num)); + * //This is an isr handler, you should post an event to process it in RTOS queue. + * } + * } else { + * if(gpio_intr_status_h & BIT(gpio_num - 32)) { + * ets_printf("Intr GPIO%d, val : %d\n",gpio_num,gpio_get_level(gpio_num)); + * //This is an isr handler, you should post an event to process it in RTOS queue. + * } + * } + * } while(++gpio_num < GPIO_PIN_COUNT); + * } + * @endcode + */ + +/** + *----EXAMPLE OF I2C CONFIG AND PICK SIGNAL FOR IO MATRIX---* + * @code{c} + * gpio_config_t io_conf; + * io_conf.intr_type = GPIO_INTR_DISABLE; //disable interrupt + * io_conf.mode = GPIO_MODE_INPUT_OUTPUT_OD; //set as output mode + * io_conf.pin_bit_mask = GPIO_SEL_21 | GPIO_SEL_22; //bit mask of the pins that you want to set,e.g.GPIO21/22 + * io_conf.pull_down_en = 0; //disable pull-down mode + * io_conf.pull_up_en = 1; //enable pull-up mode + * gpio_config(&io_conf); //configure GPIO with the given settings + * gpio_matrix_out(21, EXT_I2C_SCL_O_IDX, 0, 0); //set output signal for io_matrix + * gpio_matrix_out(22, EXT_I2C_SDA_O_IDX, 0, 0); //set output signal for io_matrix + * gpio_matrix_in( 22, EXT_I2C_SDA_I_IDX, 0); //set input signal for io_matrix + * @endcode * */ -/** - * @} - */ - -/** - * @} - */ - #ifdef __cplusplus } #endif diff --git a/components/driver/include/driver/ledc.h b/components/driver/include/driver/ledc.h index 79a6c7f9f..3ab0ebff1 100644 --- a/components/driver/include/driver/ledc.h +++ b/components/driver/include/driver/ledc.h @@ -30,68 +30,68 @@ extern "C" { #define LEDC_REF_CLK_HZ (1*1000000) typedef enum { - LEDC_HIGH_SPEED_MODE = 0, /*LEDC high speed speed_mode */ + LEDC_HIGH_SPEED_MODE = 0, /*!< LEDC high speed speed_mode */ //in this version, we only support high speed speed_mode. We will access low speed speed_mode later - //LEDC_LOW_SPEED_MODE, /*LEDC low speed speed_mode */ - LEDC_SPEED_MODE_MAX, + //LEDC_LOW_SPEED_MODE, /*!< LEDC low speed speed_mode */ + LEDC_SPEED_MODE_MAX, /*!< LEDC speed limit */ } ledc_mode_t; typedef enum { - LEDC_INTR_DISABLE = 0, /*Disable LEDC interrupt */ - LEDC_INTR_FADE_END, /*Enable LEDC interrupt */ + LEDC_INTR_DISABLE = 0, /*!< Disable LEDC interrupt */ + LEDC_INTR_FADE_END, /*!< Enable LEDC interrupt */ } ledc_intr_type_t; typedef enum { - LEDC_DUTY_DIR_DECREASE = 0, /*LEDC duty decrease direction */ - LEDC_DUTY_DIR_INCREASE = 1, /*LEDC duty increase direction */ + LEDC_DUTY_DIR_DECREASE = 0, /*!< LEDC duty decrease direction */ + LEDC_DUTY_DIR_INCREASE = 1, /*!< LEDC duty increase direction */ } ledc_duty_direction_t; typedef enum { - LEDC_REF_TICK = 0, /*LEDC timer clock divided from reference tick(1Mhz) */ - LEDC_APB_CLK, /*LEDC timer clock divided from APB clock(80Mhz)*/ + LEDC_REF_TICK = 0, /*!< LEDC timer clock divided from reference tick(1Mhz) */ + LEDC_APB_CLK, /*!< LEDC timer clock divided from APB clock(80Mhz)*/ } ledc_clk_src_t; typedef enum { - LEDC_TIMER_0 = 0, /*LEDC source timer TIMER0 */ - LEDC_TIMER_1, /*LEDC source timer TIMER1 */ - LEDC_TIMER_2, /*LEDC source timer TIMER2 */ - LEDC_TIMER_3, /*LEDC source timer TIMER3 */ + LEDC_TIMER_0 = 0, /*!< LEDC source timer TIMER0 */ + LEDC_TIMER_1, /*!< LEDC source timer TIMER1 */ + LEDC_TIMER_2, /*!< LEDC source timer TIMER2 */ + LEDC_TIMER_3, /*!< LEDC source timer TIMER3 */ } ledc_timer_t; typedef enum { - LEDC_CHANNEL_0 = 0, /*LEDC channel 0 */ - LEDC_CHANNEL_1, /*LEDC channel 1 */ - LEDC_CHANNEL_2, /*LEDC channel 2 */ - LEDC_CHANNEL_3, /*LEDC channel 3 */ - LEDC_CHANNEL_4, /*LEDC channel 4 */ - LEDC_CHANNEL_5, /*LEDC channel 5 */ - LEDC_CHANNEL_6, /*LEDC channel 6 */ - LEDC_CHANNEL_7, /*LEDC channel 7 */ + LEDC_CHANNEL_0 = 0, /*!< LEDC channel 0 */ + LEDC_CHANNEL_1, /*!< LEDC channel 1 */ + LEDC_CHANNEL_2, /*!< LEDC channel 2 */ + LEDC_CHANNEL_3, /*!< LEDC channel 3 */ + LEDC_CHANNEL_4, /*!< LEDC channel 4 */ + LEDC_CHANNEL_5, /*!< LEDC channel 5 */ + LEDC_CHANNEL_6, /*!< LEDC channel 6 */ + LEDC_CHANNEL_7, /*!< LEDC channel 7 */ } ledc_channel_t; typedef enum { - LEDC_TIMER_10_BIT = 10, /*LEDC PWM depth 10Bit */ - LEDC_TIMER_11_BIT = 11, /*LEDC PWM depth 11Bit */ - LEDC_TIMER_12_BIT = 12, /*LEDC PWM depth 12Bit */ - LEDC_TIMER_13_BIT = 13, /*LEDC PWM depth 13Bit */ - LEDC_TIMER_14_BIT = 14, /*LEDC PWM depth 14Bit */ - LEDC_TIMER_15_BIT = 15, /*LEDC PWM depth 15Bit */ + LEDC_TIMER_10_BIT = 10, /*!< LEDC PWM depth 10Bit */ + LEDC_TIMER_11_BIT = 11, /*!< LEDC PWM depth 11Bit */ + LEDC_TIMER_12_BIT = 12, /*!< LEDC PWM depth 12Bit */ + LEDC_TIMER_13_BIT = 13, /*!< LEDC PWM depth 13Bit */ + LEDC_TIMER_14_BIT = 14, /*!< LEDC PWM depth 14Bit */ + LEDC_TIMER_15_BIT = 15, /*!< LEDC PWM depth 15Bit */ } ledc_timer_bit_t; typedef struct { - int gpio_num; /*the LEDC output gpio_num, if you want to use gpio16, gpio_num = 16*/ - ledc_mode_t speed_mode; /*LEDC speed speed_mode, high-speed mode or low-speed mode*/ - ledc_channel_t channel; /*LEDC channel(0 - 7)*/ - ledc_intr_type_t intr_type; /*configure interrupt, Fade interrupt enable or Fade interrupt disable*/ - ledc_timer_t timer_sel; /*Select the timer source of channel (0 - 3)*/ - uint32_t duty; /*LEDC channel duty, the duty range is [0, (2**bit_num) - 1], */ + int gpio_num; /*!< the LEDC output gpio_num, if you want to use gpio16, gpio_num = 16*/ + ledc_mode_t speed_mode; /*!< LEDC speed speed_mode, high-speed mode or low-speed mode*/ + ledc_channel_t channel; /*!< LEDC channel(0 - 7)*/ + ledc_intr_type_t intr_type; /*!< configure interrupt, Fade interrupt enable or Fade interrupt disable*/ + ledc_timer_t timer_sel; /*!< Select the timer source of channel (0 - 3)*/ + uint32_t duty; /*!< LEDC channel duty, the duty range is [0, (2**bit_num) - 1], */ } ledc_channel_config_t; typedef struct { - ledc_mode_t speed_mode; /*LEDC speed speed_mode, high-speed mode or low-speed mode*/ - ledc_timer_bit_t bit_num; /*LEDC channel duty depth*/ - ledc_timer_t timer_num; /*The timer source of channel (0 - 3)*/ - uint32_t freq_hz; /*LEDC timer frequency(Hz)*/ + ledc_mode_t speed_mode; /*!< LEDC speed speed_mode, high-speed mode or low-speed mode*/ + ledc_timer_bit_t bit_num; /*!< LEDC channel duty depth*/ + ledc_timer_t timer_num; /*!< The timer source of channel (0 - 3)*/ + uint32_t freq_hz; /*!< LEDC timer frequency(Hz)*/ } ledc_timer_config_t; @@ -100,15 +100,10 @@ typedef struct { * * User this Function, configure LEDC channel with the given channel/output gpio_num/interrupt/source timer/frequency(Hz)/LEDC depth * - * @param[in] ledc_channel_config_t - * ledc_channel_config_t.speed_mode : LEDC speed speed_mode - * ledc_channel_config_t.gpio_num : LEDC output gpio_num, if you want to use gpio16, ledc_channel_config_t.gpio_num = 16 - * ledc_channel_config_t.channel : LEDC channel(0 - 7) - * ledc_channel_config_t.intr_type : configure interrupt, Fade interrupt enable or Fade interrupt disable - * ledc_channel_config_t.timer_sel : Select the timer source of channel (0 - 3), high speed channel must bind with high speed timer. - * ledc_channel_config_t.duty : LEDC channel duty, the duty range is [0, (2**timer_bit_num) - 1], - * @return ESP_OK: success - * ESP_ERR_INVALID_ARG: parameter error + * @param ledc_conf Pointer of LEDC channel configure struct + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error * */ esp_err_t ledc_channel_config(ledc_channel_config_t* ledc_conf); @@ -118,14 +113,13 @@ esp_err_t ledc_channel_config(ledc_channel_config_t* ledc_conf); * * User this Function, configure LEDC timer with the given source timer/frequency(Hz)/bit_num * - * @param[in] ledc_timer_config_t - * ledc_timer_config_t.speed_mode : LEDC speed speed_mode - * ledc_timer_config_t.timer_num : Select the timer source of channel (0 - 3) - * ledc_timer_config_t.freq_hz : LEDC channel frequency(Hz), - * ledc_timer_config_t.bit_num : LEDC channel duty depth - * @return ESP_OK: success - * ESP_ERR_INVALID_ARG: parameter error - * ESP_FAIL: Can not find a proper pre-divider number base on the given frequency and the current bit_num. + * @param timer_conf Pointer of LEDC timer configure struct + * + * + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_FAIL Can not find a proper pre-divider number base on the given frequency and the current bit_num. * */ esp_err_t ledc_timer_config(ledc_timer_config_t* timer_conf); @@ -136,12 +130,13 @@ esp_err_t ledc_timer_config(ledc_timer_config_t* timer_conf); * Call this function to activate the LEDC updated parameters. * After ledc_set_duty, ledc_set_fade, we need to call this function to update the settings. * - * @param[in] speed_mode : select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version + * @param speed_mode Select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version * - * @param[in] channel : LEDC channel(0-7), select from ledc_channel_t + * @param channel LEDC channel(0-7), select from ledc_channel_t * - * @return ESP_OK: success - * ESP_ERR_INVALID_ARG: parameter error + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error * */ esp_err_t ledc_update_duty(ledc_mode_t speed_mode, ledc_channel_t channel); @@ -151,12 +146,13 @@ esp_err_t ledc_update_duty(ledc_mode_t speed_mode, ledc_channel_t channel); * * Disable LEDC output, and set idle level * - * @param[in] speed_mode : select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version + * @param speed_mode Select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version * - * @param[in] channel : LEDC channel(0-7), select from ledc_channel_t + * @param channel LEDC channel(0-7), select from ledc_channel_t * - * @return ESP_OK: success - * ESP_ERR_INVALID_ARG: parameter error + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error */ esp_err_t ledc_stop(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t idle_level); @@ -165,27 +161,29 @@ esp_err_t ledc_stop(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t idl * * Set LEDC frequency(Hz) * - * @param[in] speed_mode : select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version + * @param speed_mode Select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version * - * @param[in] timer_num : LEDC timer index(0-3), select from ledc_timer_t + * @param timer_num LEDC timer index(0-3), select from ledc_timer_t * - * @param[in] freq_hz : set the LEDC frequency + * @param freq_hz Set the LEDC frequency * - * @return ESP_OK: success - * ESP_ERR_INVALID_ARG: parameter error - * ESP_FAIL: Can not find a proper pre-divider number base on the given frequency and the current bit_num. + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_FAIL Can not find a proper pre-divider number base on the given frequency and the current bit_num. */ esp_err_t ledc_set_freq(ledc_mode_t speed_mode, ledc_timer_t timer_num, uint32_t freq_hz); /** * @brief LEDC get channel frequency(Hz) * - * @param[in] speed_mode : select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version + * @param speed_mode Select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version * - * @param[in] timer_num : LEDC timer index(0-3), select from ledc_timer_t + * @param timer_num LEDC timer index(0-3), select from ledc_timer_t * - * @return 0 : error - * others : current LEDC frequency + * @return + * - 0 error + * - Others Current LEDC frequency * */ uint32_t ledc_get_freq(ledc_mode_t speed_mode, ledc_timer_t timer_num); @@ -195,27 +193,29 @@ uint32_t ledc_get_freq(ledc_mode_t speed_mode, ledc_timer_t timer_num); * * Set LEDC duty, After the function calls the ledc_update_duty function, the function can take effect. * - * @param[in] speed_mode : select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version + * @param speed_mode Select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version * - * @param[in] channel : LEDC channel(0-7), select from ledc_channel_t + * @param channel LEDC channel(0-7), select from ledc_channel_t * - * @param[in] duty : set the LEDC duty, the duty range is [0, (2**bit_num) - 1] + * @param duty Set the LEDC duty, the duty range is [0, (2**bit_num) - 1] * - * @return ESP_OK: success - * ESP_ERR_INVALID_ARG: parameter error + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error */ esp_err_t ledc_set_duty(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t duty); /** * @brief LEDC get duty * - * @param[in] speed_mode : select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version + * @param speed_mode Select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version * - * @param[in] channel : LEDC channel(0-7), select from ledc_channel_t + * @param channel LEDC channel(0-7), select from ledc_channel_t * * - * @return -1: parameter error - * other value: current LEDC duty + * @return + * - (-1) parameter error + * - Others Current LEDC duty * */ int ledc_get_duty(ledc_mode_t speed_mode, ledc_channel_t channel); @@ -225,22 +225,23 @@ int ledc_get_duty(ledc_mode_t speed_mode, ledc_channel_t channel); * * Set LEDC gradient, After the function calls the ledc_update_duty function, the function can take effect. * - * @param[in] speed_mode : select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version + * @param speed_mode Select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version * - * @param[in] channel : LEDC channel(0-7), select from ledc_channel_t + * @param channel LEDC channel(0-7), select from ledc_channel_t * - * @param[in] duty : set the start of the gradient duty, the duty range is [0, (2**bit_num) - 1] + * @param duty Set the start of the gradient duty, the duty range is [0, (2**bit_num) - 1] * - * @param[in] gradule_direction : set the direction of the gradient + * @param gradule_direction Set the direction of the gradient * - * @param[in] step_num : set the number of the gradient + * @param step_num Set the number of the gradient * - * @param[in] duty_cyle_num : set how many LEDC tick each time the gradient lasts + * @param duty_cyle_num Set how many LEDC tick each time the gradient lasts * - * @param[in] duty_scale : set gradient change amplitude + * @param duty_scale Set gradient change amplitude * - * @return ESP_OK : success - * ESP_ERR_INVALID_ARG : parameter error + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error */ esp_err_t ledc_set_fade(ledc_mode_t speed_mode, uint32_t channel, uint32_t duty, ledc_duty_direction_t gradule_direction, uint32_t step_num, uint32_t duty_cyle_num, uint32_t duty_scale); @@ -248,34 +249,37 @@ esp_err_t ledc_set_fade(ledc_mode_t speed_mode, uint32_t channel, uint32_t duty, /** * @brief register LEDC interrupt handler, the handler is an ISR. * The handler will be attached to the same CPU core that this function is running on. - * Users should know that which CPU is running and then pick a INUM that is not used by system. - * We can find the information of INUM and interrupt level in soc.h. - * TODO: to move INUM options to menu_config - * @param[in] uint32_t ledc_intr_num : LEDC interrupt number, check the info in soc.h, and please see the core-isa.h for more details - * @param[in] void (* fn)(void* ) : interrupt handler function. - * Note that the handler function MUST be defined with attribution of "IRAM_ATTR". - * @param[in] void * arg : parameter for handler function + * @note + * Users should know that which CPU is running and then pick a INUM that is not used by system. + * We can find the information of INUM and interrupt level in soc.h. + * @param ledc_intr_num LEDC interrupt number, check the info in soc.h, and please see the core-isa.h for more details + * @param fn Interrupt handler function. + * @note + * Note that the handler function MUST be defined with attribution of "IRAM_ATTR". + * @param arg Parameter for handler function * - * @return ESP_OK : success ; - * ESP_ERR_INVALID_ARG : function ptr error. + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Function pointer error. */ esp_err_t ledc_isr_register(uint32_t ledc_intr_num, void (*fn)(void*), void * arg); /** * @brief configure LEDC settings * - * @param[in] speed_mode : select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version + * @param speed_mode Select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version * - * @param[in] timer_sel : timer index(0-3), there are 4 timers in LEDC module + * @param timer_sel Timer index(0-3), there are 4 timers in LEDC module * - * @param[in] div_num : timer clock divide number, the timer clock is divided from the selected clock source + * @param div_num Timer clock divide number, the timer clock is divided from the selected clock source * - * @param[in] bit_num : the count number of one period, counter range is 0 ~ ((2 ** bit_num) - 1) + * @param bit_num The count number of one period, counter range is 0 ~ ((2 ** bit_num) - 1) * - * @param[in] clk_src : select LEDC source clock. + * @param clk_src Select LEDC source clock. * - * @return -1: parameter error - * other value: current LEDC duty + * @return + * - (-1) Parameter error + * - Other Current LEDC duty * */ esp_err_t ledc_timer_set(ledc_mode_t speed_mode, ledc_timer_t timer_sel, uint32_t div_num, uint32_t bit_num, ledc_clk_src_t clk_src); @@ -283,13 +287,14 @@ esp_err_t ledc_timer_set(ledc_mode_t speed_mode, ledc_timer_t timer_sel, uint32_ /** * @brief reset LEDC timer * - * @param[in] speed_mode : select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version + * @param speed_mode Select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version * - * @param[in] timer_sel : LEDC timer index(0-3), select from ledc_timer_t + * @param timer_sel LEDC timer index(0-3), select from ledc_timer_t * * - * @return ESP_ERR_INVALID_ARG: parameter error - * ESP_OK: success + * @return + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_OK Success * */ esp_err_t ledc_timer_rst(ledc_mode_t speed_mode, uint32_t timer_sel); @@ -297,13 +302,14 @@ esp_err_t ledc_timer_rst(ledc_mode_t speed_mode, uint32_t timer_sel); /** * @brief pause LEDC timer counter * - * @param[in] speed_mode : select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version + * @param speed_mode Select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version * - * @param[in] timer_sel : LEDC timer index(0-3), select from ledc_timer_t + * @param timer_sel LEDC timer index(0-3), select from ledc_timer_t * * - * @return ESP_ERR_INVALID_ARG: parameter error - * ESP_OK: success + * @return + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_OK Success * */ esp_err_t ledc_timer_pause(ledc_mode_t speed_mode, uint32_t timer_sel); @@ -311,13 +317,14 @@ esp_err_t ledc_timer_pause(ledc_mode_t speed_mode, uint32_t timer_sel); /** * @brief pause LEDC timer resume * - * @param[in] speed_mode : select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version + * @param speed_mode Select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version * - * @param[in] timer_sel : LEDC timer index(0-3), select from ledc_timer_t + * @param timer_sel LEDC timer index(0-3), select from ledc_timer_t * * - * @return ESP_ERR_INVALID_ARG: parameter error - * ESP_OK: success + * @return + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_OK Success * */ esp_err_t ledc_timer_resume(ledc_mode_t speed_mode, uint32_t timer_sel); @@ -325,15 +332,16 @@ esp_err_t ledc_timer_resume(ledc_mode_t speed_mode, uint32_t timer_sel); /** * @brief bind LEDC channel with the selected timer * - * @param[in] speed_mode : select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version + * @param speed_mode Select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version * - * @param[in] channel : LEDC channel index(0-7), select from ledc_channel_t + * @param channel LEDC channel index(0-7), select from ledc_channel_t * - * @param[in] timer_idx : LEDC timer index(0-3), select from ledc_timer_t + * @param timer_idx LEDC timer index(0-3), select from ledc_timer_t * * - * @return ESP_ERR_INVALID_ARG: parameter error - * ESP_OK: success + * @return + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_OK Success * */ esp_err_t ledc_bind_channel_timer(ledc_mode_t speed_mode, uint32_t channel, uint32_t timer_idx); @@ -342,44 +350,56 @@ esp_err_t ledc_bind_channel_timer(ledc_mode_t speed_mode, uint32_t channel, uint * * * ----------------EXAMPLE OF LEDC SETTING --------------------- - * //1. enable LEDC - * periph_module_enable(PERIPH_LEDC_MODULE); //enable LEDC module, or you can not set any register of it. + * @code{c} + * //1. enable LEDC + * //enable LEDC module, or you can not set any register of it. + * periph_module_enable(PERIPH_LEDC_MODULE); + * @endcode * - * //2. set LEDC timer - * ledc_timer_config_t timer_conf = { - * .bit_num = LEDC_TIMER_12_BIT, //set timer counter bit number - * .freq_hz = 1000, //set frequency of pwm, here, 1000Hz - * .speed_mode = LEDC_HIGH_SPEED_MODE //timer mode, - * .timer_num = LEDC_TIMER_0, //timer number - * }; - * ledc_timer_config(&timer_conf); //setup timer. + * @code{c} + * //2. set LEDC timer + * ledc_timer_config_t timer_conf = { + * .bit_num = LEDC_TIMER_12_BIT, //set timer counter bit number + * .freq_hz = 1000, //set frequency of pwm, here, 1000Hz + * .speed_mode = LEDC_HIGH_SPEED_MODE, //timer mode, + * .timer_num = LEDC_TIMER_0, //timer number + * }; + * ledc_timer_config(&timer_conf); //setup timer. + * @endcode * - * //3. set LEDC channel - * ledc_channel_config_t ledc_conf = { - * .channel = LEDC_CHANNEL_0; //set LEDC channel 0 - * .duty = 1000; //set the duty for initialization.(duty range is 0 ~ ((2**bit_num)-1) - * .gpio_num = 16; //GPIO number - * .intr_type = LEDC_INTR_FADE_END; //GPIO INTR TYPE, as an example, we enable fade_end interrupt here. - * .speed_mode = LEDC_HIGH_SPEED_MODE; //set LEDC mode, from ledc_mode_t - * .timer_sel = LEDC_TIMER_0; //set LEDC timer source, if different channel use one timer, the frequency and bit_num of these channels should be the same - * } - * ledc_channel_config(&ledc_conf); //setup the configuration + * @code{c} + * //3. set LEDC channel + * ledc_channel_config_t ledc_conf = { + * .channel = LEDC_CHANNEL_0; //set LEDC channel 0 + * .duty = 1000; //set the duty for initialization.(duty range is 0 ~ ((2**bit_num)-1) + * .gpio_num = 16; //GPIO number + * .intr_type = LEDC_INTR_FADE_END; //GPIO INTR TYPE, as an example, we enable fade_end interrupt here. + * .speed_mode = LEDC_HIGH_SPEED_MODE; //set LEDC mode, from ledc_mode_t + * .timer_sel = LEDC_TIMER_0; //set LEDC timer source, if different channel use one timer, the frequency and bit_num of these channels should be the same + * } + * ledc_channel_config(&ledc_conf); //setup the configuration * * ----------------EXAMPLE OF SETTING DUTY --- ----------------- - * uint32_t ledc_channel = LEDC_CHANNEL_0; //LEDC channel(0-73) - * uint32_t duty = 2000; //duty range is 0 ~ ((2**bit_num)-1) - * LEDC_set_duty(LEDC_HIGH_SPEED_MODE, ledc_channel, duty); //set speed mode, channel, and duty. - * ledc_update_duty(LEDC_HIGH_SPEED_MODE, ledc_channel); //after set duty, we need to call ledc_update_duty to update the settings. - * + * @code{c} + * uint32_t ledc_channel = LEDC_CHANNEL_0; //LEDC channel(0-73) + * uint32_t duty = 2000; //duty range is 0 ~ ((2**bit_num)-1) + * LEDC_set_duty(LEDC_HIGH_SPEED_MODE, ledc_channel, duty); //set speed mode, channel, and duty. + * ledc_update_duty(LEDC_HIGH_SPEED_MODE, ledc_channel); //after set duty, we need to call ledc_update_duty to update the settings. + * @endcode * * ----------------EXAMPLE OF LEDC INTERRUPT ------------------ - * //we have fade_end interrupt and counter overflow interrupt. we just give an example of fade_end interrupt here. - * ledc_isr_register(18, ledc_isr_handler, NULL); //hook the isr handler for LEDC interrupt - * //the first parameter is INUM, you can pick one form interrupt level 1/2 which is not used by the system. - * //NOTE1:user should arrange the INUMs that used, better not to use a same INUM for different interrupt source. - * //NOTE2:do not pick the INUM that already occupied by the system. - * //NOTE3:refer to soc.h to check which INUMs that can be used. + * @code{c} + * //we have fade_end interrupt and counter overflow interrupt. we just give an example of fade_end interrupt here. + * ledc_isr_register(18, ledc_isr_handler, NULL); //hook the isr handler for LEDC interrupt + * @endcode + * @note + * 1. the first parameter is INUM, you can pick one form interrupt level 1/2 which is not used by the system. + * 2. user should arrange the INUMs that used, better not to use a same INUM for different interrupt source. + * 3. do not pick the INUM that already occupied by the system. + * 4. refer to soc.h to check which INUMs that can be used. + * * ----------------EXAMPLE OF INTERRUPT HANDLER --------------- + * @code{c} * #include "esp_attr.h" * void IRAM_ATTR ledc_isr_handler(void* arg) //we should add 'IRAM_ATTR' attribution when we declare the isr function * { @@ -391,7 +411,7 @@ esp_err_t ledc_bind_channel_timer(ledc_mode_t speed_mode, uint32_t channel, uint * * LEDC.int_clr.val = intr_st; //clear LEDC interrupt status. * } - * + * @endcode * *--------------------------END OF EXAMPLE -------------------------- */ From d0fac3c39566d819022b569f2f2f26b3552c2166 Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Wed, 2 Nov 2016 10:26:02 +0800 Subject: [PATCH 149/149] Language tweaking --- docs/openocd.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/openocd.rst b/docs/openocd.rst index cf1d25e60..9e2aeff45 100644 --- a/docs/openocd.rst +++ b/docs/openocd.rst @@ -1,7 +1,7 @@ OpenOCD setup for ESP32 ----------------------- -The ESP31 and ESP32 have two powerful Xtensa cores, allowing for a great deal of variety of program architectures. The FreeRTOS +The ESP31 and ESP32 have two powerful Xtensa cores, allowing for a great variety of program architectures. The FreeRTOS OS that comes with ESP-IDF is capable of multi-core pre-emptive multithreading, allowing for an intuitive way of writing software. The downside of the ease of programming is that debugging without the right tools is harder: figuring out a bug that is caused