mirror of
https://github.com/espressif/esp-idf.git
synced 2025-08-10 04:43:33 +00:00
esp32: SEGGER SystemView Tracing Support
Implements support for system level traces compatible with SEGGER SystemView tool on top of ESP32 application tracing module. That kind of traces can help to analyse program's behaviour. SystemView can show timeline of tasks/ISRs execution, context switches, statistics related to the CPUs' load distribution etc. Also this commit adds useful feature to ESP32 application tracing module: - Trace data buffering is implemented to handle temporary peaks of events load
This commit is contained in:
@@ -104,45 +104,6 @@ config ESP32_CORE_DUMP_LOG_LEVEL
|
||||
help
|
||||
Config core dump module logging level (0-5).
|
||||
|
||||
choice ESP32_APPTRACE_DESTINATION
|
||||
prompt "AppTrace: destination"
|
||||
default ESP32_APPTRACE_DEST_NONE
|
||||
help
|
||||
Select destination for application trace: trace memory, uart or none (to disable).
|
||||
|
||||
config ESP32_APPTRACE_DEST_TRAX
|
||||
bool "Trace memory"
|
||||
select ESP32_APPTRACE_ENABLE
|
||||
config ESP32_APPTRACE_DEST_UART
|
||||
bool "UART"
|
||||
select ESP32_APPTRACE_ENABLE
|
||||
config ESP32_APPTRACE_DEST_NONE
|
||||
bool "None"
|
||||
endchoice
|
||||
|
||||
config ESP32_APPTRACE_ENABLE
|
||||
bool
|
||||
depends on !ESP32_TRAX
|
||||
select MEMMAP_TRACEMEM
|
||||
select MEMMAP_TRACEMEM_TWOBANKS
|
||||
default F
|
||||
help
|
||||
Enables/disable application tracing module.
|
||||
|
||||
config ESP32_APPTRACE_ONPANIC_HOST_FLUSH_TMO
|
||||
int "AppTrace: Timeout for flushing last trace data to host on panic"
|
||||
depends on ESP32_APPTRACE_ENABLE
|
||||
default 4294967295
|
||||
help
|
||||
Timeout for flushing last trace data to host in case of panic. In us.
|
||||
|
||||
config ESP32_APPTRACE_ONPANIC_HOST_FLUSH_TRAX_THRESH
|
||||
int "AppTrace: Threshold for flushing last trace data to host on panic"
|
||||
depends on ESP32_APPTRACE_DEST_TRAX
|
||||
default 50
|
||||
help
|
||||
Threshold for flushing last trace data to host on panic. In percents of TRAX memory block length.
|
||||
|
||||
# Not implemented and/or needs new silicon rev to work
|
||||
config MEMMAP_SPISRAM
|
||||
bool "Use external SPI SRAM chip as main memory"
|
||||
|
@@ -1,994 +0,0 @@
|
||||
// Copyright 2017 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.
|
||||
//
|
||||
// Hot It Works
|
||||
// ************
|
||||
|
||||
// 1. Components Overview
|
||||
// ======================
|
||||
|
||||
// Xtensa has useful feature: TRAX debug module. It allows recording program execution flow during run-time without disturbing CPU commands flow.
|
||||
// Exectution flow data are written to configurable Trace RAM block. Besides accessing Trace RAM itself TRAX module also allows to read/write
|
||||
// trace memory via its registers by means of JTAG, APB or ERI transactions.
|
||||
// ESP32 has two Xtensa cores with separate TRAX modules on them and provides two special memory regions to be used as trace memory.
|
||||
// ESP32 allows muxing access to trace memory blocks in such a way that while one block is accessed by CPUs another can be accessed via JTAG by host
|
||||
// via reading/writing TRAX registers. Block muxing is configurable at run-time and allows switching trace memory blocks between
|
||||
// accessors in round-robin fashion so they can read/write separate memory blocks without disturbing each other.
|
||||
// This moduile implements application tracing feature based on above mechanisms. This feature allows to transfer arbitrary user data to
|
||||
// host via JTAG with minimal impact on system performance. This module is implied to be used in the following tracing scheme.
|
||||
|
||||
// ------>------ ----- (host components) -----
|
||||
// | | | |
|
||||
// --------------- ----------------------- ----------------------- ---------------- ------ --------- -----------------
|
||||
// |apptrace user|-->|target tracing module|<--->|TRAX_MEM0 | TRAX_MEM1|---->|TRAX_DATA_REGS|<-->|JTAG|<--->|OpenOCD|-->|trace data file|
|
||||
// --------------- ----------------------- ----------------------- ---------------- ------ --------- -----------------
|
||||
// | | | |
|
||||
// | ------<------ ---------------- |
|
||||
// |<------------------------------------------->|TRAX_CTRL_REGS|<---->|
|
||||
// ----------------
|
||||
|
||||
// In general tracing happens in the following way. User aplication requests tracing module to send some data by calling esp_apptrace_buffer_get(),
|
||||
// moduile allocates necessary buffer in current input trace block. Then user fills received buffer with data and calls esp_apptrace_buffer_put().
|
||||
// When current input trace block is filled with app data it is exposed to host and the second block becomes input one and buffer filling restarts.
|
||||
// While target application fills one memory block host reads another block via JTAG.
|
||||
// To control buffer switching and for other communication purposes this implementation uses some TRAX registers. It is safe since HW TRAX tracing
|
||||
// can not be used along with application tracing feature so these registers are freely readable/writeable via JTAG from host and via ERI from ESP32 cores.
|
||||
// So this implementation's target CPU overhead is produced only by calls to allocate/manage buffers and data copying.
|
||||
// On host special OpenOCD command must be used to read trace data.
|
||||
|
||||
// 2.1.1.1 TRAX Registers layout
|
||||
// =============================
|
||||
|
||||
// This module uses two TRAX HW registers to communicate with host SW (OpenOCD).
|
||||
// - Control register uses TRAX_DELAYCNT as storage. Only lower 24 bits of TRAX_DELAYCNT are writable. Control register has the following bitfields:
|
||||
// | 31..XXXXXX..24 | 23 .(host_connect). 23| 22..(block_id)..15 | 14..(block_len)..0 |
|
||||
// 14..0 bits - actual length of user data in trace memory block. Target updates it every time it fills memory block and exposes it to host.
|
||||
// Host writes zero to this field when it finishes reading exposed block;
|
||||
// 22..15 bits - trace memory block transfer ID. Block counter. It can overflow. Updated by target, host should not modify it. Actually can be 1-2 bits;
|
||||
// 23 bit - 'host connected' flag. If zero then host is not connected and tracing module works in post-mortem mode, otherwise in streaming mode;
|
||||
// - Status register uses TRAX_TRIGGERPC as storage. If this register is not zero then currentlly CPU is changing TRAX registers and
|
||||
// this register holds address of the instruction which application will execute when it finishes with those registers modifications.
|
||||
// See 'Targets Connection' setion for details.
|
||||
|
||||
// 3. Modes of operation
|
||||
// =====================
|
||||
|
||||
// This module supports two modes of operation:
|
||||
// - Post-mortem mode. This is the default mode. In this mode application tracing module does not check whether host has read all the data from block
|
||||
// exposed to it and switches block in any case. The mode does not need host interaction for operation and so can be useful when only the latest
|
||||
// trace data are necessary, e.g. for analyzing crashes. On panic the latest data from current input block are exposed to host and host can read them.
|
||||
// There is menuconfig option CONFIG_ESP32_APPTRACE_ONPANIC_HOST_FLUSH_TRAX_THRESH which control the threshold for flushing data on panic.
|
||||
// - Streaming mode. Tracing module enters this mode when host connects to targets and sets respective bit in control register. In this mode tracing
|
||||
// module waits for specified time until host read all the data from exposed block.
|
||||
// On panic tracing module waits (timeout is configured via menuconfig via ESP32_APPTRACE_ONPANIC_HOST_FLUSH_TMO) for the host to read all data
|
||||
// from the previously exposed block.
|
||||
|
||||
// 4. Communication Protocol
|
||||
// =========================
|
||||
|
||||
// 4.1 Trace Memory Blocks
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
// Communication is controlled via special register. Host periodically polls control register on each core to find out if there are any data avalable.
|
||||
// When current input trace memory block is filled tracing module exposes block to host and updates block_len and block_id fields in control register.
|
||||
// Host reads new register value and according to it starts reading data from exposed block. Meanwhile target starts filling another trace block.
|
||||
// When host finishes reading the block it clears block_len field in control register indicating to target that it is ready to accept the next block.
|
||||
|
||||
// 4.2 User Data Chunks Level
|
||||
// --------------------------
|
||||
|
||||
// Since trace memory block is shared between user data chunks and data copying is performed on behalf of the API user (in its normal context) in
|
||||
// multithreading environment it can happen that task/ISR which copies data is preempted by another high prio task/ISR. So it is possible situation
|
||||
// that task/ISR will fail to complete filling its data chunk before the whole trace block is exposed to the host. To handle such conditions tracing
|
||||
// module prepends all user data chunks with 4 bytes header which contains allocated buffer size and actual data length within it. OpenOCD command
|
||||
// which reads application traces will report error when it will read incompleted user data block.
|
||||
|
||||
// 4.3 Targets Connection/Disconnection
|
||||
// ------------------------------------
|
||||
|
||||
// When host is going to start tracing in streaming mode it needs to put both ESP32 cores into initial state when 'host connected' bit is set
|
||||
// on both cores. To accomplish this host halts both cores and sets this bit in TRAX registers. But target code can be halted in state when it has read control
|
||||
// register but has not updated its value. To handle such situations target code indicates to the host that it is updating control register by writing
|
||||
// non-zero value to status register. Actually it writes address of the instruction which it will execute when it finishes with
|
||||
// the registers update. When target is halted during control register update host sets breakpoint at the address from status register and resumes CPU.
|
||||
// After target code finishes with register update it is halted on breakpoint, host detects it and safely sets 'host connected' bit. When both cores
|
||||
// are set up they are resumed. Tracing starts without further intrusion into CPUs work.
|
||||
// When host is going to stop tracing in streaming mode it needs to disconnect targets. Disconnection process is done using the same algorithm
|
||||
// as for connecting, but 'host connected' bits are cleared on ESP32 cores.
|
||||
|
||||
// 5. Module Access Synchronization
|
||||
// ================================
|
||||
|
||||
// Access to internal module's data is synchronized with custom mutex. Mutex is a wrapper for portMUX_TYPE and uses almost the same sync mechanism as in
|
||||
// vPortCPUAcquireMutex/vPortCPUReleaseMutex. The mechanism uses S32C1I Xtensa instruction to implement exclusive access to module's data from tasks and
|
||||
// ISRs running on both cores. Also custom mutex allows specifying timeout for locking operation. Locking routine checks underlaying mutex in cycle until
|
||||
// it gets its ownership or timeout expires. The differences of application tracing module's mutex implementation from vPortCPUAcquireMutex/vPortCPUReleaseMutex are:
|
||||
// - Support for timeouts.
|
||||
// - Local IRQs for CPU which owns the mutex are disabled till the call to unlocking routine. This is made to avoid possible task's prio inversion.
|
||||
// When low prio task takes mutex and enables local IRQs gets preempted by high prio task which in its turn can try to acquire mutex using infinite timeout.
|
||||
// So no local task switch occurs when mutex is locked. But this does not apply to tasks on another CPU.
|
||||
// WARNING: Priority inversion can happen when low prio task works on one CPU and medium and high prio tasks work on another.
|
||||
// There are some differences how mutex behaves when it is used from task and ISR context when timeout is non-zero:
|
||||
// - In task context when mutex can not be locked portYIELD() is called before check for timeout condition to alow othet tasks work on the same CPU.
|
||||
// - In ISR context when mutex can not be locked nothing is done before expired time check.
|
||||
// WARNING: Care must be taken when selecting timeout values for trace calls from ISRs. Tracing module does not care about watchdogs when waiting on internal locks
|
||||
// and when waiting for host to complete previous block reading, so if wating timeout value exceedes watchdog's one it can lead to system reboot.
|
||||
|
||||
// 6. Timeouts
|
||||
// ------------
|
||||
|
||||
// Timeout mechanism is based on xthal_get_ccount() routine and supports timeout values in micorseconds.
|
||||
// There are two situations when task/ISR can be delayed by tracing API call. Timeout mechanism takes into account both conditions:
|
||||
// - Trace data are locked by another task/ISR. When wating on trace data lock.
|
||||
// - Current TRAX memory input block is full when working in streaming mode (host is connected). When waiting for host to complete previous block reading.
|
||||
// When wating for any of above conditions xthal_get_ccount() is called periodically to calculate time elapsed from trace API routine entry. When elapsed
|
||||
// time exceeds specified timeout value operation is canceled and ESP_ERR_TIMEOUT code is returned.
|
||||
|
||||
// ALSO SEE example usage of application tracing module in 'components/log/README.rst'
|
||||
|
||||
#include <string.h>
|
||||
#include "soc/soc.h"
|
||||
#include "soc/dport_reg.h"
|
||||
#include "eri.h"
|
||||
#include "trax.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/portmacro.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/task.h"
|
||||
#include "soc/timer_group_struct.h"
|
||||
#include "soc/timer_group_reg.h"
|
||||
#include "esp_app_trace.h"
|
||||
|
||||
#if CONFIG_ESP32_APPTRACE_ENABLE
|
||||
#define ESP_APPTRACE_DEBUG_STATS_ENABLE 0
|
||||
#define ESP_APPTRACE_BUF_HISTORY_DEPTH (16*100)
|
||||
|
||||
#define ESP_APPTRACE_MAX_VPRINTF_ARGS 256
|
||||
|
||||
#define ESP_APPTRACE_PRINT_LOCK_NONE 0
|
||||
#define ESP_APPTRACE_PRINT_LOCK_SEM 1
|
||||
#define ESP_APPTRACE_PRINT_LOCK_MUX 2
|
||||
#define ESP_APPTRACE_PRINT_LOCK ESP_APPTRACE_PRINT_LOCK_NONE//ESP_APPTRACE_PRINT_LOCK_SEM
|
||||
|
||||
#define ESP_APPTRACE_USE_LOCK_SEM 0 // 1 - semaphore (now may be broken), 0 - portMUX_TYPE
|
||||
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
|
||||
#include "esp_log.h"
|
||||
const static char *TAG = "esp_apptrace";
|
||||
|
||||
#if ESP_APPTRACE_PRINT_LOCK != ESP_APPTRACE_PRINT_LOCK_NONE
|
||||
#define ESP_APPTRACE_LOG( format, ... ) \
|
||||
do { \
|
||||
esp_apptrace_log_lock(); \
|
||||
ets_printf(format, ##__VA_ARGS__); \
|
||||
esp_apptrace_log_unlock(); \
|
||||
} while(0)
|
||||
#else
|
||||
#define ESP_APPTRACE_LOG( format, ... ) \
|
||||
do { \
|
||||
ets_printf(format, ##__VA_ARGS__); \
|
||||
} while(0)
|
||||
#endif
|
||||
|
||||
#define ESP_APPTRACE_LOG_LEV( _L_, level, format, ... ) \
|
||||
do { \
|
||||
if (LOG_LOCAL_LEVEL >= level) { \
|
||||
ESP_APPTRACE_LOG(LOG_FORMAT(_L_, format), esp_log_early_timestamp(), TAG, ##__VA_ARGS__); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define ESP_APPTRACE_LOGE( format, ... ) ESP_APPTRACE_LOG_LEV(E, ESP_LOG_ERROR, format, ##__VA_ARGS__)
|
||||
#define ESP_APPTRACE_LOGW( format, ... ) ESP_APPTRACE_LOG_LEV(W, ESP_LOG_WARN, format, ##__VA_ARGS__)
|
||||
#define ESP_APPTRACE_LOGI( format, ... ) ESP_APPTRACE_LOG_LEV(I, ESP_LOG_INFO, format, ##__VA_ARGS__)
|
||||
#define ESP_APPTRACE_LOGD( format, ... ) ESP_APPTRACE_LOG_LEV(D, ESP_LOG_DEBUG, format, ##__VA_ARGS__)
|
||||
#define ESP_APPTRACE_LOGV( format, ... ) ESP_APPTRACE_LOG_LEV(V, ESP_LOG_VERBOSE, format, ##__VA_ARGS__)
|
||||
#define ESP_APPTRACE_LOGO( format, ... ) ESP_APPTRACE_LOG_LEV(E, ESP_LOG_NONE, format, ##__VA_ARGS__)
|
||||
|
||||
#define ESP_APPTRACE_CPUTICKS2US(_t_) ((_t_)/(XT_CLOCK_FREQ/1000000))
|
||||
|
||||
// TODO: move these (and same definitions in trax.c to dport_reg.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
|
||||
|
||||
// TRAX is disabled, so we use its registers for our own purposes
|
||||
// | 31..XXXXXX..24 | 23 .(host_connect). 23| 22..(block_id)..15 | 14..(block_len)..0 |
|
||||
#define ESP_APPTRACE_TRAX_CTRL_REG ERI_TRAX_DELAYCNT
|
||||
#define ESP_APPTRACE_TRAX_STAT_REG ERI_TRAX_TRIGGERPC
|
||||
|
||||
#define ESP_APPTRACE_TRAX_BLOCK_LEN_MSK 0x7FFFUL
|
||||
#define ESP_APPTRACE_TRAX_BLOCK_LEN(_l_) ((_l_) & ESP_APPTRACE_TRAX_BLOCK_LEN_MSK)
|
||||
#define ESP_APPTRACE_TRAX_BLOCK_LEN_GET(_v_) ((_v_) & ESP_APPTRACE_TRAX_BLOCK_LEN_MSK)
|
||||
#define ESP_APPTRACE_TRAX_BLOCK_ID_MSK 0xFFUL
|
||||
#define ESP_APPTRACE_TRAX_BLOCK_ID(_id_) (((_id_) & ESP_APPTRACE_TRAX_BLOCK_ID_MSK) << 15)
|
||||
#define ESP_APPTRACE_TRAX_BLOCK_ID_GET(_v_) (((_v_) >> 15) & ESP_APPTRACE_TRAX_BLOCK_ID_MSK)
|
||||
#define ESP_APPTRACE_TRAX_HOST_CONNECT (1 << 23)
|
||||
|
||||
static volatile uint8_t *s_trax_blocks[] = {
|
||||
(volatile uint8_t *) 0x3FFFC000,
|
||||
(volatile uint8_t *) 0x3FFF8000
|
||||
};
|
||||
|
||||
#define ESP_APPTRACE_TRAX_BLOCKS_NUM (sizeof(s_trax_blocks)/sizeof(s_trax_blocks[0]))
|
||||
|
||||
//#define ESP_APPTRACE_TRAX_BUFFER_SIZE (ESP_APPTRACE_TRAX_BLOCK_SIZE/4)
|
||||
|
||||
#define ESP_APPTRACE_TRAX_INBLOCK_START 0//(ESP_APPTRACE_TRAX_BLOCK_ID_MSK - 4)
|
||||
|
||||
|
||||
#define ESP_APPTRACE_TRAX_INBLOCK_MARKER_PTR_GET() (&s_trace_buf.trax.state.markers[s_trace_buf.trax.state.in_block % 2])
|
||||
#define ESP_APPTRACE_TRAX_INBLOCK_GET() (&s_trace_buf.trax.blocks[s_trace_buf.trax.state.in_block % 2])
|
||||
|
||||
#if ESP_APPTRACE_DEBUG_STATS_ENABLE == 1
|
||||
/** keeps info about apptrace API (write/get buffer) caller and internal module's data related to that call
|
||||
* NOTE: used for module debug purposes, currently this functionality is partially broken,
|
||||
* but can be useful in future
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t hnd; // task/ISR handle
|
||||
uint32_t ts; // timestamp
|
||||
uint32_t stamp; // test (user) trace buffer stamp
|
||||
uint32_t in_block; // TRAX input block ID
|
||||
uint32_t eri_len[2]; // contents of ERI control register upon entry to / exit from API routine
|
||||
uint32_t wr_err; // number of trace write errors
|
||||
} esp_trace_buffer_wr_hitem_t;
|
||||
|
||||
/** apptrace API calls history. History is organized as ring buffer*/
|
||||
typedef struct {
|
||||
uint32_t hist_rd; // the first history entry index
|
||||
uint32_t hist_wr; // the last history entry index
|
||||
esp_trace_buffer_wr_hitem_t hist[ESP_APPTRACE_BUF_HISTORY_DEPTH]; // history data
|
||||
} esp_trace_buffer_wr_stats_t;
|
||||
|
||||
/** trace module stats */
|
||||
typedef struct {
|
||||
esp_trace_buffer_wr_stats_t wr;
|
||||
} esp_trace_buffer_stats_t;
|
||||
#endif
|
||||
|
||||
/** Trace data header. Every user data chunk is prepended with this header.
|
||||
* User allocates block with esp_apptrace_buffer_get and then fills it with data,
|
||||
* in multithreading environment it can happen that tasks gets buffer and then gets interrupted,
|
||||
* so it is possible that user data are incomplete when TRAX memory block is exposed to the host.
|
||||
* In this case host SW will see that wr_sz < block_sz and will report error.
|
||||
*/
|
||||
typedef struct {
|
||||
uint16_t block_sz; // size of allocated block for user data
|
||||
uint16_t wr_sz; // size of actually written data
|
||||
} esp_tracedata_hdr_t;
|
||||
|
||||
/** TRAX HW transport state */
|
||||
typedef struct {
|
||||
uint32_t in_block; // input block ID
|
||||
uint32_t markers[ESP_APPTRACE_TRAX_BLOCKS_NUM]; // block filling level markers
|
||||
#if ESP_APPTRACE_DEBUG_STATS_ENABLE == 1
|
||||
esp_trace_buffer_stats_t stats; // stats
|
||||
#endif
|
||||
} esp_apptrace_trax_state_t;
|
||||
|
||||
/** memory block parameters */
|
||||
typedef struct {
|
||||
uint8_t *start; // start address
|
||||
uint32_t sz; // size
|
||||
} esp_apptrace_mem_block_t;
|
||||
|
||||
/** TRAX HW transport data */
|
||||
typedef struct {
|
||||
volatile esp_apptrace_trax_state_t state; // state
|
||||
esp_apptrace_mem_block_t blocks[ESP_APPTRACE_TRAX_BLOCKS_NUM]; // memory blocks
|
||||
} esp_apptrace_trax_data_t;
|
||||
|
||||
/** tracing module synchronization lock */
|
||||
typedef struct {
|
||||
volatile unsigned int irq_stat; // local (on 1 CPU) IRQ state
|
||||
portMUX_TYPE portmux; // mux for synchronization
|
||||
} esp_apptrace_lock_t;
|
||||
|
||||
#define ESP_APPTRACE_MUX_GET(_m_) (&(_m_)->portmux)
|
||||
|
||||
/** tracing module internal data */
|
||||
typedef struct {
|
||||
#if ESP_APPTRACE_USE_LOCK_SEM == 1
|
||||
SemaphoreHandle_t lock;
|
||||
#else
|
||||
esp_apptrace_lock_t lock; // sync lock
|
||||
#endif
|
||||
uint8_t inited; // module initialization state flag
|
||||
esp_apptrace_trax_data_t trax; // TRAX HW transport data
|
||||
} esp_apptrace_buffer_t;
|
||||
|
||||
/** waiting timeout data */
|
||||
typedef struct {
|
||||
uint32_t start; // waiting start (in ticks)
|
||||
uint32_t tmo; // timeout (in us)
|
||||
} esp_apptrace_tmo_t;
|
||||
|
||||
static esp_apptrace_buffer_t s_trace_buf;
|
||||
|
||||
#if ESP_APPTRACE_PRINT_LOCK == ESP_APPTRACE_PRINT_LOCK_SEM
|
||||
static SemaphoreHandle_t s_log_lock;
|
||||
#elif ESP_APPTRACE_PRINT_LOCK == ESP_APPTRACE_PRINT_LOCK_MUX
|
||||
static esp_apptrace_lock_t s_log_lock;
|
||||
#endif
|
||||
|
||||
static inline void esp_apptrace_tmo_init(esp_apptrace_tmo_t *tmo, uint32_t user_tmo)
|
||||
{
|
||||
tmo->start = xthal_get_ccount();
|
||||
tmo->tmo = user_tmo;
|
||||
}
|
||||
|
||||
static esp_err_t esp_apptrace_tmo_check(esp_apptrace_tmo_t *tmo)
|
||||
{
|
||||
unsigned cur, elapsed;
|
||||
|
||||
if (tmo->tmo != ESP_APPTRACE_TMO_INFINITE) {
|
||||
cur = xthal_get_ccount();
|
||||
if (tmo->start <= cur) {
|
||||
elapsed = cur - tmo->start;
|
||||
} else {
|
||||
elapsed = 0xFFFFFFFF - tmo->start + cur;
|
||||
}
|
||||
if (ESP_APPTRACE_CPUTICKS2US(elapsed) >= tmo->tmo) {
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
#if ESP_APPTRACE_PRINT_LOCK == ESP_APPTRACE_PRINT_LOCK_MUX || ESP_APPTRACE_USE_LOCK_SEM == 0
|
||||
static inline void esp_apptrace_mux_init(esp_apptrace_lock_t *mux)
|
||||
{
|
||||
ESP_APPTRACE_MUX_GET(mux)->mux = portMUX_FREE_VAL;
|
||||
mux->irq_stat = 0;
|
||||
}
|
||||
|
||||
static esp_err_t esp_apptrace_lock_take(esp_apptrace_lock_t *mux, uint32_t tmo)
|
||||
{
|
||||
uint32_t res = ~portMUX_FREE_VAL;
|
||||
esp_apptrace_tmo_t sleeping_tmo;
|
||||
|
||||
esp_apptrace_tmo_init(&sleeping_tmo, tmo);
|
||||
while (1) {
|
||||
res = (xPortGetCoreID() << portMUX_VAL_SHIFT) | portMUX_MAGIC_VAL;
|
||||
// first disable IRQs on this CPU, this will prevent current task from been
|
||||
// preempted by higher prio tasks, otherwise deadlock can happen:
|
||||
// when lower prio task took mux and then preempted by higher prio one which also tries to
|
||||
// get mux with INFINITE timeout
|
||||
unsigned int irq_stat = portENTER_CRITICAL_NESTED();
|
||||
// Now try to lock mux
|
||||
uxPortCompareSet(&ESP_APPTRACE_MUX_GET(mux)->mux, portMUX_FREE_VAL, &res);
|
||||
if (res == portMUX_FREE_VAL) {
|
||||
// do not enable IRQs, we will held them disabled until mux is unlocked
|
||||
// we do not need to flush cache region for mux->irq_stat because it is used
|
||||
// to hold and restore IRQ state only for CPU which took mux, other CPUs will not use this value
|
||||
mux->irq_stat = irq_stat;
|
||||
break;
|
||||
}
|
||||
// if mux is locked by other task/ISR enable IRQs and let other guys work
|
||||
portEXIT_CRITICAL_NESTED(irq_stat);
|
||||
|
||||
if (!xPortInIsrContext()) {
|
||||
portYIELD();
|
||||
}
|
||||
|
||||
int err = esp_apptrace_tmo_check(&sleeping_tmo);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_apptrace_mux_give(esp_apptrace_lock_t *mux)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
uint32_t res = 0;
|
||||
unsigned int irq_stat;
|
||||
|
||||
res = portMUX_FREE_VAL;
|
||||
|
||||
// first of all save a copy of IRQ status for this locker because uxPortCompareSet will unlock mux and tasks/ISRs
|
||||
// from other core can overwrite mux->irq_stat
|
||||
irq_stat = mux->irq_stat;
|
||||
uxPortCompareSet(&ESP_APPTRACE_MUX_GET(mux)->mux, (xPortGetCoreID() << portMUX_VAL_SHIFT) | portMUX_MAGIC_VAL, &res);
|
||||
// enable local interrupts
|
||||
portEXIT_CRITICAL_NESTED(irq_stat);
|
||||
|
||||
if ( ((res & portMUX_VAL_MASK) >> portMUX_VAL_SHIFT) == xPortGetCoreID() ) {
|
||||
// nothing to do
|
||||
} else if ( res == portMUX_FREE_VAL ) {
|
||||
ret = ESP_FAIL; // should never get here
|
||||
} else {
|
||||
ret = ESP_FAIL; // should never get here
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline esp_err_t esp_apptrace_log_init()
|
||||
{
|
||||
#if ESP_APPTRACE_PRINT_LOCK == ESP_APPTRACE_PRINT_LOCK_SEM
|
||||
s_log_lock = xSemaphoreCreateBinary();
|
||||
if (!s_log_lock) {
|
||||
ets_printf("%s: Failed to create print lock sem!", TAG);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
xSemaphoreGive(s_log_lock);
|
||||
#elif ESP_APPTRACE_PRINT_LOCK == ESP_APPTRACE_PRINT_LOCK_MUX
|
||||
esp_apptrace_mux_init(&s_log_lock);
|
||||
#endif
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static inline void esp_apptrace_log_cleanup()
|
||||
{
|
||||
#if ESP_APPTRACE_PRINT_LOCK == ESP_APPTRACE_PRINT_LOCK_SEM
|
||||
vSemaphoreDelete(s_log_lock);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline int esp_apptrace_log_lock()
|
||||
{
|
||||
#if ESP_APPTRACE_PRINT_LOCK == ESP_APPTRACE_PRINT_LOCK_SEM
|
||||
BaseType_t ret;
|
||||
if (xPortInIsrContext()) {
|
||||
ret = xSemaphoreTakeFromISR(s_print_lock, NULL);
|
||||
} else {
|
||||
ret = xSemaphoreTake(s_print_lock, portMAX_DELAY);
|
||||
}
|
||||
return ret;
|
||||
#elif ESP_APPTRACE_PRINT_LOCK == ESP_APPTRACE_PRINT_LOCK_MUX
|
||||
int ret = esp_apptrace_lock_take(&s_log_lock, ESP_APPTRACE_TMO_INFINITE);
|
||||
return ret;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void esp_apptrace_log_unlock()
|
||||
{
|
||||
#if ESP_APPTRACE_PRINT_LOCK == ESP_APPTRACE_PRINT_LOCK_SEM
|
||||
if (xPortInIsrContext()) {
|
||||
xSemaphoreGiveFromISR(s_log_lock, NULL);
|
||||
} else {
|
||||
xSemaphoreGive(s_log_lock);
|
||||
}
|
||||
#elif ESP_APPTRACE_PRINT_LOCK == ESP_APPTRACE_PRINT_LOCK_MUX
|
||||
esp_apptrace_mux_give(&s_log_lock);
|
||||
#endif
|
||||
}
|
||||
|
||||
esp_err_t esp_apptrace_lock_init()
|
||||
{
|
||||
#if ESP_APPTRACE_USE_LOCK_SEM == 1
|
||||
s_trace_buf.lock = xSemaphoreCreateBinary();
|
||||
if (!s_trace_buf.lock) {
|
||||
ESP_APPTRACE_LOGE("Failed to create lock!");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
xSemaphoreGive(s_trace_buf.lock);
|
||||
#else
|
||||
esp_apptrace_mux_init(&s_trace_buf.lock);
|
||||
#endif
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_apptrace_lock_cleanup()
|
||||
{
|
||||
#if ESP_APPTRACE_USE_LOCK_SEM == 1
|
||||
vSemaphoreDelete(s_trace_buf.lock);
|
||||
#endif
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_apptrace_lock(uint32_t *tmo)
|
||||
{
|
||||
unsigned cur, elapsed, start = xthal_get_ccount();
|
||||
|
||||
#if ESP_APPTRACE_USE_LOCK_SEM == 1
|
||||
BaseType_t ret;
|
||||
if (xPortInIsrContext()) {
|
||||
ret = xSemaphoreTakeFromISR(s_trace_buf.lock, NULL);
|
||||
} else {
|
||||
ret = xSemaphoreTake(s_trace_buf.lock, portTICK_PERIOD_MS * (*tmo) / 1000);
|
||||
}
|
||||
if (ret != pdTRUE) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
#else
|
||||
esp_err_t ret = esp_apptrace_lock_take(&s_trace_buf.lock, *tmo);
|
||||
if (ret != ESP_OK) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
#endif
|
||||
// decrease tmo by actual waiting time
|
||||
cur = xthal_get_ccount();
|
||||
if (start <= cur) {
|
||||
elapsed = cur - start;
|
||||
} else {
|
||||
elapsed = ULONG_MAX - start + cur;
|
||||
}
|
||||
if (ESP_APPTRACE_CPUTICKS2US(elapsed) > *tmo) {
|
||||
*tmo = 0;
|
||||
} else {
|
||||
*tmo -= ESP_APPTRACE_CPUTICKS2US(elapsed);
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_apptrace_unlock()
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
#if ESP_APPTRACE_USE_LOCK_SEM == 1
|
||||
if (xPortInIsrContext()) {
|
||||
xSemaphoreGiveFromISR(s_trace_buf.lock, NULL);
|
||||
} else {
|
||||
xSemaphoreGive(s_trace_buf.lock);
|
||||
}
|
||||
#else
|
||||
ret = esp_apptrace_mux_give(&s_trace_buf.lock);
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if CONFIG_ESP32_APPTRACE_DEST_TRAX
|
||||
static void esp_apptrace_trax_init()
|
||||
{
|
||||
// Stop trace, if any (on the current CPU)
|
||||
eri_write(ERI_TRAX_TRAXCTRL, TRAXCTRL_TRSTP);
|
||||
eri_write(ERI_TRAX_TRAXCTRL, TRAXCTRL_TMEN);
|
||||
eri_write(ESP_APPTRACE_TRAX_CTRL_REG, ESP_APPTRACE_TRAX_BLOCK_ID(ESP_APPTRACE_TRAX_INBLOCK_START));
|
||||
eri_write(ESP_APPTRACE_TRAX_STAT_REG, 0);
|
||||
|
||||
ESP_APPTRACE_LOGI("Initialized TRAX on CPU%d", xPortGetCoreID());
|
||||
}
|
||||
|
||||
// assumed to be protected by caller from multi-core/thread access
|
||||
static esp_err_t esp_apptrace_trax_block_switch()
|
||||
{
|
||||
int prev_block_num = s_trace_buf.trax.state.in_block % 2;
|
||||
int new_block_num = prev_block_num ? (0) : (1);
|
||||
int res = ESP_OK;
|
||||
extern uint32_t __esp_apptrace_trax_eri_updated;
|
||||
|
||||
// indicate to host that we are about to update.
|
||||
// this is used only to place CPU into streaming mode at tracing startup
|
||||
// before starting streaming host can halt us after we read ESP_APPTRACE_TRAX_CTRL_REG and before we updated it
|
||||
// HACK: in this case host will set breakpoint just after ESP_APPTRACE_TRAX_CTRL_REG update,
|
||||
// here we set address to set bp at
|
||||
// enter ERI update critical section
|
||||
eri_write(ESP_APPTRACE_TRAX_STAT_REG, (uint32_t)&__esp_apptrace_trax_eri_updated);
|
||||
|
||||
uint32_t ctrl_reg = eri_read(ESP_APPTRACE_TRAX_CTRL_REG);
|
||||
#if ESP_APPTRACE_DEBUG_STATS_ENABLE == 1
|
||||
if (s_trace_buf.state.stats.wr.hist_wr < ESP_APPTRACE_BUF_HISTORY_DEPTH) {
|
||||
esp_trace_buffer_wr_hitem_t *hi = (esp_trace_buffer_wr_hitem_t *)&s_trace_buf.state.stats.wr.hist[s_trace_buf.state.stats.wr.hist_wr - 1];
|
||||
hi->eri_len[1] = ctrl_reg;
|
||||
}
|
||||
#endif
|
||||
uint32_t host_connected = ESP_APPTRACE_TRAX_HOST_CONNECT & ctrl_reg;
|
||||
if (host_connected) {
|
||||
uint32_t acked_block = ESP_APPTRACE_TRAX_BLOCK_ID_GET(ctrl_reg);
|
||||
uint32_t host_to_read = ESP_APPTRACE_TRAX_BLOCK_LEN_GET(ctrl_reg);
|
||||
if (host_to_read != 0 || acked_block != (s_trace_buf.trax.state.in_block & ESP_APPTRACE_TRAX_BLOCK_ID_MSK)) {
|
||||
// ESP_APPTRACE_LOGE("HC[%d]: Can not switch %x %d %x %x/%lx", xPortGetCoreID(), ctrl_reg, host_to_read, acked_block,
|
||||
// s_trace_buf.trax.state.in_block & ESP_APPTRACE_TRAX_BLOCK_ID_MSK, s_trace_buf.trax.state.in_block);
|
||||
res = ESP_ERR_NO_MEM;
|
||||
goto _on_func_exit;
|
||||
}
|
||||
}
|
||||
s_trace_buf.trax.state.markers[new_block_num] = 0;
|
||||
// switch to new block
|
||||
s_trace_buf.trax.state.in_block++;
|
||||
|
||||
DPORT_WRITE_PERI_REG(DPORT_TRACEMEM_MUX_MODE_REG, new_block_num ? TRACEMEM_MUX_BLK0_ONLY : TRACEMEM_MUX_BLK1_ONLY);
|
||||
eri_write(ESP_APPTRACE_TRAX_CTRL_REG, ESP_APPTRACE_TRAX_BLOCK_ID(s_trace_buf.trax.state.in_block) |
|
||||
host_connected | ESP_APPTRACE_TRAX_BLOCK_LEN(s_trace_buf.trax.state.markers[prev_block_num]));
|
||||
|
||||
_on_func_exit:
|
||||
// exit ERI update critical section
|
||||
eri_write(ESP_APPTRACE_TRAX_STAT_REG, 0x0);
|
||||
asm volatile (
|
||||
" .global __esp_apptrace_trax_eri_updated\n"
|
||||
"__esp_apptrace_trax_eri_updated:\n"); // host will set bp here to resolve collision at streaming start
|
||||
return res;
|
||||
}
|
||||
|
||||
static esp_err_t esp_apptrace_trax_block_switch_waitus(uint32_t tmo)
|
||||
{
|
||||
int res;
|
||||
esp_apptrace_tmo_t sleeping_tmo;
|
||||
|
||||
esp_apptrace_tmo_init(&sleeping_tmo, tmo);
|
||||
|
||||
while ((res = esp_apptrace_trax_block_switch()) != ESP_OK) {
|
||||
res = esp_apptrace_tmo_check(&sleeping_tmo);
|
||||
if (res != ESP_OK) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static uint8_t *esp_apptrace_trax_get_buffer(size_t size, uint32_t *tmo)
|
||||
{
|
||||
uint8_t *buf_ptr = NULL;
|
||||
volatile uint32_t *cur_block_marker;
|
||||
esp_apptrace_mem_block_t *cur_block;
|
||||
|
||||
int res = esp_apptrace_lock(tmo);
|
||||
if (res != ESP_OK) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#if ESP_APPTRACE_DEBUG_STATS_ENABLE == 1
|
||||
esp_trace_buffer_wr_hitem_t *hi = NULL;
|
||||
if (s_trace_buf.state.stats.wr.hist_wr < ESP_APPTRACE_BUF_HISTORY_DEPTH) {
|
||||
hi = (esp_trace_buffer_wr_hitem_t *)&s_trace_buf.state.stats.wr.hist[s_trace_buf.state.stats.wr.hist_wr++];
|
||||
hi->hnd = *(uint32_t *)(buf + 0);
|
||||
hi->ts = *(uint32_t *)(buf + sizeof(uint32_t));
|
||||
hi->stamp = *(buf + 2 * sizeof(uint32_t));
|
||||
hi->in_block = s_trace_buf.state.in_block;
|
||||
hi->wr_err = 0;
|
||||
hi->eri_len[0] = eri_read(ESP_APPTRACE_TRAX_CTRL_REG);
|
||||
if (s_trace_buf.state.stats.wr.hist_wr == ESP_APPTRACE_BUF_HISTORY_DEPTH) {
|
||||
s_trace_buf.state.stats.wr.hist_wr = 0;
|
||||
}
|
||||
if (s_trace_buf.state.stats.wr.hist_wr == s_trace_buf.state.stats.wr.hist_rd) {
|
||||
s_trace_buf.state.stats.wr.hist_rd++;
|
||||
if (s_trace_buf.state.stats.wr.hist_rd == ESP_APPTRACE_BUF_HISTORY_DEPTH) {
|
||||
s_trace_buf.state.stats.wr.hist_rd = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
cur_block_marker = ESP_APPTRACE_TRAX_INBLOCK_MARKER_PTR_GET();
|
||||
cur_block = ESP_APPTRACE_TRAX_INBLOCK_GET();
|
||||
|
||||
if (*cur_block_marker + size + sizeof(esp_tracedata_hdr_t) >= cur_block->sz) {
|
||||
// flush data, we can not unlock apptrace until we have buffer for all user data
|
||||
// otherwise other tasks/ISRs can get control and write their data between chunks of this data
|
||||
res = esp_apptrace_trax_block_switch_waitus(/*size + sizeof(esp_tracedata_hdr_t),*/*tmo);
|
||||
if (res != ESP_OK) {
|
||||
if (esp_apptrace_unlock() != ESP_OK) {
|
||||
ESP_APPTRACE_LOGE("Failed to unlock apptrace data!");
|
||||
// there is a bug, should never get here
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
// we switched to new block, update TRAX block pointers
|
||||
cur_block_marker = ESP_APPTRACE_TRAX_INBLOCK_MARKER_PTR_GET();
|
||||
cur_block = ESP_APPTRACE_TRAX_INBLOCK_GET();
|
||||
}
|
||||
|
||||
buf_ptr = cur_block->start + *cur_block_marker;
|
||||
((esp_tracedata_hdr_t *)buf_ptr)->block_sz = size;
|
||||
((esp_tracedata_hdr_t *)buf_ptr)->wr_sz = 0;
|
||||
|
||||
*cur_block_marker += size + sizeof(esp_tracedata_hdr_t);
|
||||
|
||||
// now we can safely unlock apptrace to allow other tasks/ISRs to get other buffers and write their data
|
||||
if (esp_apptrace_unlock() != ESP_OK) {
|
||||
ESP_APPTRACE_LOGE("Failed to unlock apptrace data!");
|
||||
// there is a bug, should never get here
|
||||
}
|
||||
|
||||
return buf_ptr + sizeof(esp_tracedata_hdr_t);
|
||||
}
|
||||
|
||||
static esp_err_t esp_apptrace_trax_put_buffer(uint8_t *ptr, uint32_t *tmo)
|
||||
{
|
||||
int res = ESP_OK;
|
||||
esp_tracedata_hdr_t *hdr = (esp_tracedata_hdr_t *)(ptr - sizeof(esp_tracedata_hdr_t));
|
||||
|
||||
// update written size
|
||||
hdr->wr_sz = hdr->block_sz;
|
||||
|
||||
// TODO: mark block as busy in order not to re-use it for other tracing calls until it is completely written
|
||||
// TODO: avoid potential situation when all memory is consumed by low prio tasks which can not complete writing due to
|
||||
// higher prio tasks and the latter can not allocate buffers at all
|
||||
// this is abnormal situation can be detected on host which will receive only uncompleted buffers
|
||||
// workaround: use own memcpy which will kick-off dead tracing calls
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static esp_err_t esp_apptrace_trax_flush(uint32_t min_sz, uint32_t tmo)
|
||||
{
|
||||
volatile uint32_t *in_block_marker;
|
||||
int res = ESP_OK;
|
||||
|
||||
in_block_marker = ESP_APPTRACE_TRAX_INBLOCK_MARKER_PTR_GET();
|
||||
if (*in_block_marker > min_sz) {
|
||||
ESP_APPTRACE_LOGD("Wait until block switch for %u us", tmo);
|
||||
res = esp_apptrace_trax_block_switch_waitus(/*0 query any size,*/tmo);
|
||||
if (res != ESP_OK) {
|
||||
ESP_APPTRACE_LOGE("Failed to switch to another block");
|
||||
return res;
|
||||
}
|
||||
ESP_APPTRACE_LOGD("Flushed last block %u bytes", *in_block_marker);
|
||||
*in_block_marker = 0;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static esp_err_t esp_apptrace_trax_dest_init()
|
||||
{
|
||||
for (int i = 0; i < ESP_APPTRACE_TRAX_BLOCKS_NUM; i++) {
|
||||
s_trace_buf.trax.blocks[i].start = (uint8_t *)s_trax_blocks[i];
|
||||
s_trace_buf.trax.blocks[i].sz = ESP_APPTRACE_TRAX_BLOCK_SIZE;
|
||||
s_trace_buf.trax.state.markers[i] = 0;
|
||||
}
|
||||
s_trace_buf.trax.state.in_block = ESP_APPTRACE_TRAX_INBLOCK_START;
|
||||
|
||||
DPORT_WRITE_PERI_REG(DPORT_PRO_TRACEMEM_ENA_REG, DPORT_PRO_TRACEMEM_ENA_M);
|
||||
#if CONFIG_FREERTOS_UNICORE == 0
|
||||
DPORT_WRITE_PERI_REG(DPORT_APP_TRACEMEM_ENA_REG, DPORT_APP_TRACEMEM_ENA_M);
|
||||
#endif
|
||||
// Expose block 1 to host, block 0 is current trace input buffer
|
||||
DPORT_WRITE_PERI_REG(DPORT_TRACEMEM_MUX_MODE_REG, TRACEMEM_MUX_BLK1_ONLY);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
esp_err_t esp_apptrace_init()
|
||||
{
|
||||
int res;
|
||||
|
||||
if (!s_trace_buf.inited) {
|
||||
res = esp_apptrace_log_init();
|
||||
if (res != ESP_OK) {
|
||||
ets_printf("%s: Failed to init log lock (%d)!", TAG, res);
|
||||
return res;
|
||||
}
|
||||
//memset(&s_trace_buf, 0, sizeof(s_trace_buf));
|
||||
res = esp_apptrace_lock_init(&s_trace_buf.lock);
|
||||
if (res != ESP_OK) {
|
||||
ESP_APPTRACE_LOGE("Failed to init log lock (%d)!", res);
|
||||
esp_apptrace_log_cleanup();
|
||||
return res;
|
||||
}
|
||||
#if CONFIG_ESP32_APPTRACE_DEST_TRAX
|
||||
res = esp_apptrace_trax_dest_init();
|
||||
if (res != ESP_OK) {
|
||||
ESP_APPTRACE_LOGE("Failed to init TRAX dest data (%d)!", res);
|
||||
esp_apptrace_lock_cleanup();
|
||||
esp_apptrace_log_cleanup();
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if CONFIG_ESP32_APPTRACE_DEST_TRAX
|
||||
// init TRAX on this CPU
|
||||
esp_apptrace_trax_init();
|
||||
#endif
|
||||
|
||||
s_trace_buf.inited |= 1 << xPortGetCoreID(); // global and this CPU-specific data are inited
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_apptrace_write(esp_apptrace_dest_t dest, void *data, size_t size, uint32_t user_tmo)
|
||||
{
|
||||
uint8_t *ptr = NULL;
|
||||
uint32_t tmo = user_tmo;
|
||||
//TODO: use ptr to HW transport iface struct
|
||||
uint8_t *(*apptrace_get_buffer)(size_t, uint32_t *);
|
||||
esp_err_t (*apptrace_put_buffer)(uint8_t *, uint32_t *);
|
||||
|
||||
if (dest == ESP_APPTRACE_DEST_TRAX) {
|
||||
#if CONFIG_ESP32_APPTRACE_DEST_TRAX
|
||||
apptrace_get_buffer = esp_apptrace_trax_get_buffer;
|
||||
apptrace_put_buffer = esp_apptrace_trax_put_buffer;
|
||||
#else
|
||||
ESP_APPTRACE_LOGE("Application tracing via TRAX is disabled in menuconfig!");
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
#endif
|
||||
} else {
|
||||
ESP_APPTRACE_LOGE("Trace destinations other then TRAX are not supported yet!");
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
ptr = apptrace_get_buffer(size, &tmo);
|
||||
if (ptr == NULL) {
|
||||
//ESP_APPTRACE_LOGE("Failed to get buffer!");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
// actually can be suspended here by higher prio tasks/ISRs
|
||||
//TODO: use own memcpy with dead trace calls kick-off algo, and tmo expiration check
|
||||
memcpy(ptr, data, size);
|
||||
|
||||
// now indicate that this buffer is ready to be sent off to host
|
||||
return apptrace_put_buffer(ptr, &tmo);
|
||||
}
|
||||
|
||||
int esp_apptrace_vprintf_to(esp_apptrace_dest_t dest, uint32_t user_tmo, const char *fmt, va_list ap)
|
||||
{
|
||||
uint16_t nargs = 0;
|
||||
uint8_t *pout, *p = (uint8_t *)fmt;
|
||||
uint32_t tmo = user_tmo;
|
||||
//TODO: use ptr to HW transport iface struct
|
||||
uint8_t *(*apptrace_get_buffer)(size_t, uint32_t *);
|
||||
esp_err_t (*apptrace_put_buffer)(uint8_t *, uint32_t *);
|
||||
|
||||
if (dest == ESP_APPTRACE_DEST_TRAX) {
|
||||
#if CONFIG_ESP32_APPTRACE_DEST_TRAX
|
||||
apptrace_get_buffer = esp_apptrace_trax_get_buffer;
|
||||
apptrace_put_buffer = esp_apptrace_trax_put_buffer;
|
||||
#else
|
||||
ESP_APPTRACE_LOGE("Application tracing via TRAX is disabled in menuconfig!");
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
#endif
|
||||
} else {
|
||||
ESP_APPTRACE_LOGE("Trace destinations other then TRAX are not supported yet!");
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
// ESP_APPTRACE_LOGI("fmt %x", fmt);
|
||||
while ((p = (uint8_t *)strchr((char *)p, '%')) && nargs < ESP_APPTRACE_MAX_VPRINTF_ARGS) {
|
||||
p++;
|
||||
if (*p != '%' && *p != 0) {
|
||||
nargs++;
|
||||
}
|
||||
}
|
||||
// ESP_APPTRACE_LOGI("nargs = %d", nargs);
|
||||
if (p) {
|
||||
ESP_APPTRACE_LOGE("Failed to store all printf args!");
|
||||
}
|
||||
|
||||
pout = apptrace_get_buffer(1 + sizeof(char *) + nargs * sizeof(uint32_t), &tmo);
|
||||
if (pout == NULL) {
|
||||
ESP_APPTRACE_LOGE("Failed to get buffer!");
|
||||
return -1;
|
||||
}
|
||||
p = pout;
|
||||
*pout = nargs;
|
||||
pout++;
|
||||
*(const char **)pout = fmt;
|
||||
pout += sizeof(char *);
|
||||
while (nargs-- > 0) {
|
||||
uint32_t arg = va_arg(ap, uint32_t);
|
||||
*(uint32_t *)pout = arg;
|
||||
pout += sizeof(uint32_t);
|
||||
// ESP_APPTRACE_LOGI("arg %x", arg);
|
||||
}
|
||||
|
||||
int ret = apptrace_put_buffer(p, &tmo);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_APPTRACE_LOGE("Failed to put printf buf (%d)!", ret);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (pout - p);
|
||||
}
|
||||
|
||||
int esp_apptrace_vprintf(const char *fmt, va_list ap)
|
||||
{
|
||||
return esp_apptrace_vprintf_to(ESP_APPTRACE_DEST_TRAX, /*ESP_APPTRACE_TMO_INFINITE*/0, fmt, ap);
|
||||
}
|
||||
|
||||
uint8_t *esp_apptrace_buffer_get(esp_apptrace_dest_t dest, size_t size, uint32_t user_tmo)
|
||||
{
|
||||
uint32_t tmo = user_tmo;
|
||||
//TODO: use ptr to HW transport iface struct
|
||||
uint8_t *(*apptrace_get_buffer)(size_t, uint32_t *);
|
||||
|
||||
if (dest == ESP_APPTRACE_DEST_TRAX) {
|
||||
#if CONFIG_ESP32_APPTRACE_DEST_TRAX
|
||||
apptrace_get_buffer = esp_apptrace_trax_get_buffer;
|
||||
#else
|
||||
ESP_APPTRACE_LOGE("Application tracing via TRAX is disabled in menuconfig!");
|
||||
return NULL;
|
||||
#endif
|
||||
} else {
|
||||
ESP_APPTRACE_LOGE("Trace destinations other then TRAX are not supported yet!");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return apptrace_get_buffer(size, &tmo);
|
||||
}
|
||||
|
||||
esp_err_t esp_apptrace_buffer_put(esp_apptrace_dest_t dest, uint8_t *ptr, uint32_t user_tmo)
|
||||
{
|
||||
uint32_t tmo = user_tmo;
|
||||
//TODO: use ptr to HW transport iface struct
|
||||
esp_err_t (*apptrace_put_buffer)(uint8_t *, uint32_t *);
|
||||
|
||||
if (dest == ESP_APPTRACE_DEST_TRAX) {
|
||||
#if CONFIG_ESP32_APPTRACE_DEST_TRAX
|
||||
apptrace_put_buffer = esp_apptrace_trax_put_buffer;
|
||||
#else
|
||||
ESP_APPTRACE_LOGE("Application tracing via TRAX is disabled in menuconfig!");
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
#endif
|
||||
} else {
|
||||
ESP_APPTRACE_LOGE("Trace destinations other then TRAX are not supported yet!");
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
return apptrace_put_buffer(ptr, &tmo);
|
||||
}
|
||||
|
||||
esp_err_t esp_apptrace_flush_nolock(esp_apptrace_dest_t dest, uint32_t min_sz, uint32_t tmo)
|
||||
{
|
||||
//TODO: use ptr to HW transport iface struct
|
||||
esp_err_t (*apptrace_flush)(uint32_t, uint32_t);
|
||||
|
||||
if (dest == ESP_APPTRACE_DEST_TRAX) {
|
||||
#if CONFIG_ESP32_APPTRACE_DEST_TRAX
|
||||
apptrace_flush = esp_apptrace_trax_flush;
|
||||
#else
|
||||
ESP_APPTRACE_LOGE("Application tracing via TRAX is disabled in menuconfig!");
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
#endif
|
||||
} else {
|
||||
ESP_APPTRACE_LOGE("Trace destinations other then TRAX are not supported yet!");
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
return apptrace_flush(min_sz, tmo);
|
||||
}
|
||||
|
||||
esp_err_t esp_apptrace_flush(esp_apptrace_dest_t dest, uint32_t tmo)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = esp_apptrace_lock(&tmo);
|
||||
if (res != ESP_OK) {
|
||||
ESP_APPTRACE_LOGE("Failed to lock apptrace data (%d)!", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
res = esp_apptrace_flush_nolock(dest, 0, tmo);
|
||||
if (res != ESP_OK) {
|
||||
ESP_APPTRACE_LOGE("Failed to fluch apptrace data (%d)!", res);
|
||||
}
|
||||
|
||||
if (esp_apptrace_unlock() != ESP_OK) {
|
||||
ESP_APPTRACE_LOGE("Failed to unlock apptrace data (%d)!", res);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
#if ESP_APPTRACE_DEBUG_STATS_ENABLE == 1
|
||||
void esp_apptrace_print_stats()
|
||||
{
|
||||
uint32_t i;
|
||||
uint32_t tmo = ESP_APPTRACE_TMO_INFINITE;
|
||||
|
||||
esp_apptrace_lock(&tmo);
|
||||
|
||||
for (i = s_trace_buf.state.stats.wr.hist_rd; (i < s_trace_buf.state.stats.wr.hist_wr) && (i < ESP_APPTRACE_BUF_HISTORY_DEPTH); i++) {
|
||||
esp_trace_buffer_wr_hitem_t *hi = (esp_trace_buffer_wr_hitem_t *)&s_trace_buf.state.stats.wr.hist[i];
|
||||
ESP_APPTRACE_LOGO("hist[%u] = {%x, %x}", i, hi->hnd, hi->ts);
|
||||
}
|
||||
if (i == ESP_APPTRACE_BUF_HISTORY_DEPTH) {
|
||||
for (i = 0; i < s_trace_buf.state.stats.wr.hist_wr; i++) {
|
||||
esp_trace_buffer_wr_hitem_t *hi = (esp_trace_buffer_wr_hitem_t *)&s_trace_buf.state.stats.wr.hist[i];
|
||||
ESP_APPTRACE_LOGO("hist[%u] = {%x, %x}", i, hi->hnd, hi->ts);
|
||||
}
|
||||
}
|
||||
|
||||
esp_apptrace_unlock();
|
||||
}
|
||||
#endif
|
||||
#endif
|
@@ -188,7 +188,7 @@ void IRAM_ATTR call_start_cpu1()
|
||||
"wsr %0, vecbase\n" \
|
||||
::"r"(&_init_start));
|
||||
|
||||
ets_set_appcpu_boot_addr(0);
|
||||
ets_set_appcpu_boot_addr(0);
|
||||
cpu_configure_region_protection();
|
||||
|
||||
#if CONFIG_CONSOLE_UART_NONE
|
||||
@@ -257,6 +257,9 @@ void start_cpu0_default(void)
|
||||
if (err != ESP_OK) {
|
||||
ESP_EARLY_LOGE(TAG, "Failed to init apptrace module on CPU0 (%d)!", err);
|
||||
}
|
||||
#endif
|
||||
#if CONFIG_SYSVIEW_ENABLE
|
||||
SEGGER_SYSVIEW_Conf();
|
||||
#endif
|
||||
do_global_ctors();
|
||||
#if CONFIG_INT_WDT
|
||||
|
@@ -188,9 +188,9 @@ static void dport_access_init_core1(void *arg)
|
||||
void esp_dport_access_int_init(void)
|
||||
{
|
||||
if (xPortGetCoreID() == 0) {
|
||||
xTaskCreatePinnedToCore(&dport_access_init_core0, "dport0", 512, NULL, 5, NULL, 0);
|
||||
xTaskCreatePinnedToCore(&dport_access_init_core0, "dport0", 2048, NULL, 5, NULL, 0);
|
||||
} else {
|
||||
xTaskCreatePinnedToCore(&dport_access_init_core1, "dport1", 512, NULL, 5, NULL, 1);
|
||||
xTaskCreatePinnedToCore(&dport_access_init_core1, "dport1", 2048, NULL, 5, NULL, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,123 +0,0 @@
|
||||
// Copyright 2017 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_APP_TRACE_H_
|
||||
#define ESP_APP_TRACE_H_
|
||||
|
||||
#include <stdarg.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
// infinite waiting timeout
|
||||
#define ESP_APPTRACE_TMO_INFINITE ((uint32_t)-1)
|
||||
|
||||
// Trace memory block size
|
||||
#define ESP_APPTRACE_TRAX_BLOCK_SIZE 0x4000UL
|
||||
|
||||
/**
|
||||
* Application trace data destinations bits.
|
||||
*/
|
||||
typedef enum {
|
||||
ESP_APPTRACE_DEST_TRAX = 0x1,
|
||||
ESP_APPTRACE_DEST_UART0 = 0x2,
|
||||
//ESP_APPTRACE_DEST_UART1 = 0x4,
|
||||
} esp_apptrace_dest_t;
|
||||
|
||||
/**
|
||||
* @brief Initializes application tracing module.
|
||||
*
|
||||
* @note Should be called before any esp_apptrace_xxx call.
|
||||
*
|
||||
* @return ESP_OK on success, otherwise \see esp_err_t
|
||||
*/
|
||||
esp_err_t esp_apptrace_init();
|
||||
|
||||
/**
|
||||
* @brief Allocates buffer for trace data.
|
||||
* After data in buffer are ready to be sent off esp_apptrace_buffer_put must be called to indicate it.
|
||||
*
|
||||
* @param dest Indicates HW interface to send data.
|
||||
* @param size Size of data to write to trace buffer.
|
||||
* @param tmo Timeout for operation (in us). Use ESP_APPTRACE_TMO_INFINITE to wait indefinetly.
|
||||
*
|
||||
* @return non-NULL on success, otherwise NULL.
|
||||
*/
|
||||
uint8_t *esp_apptrace_buffer_get(esp_apptrace_dest_t dest, size_t size, uint32_t tmo);
|
||||
|
||||
/**
|
||||
* @brief Indicates that the data in buffer are ready to be sent off.
|
||||
* This function is a counterpart of must be preceeded by esp_apptrace_buffer_get.
|
||||
*
|
||||
* @param dest Indicates HW interface to send data. Should be identical to the same parameter in call to esp_apptrace_buffer_get.
|
||||
* @param ptr Address of trace buffer to release. Should be the value returned by call to esp_apptrace_buffer_get.
|
||||
* @param tmo Timeout for operation (in us). Use ESP_APPTRACE_TMO_INFINITE to wait indefinetly.
|
||||
*
|
||||
* @return ESP_OK on success, otherwise \see esp_err_t
|
||||
*/
|
||||
esp_err_t esp_apptrace_buffer_put(esp_apptrace_dest_t dest, uint8_t *ptr, uint32_t tmo);
|
||||
|
||||
/**
|
||||
* @brief Writes data to trace buffer.
|
||||
*
|
||||
* @param dest Indicates HW interface to send data.
|
||||
* @param data Address of data to write to trace buffer.
|
||||
* @param size Size of data to write to trace buffer.
|
||||
* @param tmo Timeout for operation (in us). Use ESP_APPTRACE_TMO_INFINITE to wait indefinetly.
|
||||
*
|
||||
* @return ESP_OK on success, otherwise \see esp_err_t
|
||||
*/
|
||||
esp_err_t esp_apptrace_write(esp_apptrace_dest_t dest, void *data, size_t size, uint32_t tmo);
|
||||
|
||||
/**
|
||||
* @brief vprintf-like function to sent log messages to host via specified HW interface.
|
||||
*
|
||||
* @param dest Indicates HW interface to send data.
|
||||
* @param fmt Address of format string.
|
||||
* @param ap List of arguments.
|
||||
*
|
||||
* @return Number of bytes written.
|
||||
*/
|
||||
int esp_apptrace_vprintf_to(esp_apptrace_dest_t dest, uint32_t user_tmo, const char *fmt, va_list ap);
|
||||
|
||||
/**
|
||||
* @brief vprintf-like function to sent log messages to host.
|
||||
*
|
||||
* @param fmt Address of format string.
|
||||
* @param ap List of arguments.
|
||||
*
|
||||
* @return Number of bytes written.
|
||||
*/
|
||||
int esp_apptrace_vprintf(const char *fmt, va_list ap);
|
||||
|
||||
/**
|
||||
* @brief Flushes remaining data in trace buffer to host.
|
||||
*
|
||||
* @param dest Indicates HW interface to flush data on.
|
||||
* @param tmo Timeout for operation (in us). Use ESP_APPTRACE_TMO_INFINITE to wait indefinetly.
|
||||
*
|
||||
* @return ESP_OK on success, otherwise \see esp_err_t
|
||||
*/
|
||||
esp_err_t esp_apptrace_flush(esp_apptrace_dest_t dest, uint32_t tmo);
|
||||
|
||||
/**
|
||||
* @brief Flushes remaining data in trace buffer to host without locking internal data.
|
||||
This is special version of esp_apptrace_flush which should be called from panic handler.
|
||||
*
|
||||
* @param dest Indicates HW interface to flush data on.
|
||||
* @param min_sz Threshold for flushing data. If current filling level is above this value, data will be flushed. TRAX destinations only.
|
||||
* @param tmo Timeout for operation (in us). Use ESP_APPTRACE_TMO_INFINITE to wait indefinetly.
|
||||
*
|
||||
* @return ESP_OK on success, otherwise \see esp_err_t
|
||||
*/
|
||||
esp_err_t esp_apptrace_flush_nolock(esp_apptrace_dest_t dest, uint32_t min_sz, uint32_t tmo);
|
||||
|
||||
#endif
|
@@ -77,6 +77,9 @@ extern "C" {
|
||||
|
||||
/**@}*/
|
||||
|
||||
// This is used to provide SystemView with positive IRQ IDs, otherwise sheduler events are not shown properly
|
||||
#define ETS_INTERNAL_INTR_SOURCE_OFF (-ETS_INTERNAL_PROFILING_INTR_SOURCE)
|
||||
|
||||
typedef void (*intr_handler_t)(void *arg);
|
||||
|
||||
|
||||
@@ -221,7 +224,6 @@ int esp_intr_get_cpu(intr_handle_t handle);
|
||||
*/
|
||||
int esp_intr_get_intno(intr_handle_t handle);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Disable the interrupt associated with the handle
|
||||
*
|
||||
|
@@ -24,6 +24,7 @@
|
||||
#include "freertos/task.h"
|
||||
#include <esp_types.h>
|
||||
#include "esp_err.h"
|
||||
//#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
|
||||
#include "esp_log.h"
|
||||
#include "esp_intr.h"
|
||||
#include "esp_attr.h"
|
||||
@@ -33,7 +34,6 @@
|
||||
|
||||
static const char* TAG = "intr_alloc";
|
||||
|
||||
|
||||
#define ETS_INTERNAL_TIMER0_INTR_NO 6
|
||||
#define ETS_INTERNAL_TIMER1_INTR_NO 15
|
||||
#define ETS_INTERNAL_TIMER2_INTR_NO 16
|
||||
@@ -74,7 +74,7 @@ typedef struct {
|
||||
} int_desc_t;
|
||||
|
||||
|
||||
//We should mark the interrupt for the timer used by FreeRTOS as reserved. The specific timer
|
||||
//We should mark the interrupt for the timer used by FreeRTOS as reserved. The specific timer
|
||||
//is selectable using menuconfig; we use these cpp bits to convert that into something we can use in
|
||||
//the table below.
|
||||
#if CONFIG_FREERTOS_CORETIMER_0
|
||||
@@ -159,6 +159,13 @@ struct intr_handle_data_t {
|
||||
shared_vector_desc_t *shared_vector_desc;
|
||||
};
|
||||
|
||||
typedef struct non_shared_isr_arg_t non_shared_isr_arg_t;
|
||||
|
||||
struct non_shared_isr_arg_t {
|
||||
intr_handler_t isr;
|
||||
void *isr_arg;
|
||||
int source;
|
||||
};
|
||||
|
||||
//Linked list of vector descriptions, sorted by cpu.intno value
|
||||
static vector_desc_t *vector_desc_head;
|
||||
@@ -169,12 +176,15 @@ static uint32_t non_iram_int_mask[portNUM_PROCESSORS];
|
||||
static uint32_t non_iram_int_disabled[portNUM_PROCESSORS];
|
||||
static bool non_iram_int_disabled_flag[portNUM_PROCESSORS];
|
||||
|
||||
#if CONFIG_SYSVIEW_ENABLE
|
||||
extern uint32_t port_switch_flag[];
|
||||
#endif
|
||||
|
||||
static portMUX_TYPE spinlock = portMUX_INITIALIZER_UNLOCKED;
|
||||
|
||||
//Inserts an item into vector_desc list so that the list is sorted
|
||||
//with an incrementing cpu.intno value.
|
||||
static void insert_vector_desc(vector_desc_t *to_insert)
|
||||
static void insert_vector_desc(vector_desc_t *to_insert)
|
||||
{
|
||||
vector_desc_t *vd=vector_desc_head;
|
||||
vector_desc_t *prev=NULL;
|
||||
@@ -195,7 +205,7 @@ static void insert_vector_desc(vector_desc_t *to_insert)
|
||||
}
|
||||
|
||||
//Returns a vector_desc entry for an intno/cpu, or NULL if none exists.
|
||||
static vector_desc_t *find_desc_for_int(int intno, int cpu)
|
||||
static vector_desc_t *find_desc_for_int(int intno, int cpu)
|
||||
{
|
||||
vector_desc_t *vd=vector_desc_head;
|
||||
while(vd!=NULL) {
|
||||
@@ -208,7 +218,7 @@ static vector_desc_t *find_desc_for_int(int intno, int cpu)
|
||||
//Returns a vector_desc entry for an intno/cpu.
|
||||
//Either returns a preexisting one or allocates a new one and inserts
|
||||
//it into the list. Returns NULL on malloc fail.
|
||||
static vector_desc_t *get_desc_for_int(int intno, int cpu)
|
||||
static vector_desc_t *get_desc_for_int(int intno, int cpu)
|
||||
{
|
||||
vector_desc_t *vd=find_desc_for_int(intno, cpu);
|
||||
if (vd==NULL) {
|
||||
@@ -234,7 +244,7 @@ esp_err_t esp_intr_mark_shared(int intno, int cpu, bool is_int_ram)
|
||||
if (vd==NULL) {
|
||||
portEXIT_CRITICAL(&spinlock);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
}
|
||||
vd->flags=VECDESC_FL_SHARED;
|
||||
if (is_int_ram) vd->flags|=VECDESC_FL_INIRAM;
|
||||
portEXIT_CRITICAL(&spinlock);
|
||||
@@ -252,16 +262,16 @@ esp_err_t esp_intr_reserve(int intno, int cpu)
|
||||
if (vd==NULL) {
|
||||
portEXIT_CRITICAL(&spinlock);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
}
|
||||
vd->flags=VECDESC_FL_RESERVED;
|
||||
portEXIT_CRITICAL(&spinlock);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
//Interrupt handler table and unhandled uinterrupt routine. Duplicated
|
||||
//from xtensa_intr.c... it's supposed to be private, but we need to look
|
||||
//into it in order to see if someone allocated an int using
|
||||
//Interrupt handler table and unhandled uinterrupt routine. Duplicated
|
||||
//from xtensa_intr.c... it's supposed to be private, but we need to look
|
||||
//into it in order to see if someone allocated an int using
|
||||
//xt_set_interrupt_handler.
|
||||
typedef struct xt_handler_table_entry {
|
||||
void * handler;
|
||||
@@ -271,7 +281,7 @@ extern xt_handler_table_entry _xt_interrupt_table[XCHAL_NUM_INTERRUPTS*portNUM_P
|
||||
extern void xt_unhandled_interrupt(void * arg);
|
||||
|
||||
//Returns true if handler for interrupt is not the default unhandled interrupt handler
|
||||
static bool int_has_handler(int intr, int cpu)
|
||||
static bool int_has_handler(int intr, int cpu)
|
||||
{
|
||||
return (_xt_interrupt_table[intr*portNUM_PROCESSORS+cpu].handler != xt_unhandled_interrupt);
|
||||
}
|
||||
@@ -303,8 +313,8 @@ static int get_free_int(int flags, int cpu, int force)
|
||||
ALCHLOG(TAG, "Ignoring int %d: forced to %d", x, force);
|
||||
continue;
|
||||
}
|
||||
ALCHLOG(TAG, "Int %d reserved %d level %d %s hasIsr %d",
|
||||
x, int_desc[x].cpuflags[cpu]==INTDESC_RESVD, int_desc[x].level,
|
||||
ALCHLOG(TAG, "Int %d reserved %d level %d %s hasIsr %d",
|
||||
x, int_desc[x].cpuflags[cpu]==INTDESC_RESVD, int_desc[x].level,
|
||||
int_desc[x].type==INTTP_LEVEL?"LEVEL":"EDGE", int_has_handler(x, cpu));
|
||||
//Check if interrupt is not reserved by design
|
||||
if (int_desc[x].cpuflags[cpu]==INTDESC_RESVD) {
|
||||
@@ -321,7 +331,7 @@ static int get_free_int(int flags, int cpu, int force)
|
||||
continue;
|
||||
}
|
||||
//check if edge/level type matches what we want
|
||||
if (((flags&ESP_INTR_FLAG_EDGE) && (int_desc[x].type==INTTP_LEVEL)) ||
|
||||
if (((flags&ESP_INTR_FLAG_EDGE) && (int_desc[x].type==INTTP_LEVEL)) ||
|
||||
(((!(flags&ESP_INTR_FLAG_EDGE)) && (int_desc[x].type==INTTP_EDGE)))) {
|
||||
ALCHLOG(TAG, "....Unusable: incompatible trigger type");
|
||||
continue;
|
||||
@@ -373,7 +383,7 @@ static int get_free_int(int flags, int cpu, int force)
|
||||
}
|
||||
} else {
|
||||
if (best==-1) {
|
||||
//We haven't found a feasible shared interrupt yet. This one is still free and usable, even if
|
||||
//We haven't found a feasible shared interrupt yet. This one is still free and usable, even if
|
||||
//not marked as shared.
|
||||
//Remember it in case we don't find any other shared interrupt that qualifies.
|
||||
if (bestLevel>int_desc[x].level) {
|
||||
@@ -406,9 +416,8 @@ static int get_free_int(int flags, int cpu, int force)
|
||||
return best;
|
||||
}
|
||||
|
||||
|
||||
//Common shared isr handler. Chain-call all ISRs.
|
||||
static void IRAM_ATTR shared_intr_isr(void *arg)
|
||||
static void IRAM_ATTR shared_intr_isr(void *arg)
|
||||
{
|
||||
vector_desc_t *vd=(vector_desc_t*)arg;
|
||||
shared_vector_desc_t *sh_vec=vd->shared_vec_info;
|
||||
@@ -416,7 +425,16 @@ static void IRAM_ATTR shared_intr_isr(void *arg)
|
||||
while(sh_vec) {
|
||||
if (!sh_vec->disabled) {
|
||||
if ((sh_vec->statusreg == NULL) || (*sh_vec->statusreg & sh_vec->statusmask)) {
|
||||
#if CONFIG_SYSVIEW_ENABLE
|
||||
traceISR_ENTER(sh_vec->source+ETS_INTERNAL_INTR_SOURCE_OFF);
|
||||
#endif
|
||||
sh_vec->isr(sh_vec->arg);
|
||||
#if CONFIG_SYSVIEW_ENABLE
|
||||
// check if we will return to scheduler or to interrupted task after ISR
|
||||
if (!port_switch_flag[xPortGetCoreID()]) {
|
||||
traceISR_EXIT();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
sh_vec=sh_vec->next;
|
||||
@@ -424,10 +442,27 @@ static void IRAM_ATTR shared_intr_isr(void *arg)
|
||||
portEXIT_CRITICAL(&spinlock);
|
||||
}
|
||||
|
||||
#if CONFIG_SYSVIEW_ENABLE
|
||||
//Common non-shared isr handler wrapper.
|
||||
static void IRAM_ATTR non_shared_intr_isr(void *arg)
|
||||
{
|
||||
non_shared_isr_arg_t *ns_isr_arg=(non_shared_isr_arg_t*)arg;
|
||||
portENTER_CRITICAL(&spinlock);
|
||||
traceISR_ENTER(ns_isr_arg->source+ETS_INTERNAL_INTR_SOURCE_OFF);
|
||||
// FIXME: can we call ISR and check port_switch_flag after releasing spinlock?
|
||||
// when CONFIG_SYSVIEW_ENABLE = 0 ISRs for non-shared IRQs are called without spinlock
|
||||
ns_isr_arg->isr(ns_isr_arg->isr_arg);
|
||||
// check if we will return to scheduler or to interrupted task after ISR
|
||||
if (!port_switch_flag[xPortGetCoreID()]) {
|
||||
traceISR_EXIT();
|
||||
}
|
||||
portEXIT_CRITICAL(&spinlock);
|
||||
}
|
||||
#endif
|
||||
|
||||
//We use ESP_EARLY_LOG* here because this can be called before the scheduler is running.
|
||||
esp_err_t esp_intr_alloc_intrstatus(int source, int flags, uint32_t intrstatusreg, uint32_t intrstatusmask, intr_handler_t handler,
|
||||
void *arg, intr_handle_t *ret_handle)
|
||||
esp_err_t esp_intr_alloc_intrstatus(int source, int flags, uint32_t intrstatusreg, uint32_t intrstatusmask, intr_handler_t handler,
|
||||
void *arg, intr_handle_t *ret_handle)
|
||||
{
|
||||
intr_handle_data_t *ret=NULL;
|
||||
int force=-1;
|
||||
@@ -456,7 +491,7 @@ esp_err_t esp_intr_alloc_intrstatus(int source, int flags, uint32_t intrstatusre
|
||||
}
|
||||
}
|
||||
ESP_EARLY_LOGV(TAG, "esp_intr_alloc_intrstatus (cpu %d): Args okay. Resulting flags 0x%X", xPortGetCoreID(), flags);
|
||||
|
||||
|
||||
//Check 'special' interrupt sources. These are tied to one specific interrupt, so we
|
||||
//have to force get_free_int to only look at that.
|
||||
if (source==ETS_INTERNAL_TIMER0_INTR_SOURCE) force=ETS_INTERNAL_TIMER0_INTR_NO;
|
||||
@@ -513,7 +548,20 @@ esp_err_t esp_intr_alloc_intrstatus(int source, int flags, uint32_t intrstatusre
|
||||
//Mark as unusable for other interrupt sources. This is ours now!
|
||||
vd->flags=VECDESC_FL_NONSHARED;
|
||||
if (handler) {
|
||||
#if CONFIG_SYSVIEW_ENABLE
|
||||
non_shared_isr_arg_t *ns_isr_arg=malloc(sizeof(non_shared_isr_arg_t));
|
||||
if (!ns_isr_arg) {
|
||||
portEXIT_CRITICAL(&spinlock);
|
||||
free(ret);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
ns_isr_arg->isr=handler;
|
||||
ns_isr_arg->isr_arg=arg;
|
||||
ns_isr_arg->source=source;
|
||||
xt_set_interrupt_handler(intr, non_shared_intr_isr, ns_isr_arg);
|
||||
#else
|
||||
xt_set_interrupt_handler(intr, handler, arg);
|
||||
#endif
|
||||
}
|
||||
if (flags&ESP_INTR_FLAG_EDGE) xthal_set_intclear(1 << intr);
|
||||
vd->source=source;
|
||||
@@ -555,18 +603,18 @@ esp_err_t esp_intr_alloc_intrstatus(int source, int flags, uint32_t intrstatusre
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_intr_alloc(int source, int flags, intr_handler_t handler, void *arg, intr_handle_t *ret_handle)
|
||||
esp_err_t esp_intr_alloc(int source, int flags, intr_handler_t handler, void *arg, intr_handle_t *ret_handle)
|
||||
{
|
||||
/*
|
||||
As an optimization, we can create a table with the possible interrupt status registers and masks for every single
|
||||
source there is. We can then add code here to look up an applicable value and pass that to the
|
||||
source there is. We can then add code here to look up an applicable value and pass that to the
|
||||
esp_intr_alloc_intrstatus function.
|
||||
*/
|
||||
return esp_intr_alloc_intrstatus(source, flags, 0, 0, handler, arg, ret_handle);
|
||||
}
|
||||
|
||||
|
||||
esp_err_t esp_intr_free(intr_handle_t handle)
|
||||
esp_err_t esp_intr_free(intr_handle_t handle)
|
||||
{
|
||||
bool free_shared_vector=false;
|
||||
if (!handle) return ESP_ERR_INVALID_ARG;
|
||||
@@ -576,7 +624,7 @@ esp_err_t esp_intr_free(intr_handle_t handle)
|
||||
portENTER_CRITICAL(&spinlock);
|
||||
esp_intr_disable(handle);
|
||||
if (handle->vector_desc->flags&VECDESC_FL_SHARED) {
|
||||
//Find and kill the shared int
|
||||
//Find and kill the shared int
|
||||
shared_vector_desc_t *svd=handle->vector_desc->shared_vec_info;
|
||||
shared_vector_desc_t *prevsvd=NULL;
|
||||
assert(svd); //should be something in there for a shared int
|
||||
@@ -601,11 +649,19 @@ esp_err_t esp_intr_free(intr_handle_t handle)
|
||||
|
||||
if ((handle->vector_desc->flags&VECDESC_FL_NONSHARED) || free_shared_vector) {
|
||||
ESP_LOGV(TAG, "esp_intr_free: Disabling int, killing handler");
|
||||
#if CONFIG_SYSVIEW_ENABLE
|
||||
if (!free_shared_vector) {
|
||||
void *isr_arg = xt_get_interrupt_handler_arg(handle->vector_desc->intno);
|
||||
if (isr_arg) {
|
||||
free(isr_arg);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
//Reset to normal handler
|
||||
xt_set_interrupt_handler(handle->vector_desc->intno, xt_unhandled_interrupt, (void*)((int)handle->vector_desc->intno));
|
||||
//Theoretically, we could free the vector_desc... not sure if that's worth the few bytes of memory
|
||||
//we save.(We can also not use the same exit path for empty shared ints anymore if we delete
|
||||
//the desc.) For now, just mark it as free.
|
||||
//we save.(We can also not use the same exit path for empty shared ints anymore if we delete
|
||||
//the desc.) For now, just mark it as free.
|
||||
handle->vector_desc->flags&=!(VECDESC_FL_NONSHARED|VECDESC_FL_RESERVED);
|
||||
//Also kill non_iram mask bit.
|
||||
non_iram_int_mask[handle->vector_desc->cpu]&=~(1<<(handle->vector_desc->intno));
|
||||
|
@@ -86,7 +86,8 @@ SECTIONS
|
||||
*libesp32.a:panic.o(.literal .text .literal.* .text.*)
|
||||
*libesp32.a:core_dump.o(.literal .text .literal.* .text.*)
|
||||
*libesp32.a:heap_alloc_caps.o(.literal .text .literal.* .text.*)
|
||||
*libesp32.a:app_trace.o(.literal .text .literal.* .text.*)
|
||||
*libapp_trace.a:(.literal .text .literal.* .text.*)
|
||||
*libxtensa-debug-module.a:eri.o(.literal .text .literal.* .text.*)
|
||||
*libphy.a:(.literal .text .literal.* .text.*)
|
||||
*librtc.a:(.literal .text .literal.* .text.*)
|
||||
*libsoc.a:(.literal .text .literal.* .text.*)
|
||||
@@ -111,8 +112,8 @@ SECTIONS
|
||||
KEEP(*(.jcr))
|
||||
*(.dram1 .dram1.*)
|
||||
*libesp32.a:panic.o(.rodata .rodata.*)
|
||||
*libesp32.a:app_trace.o(.rodata .rodata.*)
|
||||
*libphy.a:(.rodata .rodata.*)
|
||||
*libapp_trace.a:(.rodata .rodata.*)
|
||||
_data_end = ABSOLUTE(.);
|
||||
. = ALIGN(4);
|
||||
} >dram0_0_seg
|
||||
|
@@ -39,7 +39,15 @@
|
||||
#include "esp_spi_flash.h"
|
||||
#include "esp_cache_err_int.h"
|
||||
#include "esp_app_trace.h"
|
||||
#if CONFIG_SYSVIEW_ENABLE
|
||||
#include "SEGGER_RTT.h"
|
||||
#endif
|
||||
|
||||
#if CONFIG_ESP32_APPTRACE_ONPANIC_HOST_FLUSH_TMO == -1
|
||||
#define APPTRACE_ONPANIC_HOST_FLUSH_TMO ESP_APPTRACE_TMO_INFINITE
|
||||
#else
|
||||
#define APPTRACE_ONPANIC_HOST_FLUSH_TMO (1000*CONFIG_ESP32_APPTRACE_ONPANIC_HOST_FLUSH_TMO)
|
||||
#endif
|
||||
/*
|
||||
Panic handlers; these get called when an unhandled exception occurs or the assembly-level
|
||||
task switching / interrupt code runs into an unrecoverable error. The default task stack
|
||||
@@ -116,7 +124,12 @@ static __attribute__((noreturn)) inline void invoke_abort()
|
||||
{
|
||||
abort_called = true;
|
||||
#if CONFIG_ESP32_APPTRACE_ENABLE
|
||||
esp_apptrace_flush_nolock(ESP_APPTRACE_DEST_TRAX, ESP_APPTRACE_TRAX_BLOCK_SIZE*CONFIG_ESP32_APPTRACE_ONPANIC_HOST_FLUSH_TRAX_THRESH/100, CONFIG_ESP32_APPTRACE_ONPANIC_HOST_FLUSH_TMO);
|
||||
#if CONFIG_SYSVIEW_ENABLE
|
||||
SEGGER_RTT_ESP32_FlushNoLock(CONFIG_ESP32_APPTRACE_POSTMORTEM_FLUSH_TRAX_THRESH, APPTRACE_ONPANIC_HOST_FLUSH_TMO);
|
||||
#else
|
||||
esp_apptrace_flush_nolock(ESP_APPTRACE_DEST_TRAX, CONFIG_ESP32_APPTRACE_POSTMORTEM_FLUSH_TRAX_THRESH,
|
||||
APPTRACE_ONPANIC_HOST_FLUSH_TMO);
|
||||
#endif
|
||||
#endif
|
||||
while(1) {
|
||||
if (esp_cpu_in_ocd_debug_mode()) {
|
||||
@@ -233,7 +246,12 @@ void panicHandler(XtExcFrame *frame)
|
||||
|
||||
if (esp_cpu_in_ocd_debug_mode()) {
|
||||
#if CONFIG_ESP32_APPTRACE_ENABLE
|
||||
esp_apptrace_flush_nolock(ESP_APPTRACE_DEST_TRAX, ESP_APPTRACE_TRAX_BLOCK_SIZE*CONFIG_ESP32_APPTRACE_ONPANIC_HOST_FLUSH_TRAX_THRESH/100, CONFIG_ESP32_APPTRACE_ONPANIC_HOST_FLUSH_TMO);
|
||||
#if CONFIG_SYSVIEW_ENABLE
|
||||
SEGGER_RTT_ESP32_FlushNoLock(CONFIG_ESP32_APPTRACE_POSTMORTEM_FLUSH_TRAX_THRESH, APPTRACE_ONPANIC_HOST_FLUSH_TMO);
|
||||
#else
|
||||
esp_apptrace_flush_nolock(ESP_APPTRACE_DEST_TRAX, CONFIG_ESP32_APPTRACE_POSTMORTEM_FLUSH_TRAX_THRESH,
|
||||
APPTRACE_ONPANIC_HOST_FLUSH_TMO);
|
||||
#endif
|
||||
#endif
|
||||
setFirstBreakpoint(frame->pc);
|
||||
return;
|
||||
@@ -259,7 +277,12 @@ void xt_unhandled_exception(XtExcFrame *frame)
|
||||
panicPutHex(frame->pc);
|
||||
panicPutStr(". Setting bp and returning..\r\n");
|
||||
#if CONFIG_ESP32_APPTRACE_ENABLE
|
||||
esp_apptrace_flush_nolock(ESP_APPTRACE_DEST_TRAX, ESP_APPTRACE_TRAX_BLOCK_SIZE*CONFIG_ESP32_APPTRACE_ONPANIC_HOST_FLUSH_TRAX_THRESH/100, CONFIG_ESP32_APPTRACE_ONPANIC_HOST_FLUSH_TMO);
|
||||
#if CONFIG_SYSVIEW_ENABLE
|
||||
SEGGER_RTT_ESP32_FlushNoLock(CONFIG_ESP32_APPTRACE_POSTMORTEM_FLUSH_TRAX_THRESH, APPTRACE_ONPANIC_HOST_FLUSH_TMO);
|
||||
#else
|
||||
esp_apptrace_flush_nolock(ESP_APPTRACE_DEST_TRAX, CONFIG_ESP32_APPTRACE_POSTMORTEM_FLUSH_TRAX_THRESH,
|
||||
APPTRACE_ONPANIC_HOST_FLUSH_TMO);
|
||||
#endif
|
||||
#endif
|
||||
//Stick a hardware breakpoint on the address the handler returns to. This way, the OCD debugger
|
||||
//will kick in exactly at the context the error happened.
|
||||
@@ -430,7 +453,12 @@ static __attribute__((noreturn)) void commonErrorHandler(XtExcFrame *frame)
|
||||
|
||||
#if CONFIG_ESP32_APPTRACE_ENABLE
|
||||
disableAllWdts();
|
||||
esp_apptrace_flush_nolock(ESP_APPTRACE_DEST_TRAX, ESP_APPTRACE_TRAX_BLOCK_SIZE*CONFIG_ESP32_APPTRACE_ONPANIC_HOST_FLUSH_TRAX_THRESH/100, CONFIG_ESP32_APPTRACE_ONPANIC_HOST_FLUSH_TMO);
|
||||
#if CONFIG_SYSVIEW_ENABLE
|
||||
SEGGER_RTT_ESP32_FlushNoLock(CONFIG_ESP32_APPTRACE_POSTMORTEM_FLUSH_TRAX_THRESH, APPTRACE_ONPANIC_HOST_FLUSH_TMO);
|
||||
#else
|
||||
esp_apptrace_flush_nolock(ESP_APPTRACE_DEST_TRAX, CONFIG_ESP32_APPTRACE_POSTMORTEM_FLUSH_TRAX_THRESH,
|
||||
APPTRACE_ONPANIC_HOST_FLUSH_TMO);
|
||||
#endif
|
||||
reconfigureAllWdts();
|
||||
#endif
|
||||
|
||||
|
@@ -1,817 +0,0 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include "unity.h"
|
||||
#include "driver/timer.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/task.h"
|
||||
#if CONFIG_ESP32_APPTRACE_ENABLE == 1
|
||||
#include "esp_app_trace.h"
|
||||
|
||||
#define ESP_APPTRACE_TEST_USE_PRINT_LOCK 0
|
||||
#define ESP_APPTRACE_TEST_PRN_WRERR_MAX 5
|
||||
#define ESP_APPTRACE_TEST_BLOCKS_BEFORE_CRASH 100
|
||||
#define ESP_APPTRACE_TEST_BLOCK_SIZE 1024
|
||||
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
|
||||
#include "esp_log.h"
|
||||
const static char *TAG = "esp_apptrace_test";
|
||||
|
||||
#if ESP_APPTRACE_TEST_USE_PRINT_LOCK == 1
|
||||
#define ESP_APPTRACE_TEST_LOG( format, ... ) \
|
||||
do { \
|
||||
BaseType_t ret; \
|
||||
if (xPortInIsrContext()) \
|
||||
ret = xSemaphoreTakeFromISR(s_print_lock, NULL); \
|
||||
else \
|
||||
ret = xSemaphoreTake(s_print_lock, portMAX_DELAY); \
|
||||
if (ret == pdTRUE) { \
|
||||
ets_printf(format, ##__VA_ARGS__); \
|
||||
if (xPortInIsrContext()) \
|
||||
xSemaphoreGiveFromISR(s_print_lock, NULL); \
|
||||
else \
|
||||
xSemaphoreGive(s_print_lock); \
|
||||
} \
|
||||
} while(0)
|
||||
#else
|
||||
#define ESP_APPTRACE_TEST_LOG( format, ... ) \
|
||||
do { \
|
||||
ets_printf(format, ##__VA_ARGS__); \
|
||||
} while(0)
|
||||
#endif
|
||||
|
||||
#define ESP_APPTRACE_TEST_LOG_LEVEL( _L_, level, format, ... ) \
|
||||
do { \
|
||||
if (LOG_LOCAL_LEVEL >= level) { \
|
||||
ESP_APPTRACE_TEST_LOG(LOG_FORMAT(_L_, format), esp_log_early_timestamp(), TAG, ##__VA_ARGS__); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define ESP_APPTRACE_TEST_LOGE( format, ... ) ESP_APPTRACE_TEST_LOG_LEVEL(E, ESP_LOG_ERROR, format, ##__VA_ARGS__)
|
||||
#define ESP_APPTRACE_TEST_LOGW( format, ... ) ESP_APPTRACE_TEST_LOG_LEVEL(W, ESP_LOG_WARN, format, ##__VA_ARGS__)
|
||||
#define ESP_APPTRACE_TEST_LOGI( format, ... ) ESP_APPTRACE_TEST_LOG_LEVEL(I, ESP_LOG_INFO, format, ##__VA_ARGS__)
|
||||
#define ESP_APPTRACE_TEST_LOGD( format, ... ) ESP_APPTRACE_TEST_LOG_LEVEL(D, ESP_LOG_DEBUG, format, ##__VA_ARGS__)
|
||||
#define ESP_APPTRACE_TEST_LOGV( format, ... ) ESP_APPTRACE_TEST_LOG_LEVEL(V, ESP_LOG_VERBOSE, format, ##__VA_ARGS__)
|
||||
#define ESP_APPTRACE_TEST_LOGO( format, ... ) ESP_APPTRACE_TEST_LOG_LEVEL(E, ESP_LOG_NONE, format, ##__VA_ARGS__)
|
||||
|
||||
#define ESP_APPTRACE_TEST_WRITE(_b_, _s_) esp_apptrace_write(ESP_APPTRACE_DEST_TRAX, _b_, _s_, ESP_APPTRACE_TMO_INFINITE)
|
||||
#define ESP_APPTRACE_TEST_WRITE_FROM_ISR(_b_, _s_) esp_apptrace_write(ESP_APPTRACE_DEST_TRAX, _b_, _s_, 100UL)
|
||||
#define ESP_APPTRACE_TEST_WRITE_NOWAIT(_b_, _s_) esp_apptrace_write(ESP_APPTRACE_DEST_TRAX, _b_, _s_, 0)
|
||||
|
||||
#define ESP_APPTRACE_TEST_CPUTICKS2US(_t_) ((_t_)/(XT_CLOCK_FREQ/1000000))
|
||||
|
||||
typedef struct {
|
||||
uint8_t *buf;
|
||||
uint32_t buf_sz;
|
||||
uint8_t mask;
|
||||
uint32_t period; // trace write period in us
|
||||
uint32_t wr_err;
|
||||
uint32_t wr_cnt;
|
||||
} esp_apptrace_test_gen_data_t;
|
||||
|
||||
typedef struct {
|
||||
int group;
|
||||
int id;
|
||||
void (*isr_func)(void *);
|
||||
esp_apptrace_test_gen_data_t data;
|
||||
} esp_apptrace_test_timer_arg_t;
|
||||
|
||||
typedef struct {
|
||||
int nowait;
|
||||
int core;
|
||||
int prio;
|
||||
void (*task_func)(void *);
|
||||
esp_apptrace_test_gen_data_t data;
|
||||
volatile int stop;
|
||||
SemaphoreHandle_t done;
|
||||
|
||||
uint32_t timers_num;
|
||||
esp_apptrace_test_timer_arg_t *timers;
|
||||
} esp_apptrace_test_task_arg_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t tasks_num;
|
||||
esp_apptrace_test_task_arg_t *tasks;
|
||||
} esp_apptrace_test_cfg_t;
|
||||
|
||||
#if ESP_APPTRACE_TEST_USE_PRINT_LOCK == 1
|
||||
static SemaphoreHandle_t s_print_lock;
|
||||
#endif
|
||||
|
||||
static uint64_t esp_apptrace_test_ts_get();
|
||||
|
||||
static void esp_apptrace_test_timer_init(int timer_group, int timer_idx, uint32_t period)
|
||||
{
|
||||
timer_config_t config;
|
||||
uint64_t alarm_val = (period * (TIMER_BASE_CLK / 1000000UL)) / 2;
|
||||
|
||||
config.alarm_en = 1;
|
||||
config.auto_reload = 1;
|
||||
config.counter_dir = TIMER_COUNT_UP;
|
||||
config.divider = 1;
|
||||
config.intr_type = TIMER_INTR_LEVEL;
|
||||
config.counter_en = TIMER_PAUSE;
|
||||
/*Configure timer*/
|
||||
timer_init(timer_group, timer_idx, &config);
|
||||
/*Stop timer counter*/
|
||||
timer_pause(timer_group, timer_idx);
|
||||
/*Load counter value */
|
||||
timer_set_counter_value(timer_group, timer_idx, 0x00000000ULL);
|
||||
/*Set alarm value*/
|
||||
timer_set_alarm_value(timer_group, timer_idx, alarm_val);
|
||||
/*Enable timer interrupt*/
|
||||
timer_enable_intr(timer_group, timer_idx);
|
||||
}
|
||||
|
||||
static void esp_apptrace_test_timer_isr(void *arg)
|
||||
{
|
||||
esp_apptrace_test_timer_arg_t *tim_arg = (esp_apptrace_test_timer_arg_t *)arg;
|
||||
|
||||
uint32_t *ts = (uint32_t *)(tim_arg->data.buf + sizeof(uint32_t));
|
||||
*ts = (uint32_t)esp_apptrace_test_ts_get();
|
||||
memset(tim_arg->data.buf + 2 * sizeof(uint32_t), tim_arg->data.wr_cnt & tim_arg->data.mask, tim_arg->data.buf_sz - 2 * sizeof(uint32_t));
|
||||
int res = ESP_APPTRACE_TEST_WRITE_FROM_ISR(tim_arg->data.buf, tim_arg->data.buf_sz);
|
||||
if (res != ESP_OK) {
|
||||
} else {
|
||||
if (0) {
|
||||
ets_printf("tim-%d-%d: Written chunk%d %d bytes, %x\n",
|
||||
tim_arg->group, tim_arg->id, tim_arg->data.wr_cnt, tim_arg->data.buf_sz, tim_arg->data.wr_cnt & tim_arg->data.mask);
|
||||
}
|
||||
tim_arg->data.wr_err = 0;
|
||||
}
|
||||
|
||||
tim_arg->data.wr_cnt++;
|
||||
if (tim_arg->group == 0) {
|
||||
if (tim_arg->id == 0) {
|
||||
TIMERG0.int_clr_timers.t0 = 1;
|
||||
TIMERG0.hw_timer[0].update = 1;
|
||||
TIMERG0.hw_timer[0].config.alarm_en = 1;
|
||||
} else {
|
||||
TIMERG0.int_clr_timers.t1 = 1;
|
||||
TIMERG0.hw_timer[1].update = 1;
|
||||
TIMERG0.hw_timer[1].config.alarm_en = 1;
|
||||
}
|
||||
}
|
||||
if (tim_arg->group == 1) {
|
||||
if (tim_arg->id == 0) {
|
||||
TIMERG1.int_clr_timers.t0 = 1;
|
||||
TIMERG1.hw_timer[0].update = 1;
|
||||
TIMERG1.hw_timer[0].config.alarm_en = 1;
|
||||
} else {
|
||||
TIMERG1.int_clr_timers.t1 = 1;
|
||||
TIMERG1.hw_timer[1].update = 1;
|
||||
TIMERG1.hw_timer[1].config.alarm_en = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void esp_apptrace_test_timer_isr_crash(void *arg)
|
||||
{
|
||||
esp_apptrace_test_timer_arg_t *tim_arg = (esp_apptrace_test_timer_arg_t *)arg;
|
||||
|
||||
if (tim_arg->group == 0) {
|
||||
if (tim_arg->id == 0) {
|
||||
TIMERG0.int_clr_timers.t0 = 1;
|
||||
TIMERG0.hw_timer[0].update = 1;
|
||||
TIMERG0.hw_timer[0].config.alarm_en = 1;
|
||||
} else {
|
||||
TIMERG0.int_clr_timers.t1 = 1;
|
||||
TIMERG0.hw_timer[1].update = 1;
|
||||
TIMERG0.hw_timer[1].config.alarm_en = 1;
|
||||
}
|
||||
}
|
||||
if (tim_arg->group == 1) {
|
||||
if (tim_arg->id == 0) {
|
||||
TIMERG1.int_clr_timers.t0 = 1;
|
||||
TIMERG1.hw_timer[0].update = 1;
|
||||
TIMERG1.hw_timer[0].config.alarm_en = 1;
|
||||
} else {
|
||||
TIMERG1.int_clr_timers.t1 = 1;
|
||||
TIMERG1.hw_timer[1].update = 1;
|
||||
TIMERG1.hw_timer[1].config.alarm_en = 1;
|
||||
}
|
||||
}
|
||||
if (tim_arg->data.wr_cnt < ESP_APPTRACE_TEST_BLOCKS_BEFORE_CRASH) {
|
||||
uint32_t *ts = (uint32_t *)(tim_arg->data.buf + sizeof(uint32_t));
|
||||
*ts = (uint32_t)esp_apptrace_test_ts_get();//xthal_get_ccount();//xTaskGetTickCount();
|
||||
memset(tim_arg->data.buf + 2 * sizeof(uint32_t), tim_arg->data.wr_cnt & tim_arg->data.mask, tim_arg->data.buf_sz - 2 * sizeof(uint32_t));
|
||||
int res = ESP_APPTRACE_TEST_WRITE_FROM_ISR(tim_arg->data.buf, tim_arg->data.buf_sz);
|
||||
if (res != ESP_OK) {
|
||||
ets_printf("tim-%d-%d: Failed to write trace %d %x!\n", tim_arg->group, tim_arg->id, res, tim_arg->data.wr_cnt & tim_arg->data.mask);
|
||||
} else {
|
||||
ets_printf("tim-%d-%d: Written chunk%d %d bytes, %x\n",
|
||||
tim_arg->group, tim_arg->id, tim_arg->data.wr_cnt, tim_arg->data.buf_sz, tim_arg->data.wr_cnt & tim_arg->data.mask);
|
||||
tim_arg->data.wr_cnt++;
|
||||
}
|
||||
} else {
|
||||
uint32_t *ptr = 0;
|
||||
*ptr = 1000;
|
||||
}
|
||||
}
|
||||
|
||||
static void esp_apptrace_dummy_task(void *p)
|
||||
{
|
||||
esp_apptrace_test_task_arg_t *arg = (esp_apptrace_test_task_arg_t *) p;
|
||||
int res, flags = 0, i;
|
||||
timer_isr_handle_t *inth = NULL;
|
||||
TickType_t tmo_ticks = arg->data.period / (1000 * portTICK_PERIOD_MS);
|
||||
|
||||
ESP_APPTRACE_TEST_LOGI("%x: run dummy task (period %u us, %u timers)", xTaskGetCurrentTaskHandle(), arg->data.period, arg->timers_num);
|
||||
|
||||
if (arg->timers_num > 0) {
|
||||
inth = pvPortMalloc(arg->timers_num * sizeof(timer_isr_handle_t));
|
||||
if (!inth) {
|
||||
ESP_APPTRACE_TEST_LOGE("Failed to alloc timer ISR handles!");
|
||||
goto on_fail;
|
||||
}
|
||||
memset(inth, 0, arg->timers_num * sizeof(timer_isr_handle_t));
|
||||
for (int i = 0; i < arg->timers_num; i++) {
|
||||
esp_apptrace_test_timer_init(arg->timers[i].group, arg->timers[i].id, arg->timers[i].data.period);
|
||||
res = timer_isr_register(arg->timers[i].group, arg->timers[i].id, arg->timers[i].isr_func, &arg->timers[i], flags, &inth[i]);
|
||||
if (res != ESP_OK) {
|
||||
ESP_APPTRACE_TEST_LOGE("Failed to timer_isr_register (%d)!", res);
|
||||
goto on_fail;
|
||||
}
|
||||
*(uint32_t *)arg->timers[i].data.buf = (uint32_t)inth[i] | (1 << 31);
|
||||
ESP_APPTRACE_TEST_LOGI("%x: start timer %x period %u us", xTaskGetCurrentTaskHandle(), inth[i], arg->timers[i].data.period);
|
||||
res = timer_start(arg->timers[i].group, arg->timers[i].id);
|
||||
if (res != ESP_OK) {
|
||||
ESP_APPTRACE_TEST_LOGE("Failed to timer_start (%d)!", res);
|
||||
goto on_fail;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i = 0;
|
||||
while (!arg->stop) {
|
||||
ESP_APPTRACE_TEST_LOGD("%x: dummy task work %d.%d", xTaskGetCurrentTaskHandle(), xPortGetCoreID(), i++);
|
||||
if (tmo_ticks) {
|
||||
vTaskDelay(tmo_ticks);
|
||||
}
|
||||
}
|
||||
|
||||
on_fail:
|
||||
if (inth) {
|
||||
for (int i = 0; i < arg->timers_num; i++) {
|
||||
timer_pause(arg->timers[i].group, arg->timers[i].id);
|
||||
timer_disable_intr(arg->timers[i].group, arg->timers[i].id);
|
||||
if (inth[i]) {
|
||||
esp_intr_free(inth[i]);
|
||||
}
|
||||
}
|
||||
vPortFree(inth);
|
||||
}
|
||||
xSemaphoreGive(arg->done);
|
||||
vTaskDelay(1);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static void esp_apptrace_test_task(void *p)
|
||||
{
|
||||
esp_apptrace_test_task_arg_t *arg = (esp_apptrace_test_task_arg_t *) p;
|
||||
int res, flags = 0;
|
||||
timer_isr_handle_t *inth = NULL;
|
||||
TickType_t tmo_ticks = arg->data.period / (1000 * portTICK_PERIOD_MS);
|
||||
|
||||
ESP_APPTRACE_TEST_LOGI("%x: run (period %u us, stamp mask %x, %u timers)", xTaskGetCurrentTaskHandle(), arg->data.period, arg->data.mask, arg->timers_num);
|
||||
|
||||
if (arg->timers_num > 0) {
|
||||
inth = pvPortMalloc(arg->timers_num * sizeof(timer_isr_handle_t));
|
||||
if (!inth) {
|
||||
ESP_APPTRACE_TEST_LOGE("Failed to alloc timer ISR handles!");
|
||||
goto on_fail;
|
||||
}
|
||||
memset(inth, 0, arg->timers_num * sizeof(timer_isr_handle_t));
|
||||
for (int i = 0; i < arg->timers_num; i++) {
|
||||
esp_apptrace_test_timer_init(arg->timers[i].group, arg->timers[i].id, arg->timers[i].data.period);
|
||||
res = timer_isr_register(arg->timers[i].group, arg->timers[i].id, arg->timers[i].isr_func, &arg->timers[i], flags, &inth[i]);
|
||||
if (res != ESP_OK) {
|
||||
ESP_APPTRACE_TEST_LOGE("Failed to timer_isr_register (%d)!", res);
|
||||
goto on_fail;
|
||||
}
|
||||
*(uint32_t *)arg->timers[i].data.buf = ((uint32_t)inth[i]) | (1 << 31) | (xPortGetCoreID() ? 0x1 : 0);
|
||||
ESP_APPTRACE_TEST_LOGI("%x: start timer %x period %u us", xTaskGetCurrentTaskHandle(), inth[i], arg->timers[i].data.period);
|
||||
res = timer_start(arg->timers[i].group, arg->timers[i].id);
|
||||
if (res != ESP_OK) {
|
||||
ESP_APPTRACE_TEST_LOGE("Failed to timer_start (%d)!", res);
|
||||
goto on_fail;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*(uint32_t *)arg->data.buf = (uint32_t)xTaskGetCurrentTaskHandle() | (xPortGetCoreID() ? 0x1 : 0);
|
||||
arg->data.wr_cnt = 0;
|
||||
arg->data.wr_err = 0;
|
||||
while (!arg->stop) {
|
||||
uint32_t *ts = (uint32_t *)(arg->data.buf + sizeof(uint32_t));
|
||||
*ts = (uint32_t)esp_apptrace_test_ts_get();
|
||||
memset(arg->data.buf + 2 * sizeof(uint32_t), arg->data.wr_cnt & arg->data.mask, arg->data.buf_sz - 2 * sizeof(uint32_t));
|
||||
if (arg->nowait) {
|
||||
res = ESP_APPTRACE_TEST_WRITE_NOWAIT(arg->data.buf, arg->data.buf_sz);
|
||||
} else {
|
||||
res = ESP_APPTRACE_TEST_WRITE(arg->data.buf, arg->data.buf_sz);
|
||||
}
|
||||
if (res) {
|
||||
if (arg->data.wr_err++ < ESP_APPTRACE_TEST_PRN_WRERR_MAX) {
|
||||
ESP_APPTRACE_TEST_LOGE("%x: Failed to write trace %d %x!", xTaskGetCurrentTaskHandle(), res, arg->data.wr_cnt & arg->data.mask);
|
||||
if (arg->data.wr_err == ESP_APPTRACE_TEST_PRN_WRERR_MAX) {
|
||||
ESP_APPTRACE_TEST_LOGE("\n");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (0) {
|
||||
ESP_APPTRACE_TEST_LOGD("%x:%x: Written chunk%d %d bytes, %x", xTaskGetCurrentTaskHandle(), *ts, arg->data.wr_cnt, arg->data.buf_sz, arg->data.wr_cnt & arg->data.mask);
|
||||
}
|
||||
arg->data.wr_err = 0;
|
||||
}
|
||||
arg->data.wr_cnt++;
|
||||
if (tmo_ticks) {
|
||||
vTaskDelay(tmo_ticks);
|
||||
}
|
||||
}
|
||||
|
||||
on_fail:
|
||||
if (inth) {
|
||||
for (int i = 0; i < arg->timers_num; i++) {
|
||||
timer_pause(arg->timers[i].group, arg->timers[i].id);
|
||||
timer_disable_intr(arg->timers[i].group, arg->timers[i].id);
|
||||
if (inth[i]) {
|
||||
esp_intr_free(inth[i]);
|
||||
}
|
||||
}
|
||||
vPortFree(inth);
|
||||
}
|
||||
xSemaphoreGive(arg->done);
|
||||
vTaskDelay(1);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static void esp_apptrace_test_task_crash(void *p)
|
||||
{
|
||||
esp_apptrace_test_task_arg_t *arg = (esp_apptrace_test_task_arg_t *) p;
|
||||
int res, i;
|
||||
|
||||
ESP_APPTRACE_TEST_LOGE("%x: run (period %u us, stamp mask %x, %u timers)", xTaskGetCurrentTaskHandle(), arg->data.period, arg->data.mask, arg->timers_num);
|
||||
|
||||
arg->data.wr_cnt = 0;
|
||||
*(uint32_t *)arg->data.buf = (uint32_t)xTaskGetCurrentTaskHandle();
|
||||
for (i = 0; i < ESP_APPTRACE_TEST_BLOCKS_BEFORE_CRASH; i++) {
|
||||
uint32_t *ts = (uint32_t *)(arg->data.buf + sizeof(uint32_t));
|
||||
*ts = (uint32_t)esp_apptrace_test_ts_get();
|
||||
memset(arg->data.buf + sizeof(uint32_t), arg->data.wr_cnt & arg->data.mask, arg->data.buf_sz - sizeof(uint32_t));
|
||||
res = ESP_APPTRACE_TEST_WRITE(arg->data.buf, arg->data.buf_sz);
|
||||
if (res) {
|
||||
ESP_APPTRACE_TEST_LOGE("%x: Failed to write trace %d %x!", xTaskGetCurrentTaskHandle(), res, arg->data.wr_cnt & arg->data.mask);
|
||||
} else {
|
||||
ESP_APPTRACE_TEST_LOGD("%x: Written chunk%d %d bytes, %x", xTaskGetCurrentTaskHandle(), arg->data.wr_cnt, arg->data.buf_sz, arg->data.wr_cnt & arg->data.mask);
|
||||
}
|
||||
arg->data.wr_cnt++;
|
||||
}
|
||||
vTaskDelay(500);
|
||||
uint32_t *ptr = 0;
|
||||
*ptr = 1000;
|
||||
|
||||
xSemaphoreGive(arg->done);
|
||||
vTaskDelay(1);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static int s_ts_timer_group, s_ts_timer_idx;
|
||||
|
||||
static uint64_t esp_apptrace_test_ts_get()
|
||||
{
|
||||
uint64_t ts = 0;
|
||||
timer_get_counter_value(s_ts_timer_group, s_ts_timer_idx, &ts);
|
||||
return ts;
|
||||
}
|
||||
|
||||
static void esp_apptrace_test_ts_init(int timer_group, int timer_idx)
|
||||
{
|
||||
timer_config_t config;
|
||||
//uint64_t alarm_val = period * (TIMER_BASE_CLK / 1000000UL);
|
||||
|
||||
ESP_APPTRACE_TEST_LOGI("Use timer%d.%d for TS", timer_group, timer_idx);
|
||||
|
||||
s_ts_timer_group = timer_group;
|
||||
s_ts_timer_idx = timer_idx;
|
||||
|
||||
config.alarm_en = 0;
|
||||
config.auto_reload = 0;
|
||||
config.counter_dir = TIMER_COUNT_UP;
|
||||
config.divider = 1;
|
||||
config.counter_en = 0;
|
||||
/*Configure timer*/
|
||||
timer_init(timer_group, timer_idx, &config);
|
||||
/*Load counter value */
|
||||
timer_set_counter_value(timer_group, timer_idx, 0x00000000ULL);
|
||||
/*Enable timer interrupt*/
|
||||
timer_start(timer_group, timer_idx);
|
||||
}
|
||||
|
||||
static void esp_apptrace_test_ts_cleanup()
|
||||
{
|
||||
timer_config_t config;
|
||||
|
||||
config.alarm_en = 0;
|
||||
config.auto_reload = 0;
|
||||
config.counter_dir = TIMER_COUNT_UP;
|
||||
config.divider = 1;
|
||||
config.counter_en = 0;
|
||||
/*Configure timer*/
|
||||
timer_init(s_ts_timer_group, s_ts_timer_idx, &config);
|
||||
}
|
||||
|
||||
static void esp_apptrace_test(esp_apptrace_test_cfg_t *test_cfg)
|
||||
{
|
||||
int i, k;
|
||||
int tims_in_use[TIMER_GROUP_MAX][TIMER_MAX] = {{0, 0}, {0, 0}};
|
||||
esp_apptrace_test_task_arg_t dummy_task_arg[1];
|
||||
|
||||
memset(dummy_task_arg, 0, sizeof(dummy_task_arg));
|
||||
dummy_task_arg[0].core = 0;
|
||||
dummy_task_arg[0].prio = 3;
|
||||
dummy_task_arg[0].task_func = esp_apptrace_test_task_crash;
|
||||
dummy_task_arg[0].data.buf = NULL;
|
||||
dummy_task_arg[0].data.buf_sz = 0;
|
||||
dummy_task_arg[0].data.period = 500000;
|
||||
dummy_task_arg[0].timers_num = 0;
|
||||
dummy_task_arg[0].timers = NULL;
|
||||
#if ESP_APPTRACE_TEST_USE_PRINT_LOCK == 1
|
||||
s_print_lock = xSemaphoreCreateBinary();
|
||||
if (!s_print_lock) {
|
||||
ets_printf("%s: Failed to create print lock!", TAG);
|
||||
return;
|
||||
}
|
||||
xSemaphoreGive(s_print_lock);
|
||||
#else
|
||||
#endif
|
||||
|
||||
for (i = 0; i < test_cfg->tasks_num; i++) {
|
||||
test_cfg->tasks[i].data.mask = 0xFF;
|
||||
test_cfg->tasks[i].stop = 0;
|
||||
test_cfg->tasks[i].done = xSemaphoreCreateBinary();
|
||||
if (!test_cfg->tasks[i].done) {
|
||||
ESP_APPTRACE_TEST_LOGE("Failed to create task completion semaphore!");
|
||||
goto on_fail;
|
||||
}
|
||||
for (k = 0; k < test_cfg->tasks[i].timers_num; k++) {
|
||||
test_cfg->tasks[i].timers[k].data.mask = 0xFF;
|
||||
tims_in_use[test_cfg->tasks[i].timers[k].group][test_cfg->tasks[i].timers[k].id] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
int found = 0;
|
||||
for (i = 0; i < TIMER_GROUP_MAX; i++) {
|
||||
for (k = 0; k < TIMER_MAX; k++) {
|
||||
if (!tims_in_use[i][k]) {
|
||||
ESP_APPTRACE_TEST_LOGD("Found timer%d.%d", i, k);
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
ESP_APPTRACE_TEST_LOGE("No free timer for TS!");
|
||||
goto on_fail;
|
||||
}
|
||||
esp_apptrace_test_ts_init(i, k);
|
||||
|
||||
for (int i = 0; i < test_cfg->tasks_num; i++) {
|
||||
char name[30];
|
||||
TaskHandle_t thnd;
|
||||
sprintf(name, "apptrace_test%d", i);
|
||||
xTaskCreatePinnedToCore(test_cfg->tasks[i].task_func, name, 2048, &test_cfg->tasks[i], test_cfg->tasks[i].prio, &thnd, test_cfg->tasks[i].core);
|
||||
ESP_APPTRACE_TEST_LOGI("Created task %x", thnd);
|
||||
}
|
||||
xTaskCreatePinnedToCore(esp_apptrace_dummy_task, "dummy0", 2048, &dummy_task_arg[0], dummy_task_arg[0].prio, NULL, 0);
|
||||
xTaskCreatePinnedToCore(esp_apptrace_dummy_task, "dummy1", 2048, &dummy_task_arg[0], dummy_task_arg[0].prio, NULL, 1);
|
||||
|
||||
for (int i = 0; i < test_cfg->tasks_num; i++) {
|
||||
//arg1.stop = 1;
|
||||
xSemaphoreTake(test_cfg->tasks[i].done, portMAX_DELAY);
|
||||
}
|
||||
|
||||
on_fail:
|
||||
for (int i = 0; i < test_cfg->tasks_num; i++) {
|
||||
if (test_cfg->tasks[i].done) {
|
||||
vSemaphoreDelete(test_cfg->tasks[i].done);
|
||||
}
|
||||
}
|
||||
esp_apptrace_test_ts_cleanup();
|
||||
|
||||
#if ESP_APPTRACE_TEST_USE_PRINT_LOCK == 1
|
||||
vSemaphoreDelete(s_print_lock);
|
||||
#else
|
||||
#endif
|
||||
}
|
||||
|
||||
static esp_apptrace_test_task_arg_t s_test_tasks[4];
|
||||
static esp_apptrace_test_timer_arg_t s_test_timers[2];
|
||||
static uint8_t s_bufs[6][ESP_APPTRACE_TEST_BLOCK_SIZE];
|
||||
|
||||
TEST_CASE("App trace test (1 task + 1 crashed timer ISR @ 1 core)", "[trace][ignore]")
|
||||
{
|
||||
esp_apptrace_test_cfg_t test_cfg = {
|
||||
.tasks_num = 1,
|
||||
.tasks = s_test_tasks,
|
||||
};
|
||||
|
||||
memset(s_test_timers, 0, sizeof(s_test_timers));
|
||||
memset(s_test_tasks, 0, sizeof(s_test_tasks));
|
||||
|
||||
s_test_timers[0].group = TIMER_GROUP_0;
|
||||
s_test_timers[0].id = TIMER_0;
|
||||
s_test_timers[0].isr_func = esp_apptrace_test_timer_isr_crash;
|
||||
s_test_timers[0].data.buf = s_bufs[0];
|
||||
s_test_timers[0].data.buf_sz = sizeof(s_bufs[0]);
|
||||
s_test_timers[0].data.period = 1000;
|
||||
|
||||
s_test_tasks[0].core = 0;
|
||||
s_test_tasks[0].prio = 3;
|
||||
s_test_tasks[0].task_func = esp_apptrace_dummy_task;
|
||||
s_test_tasks[0].data.buf = NULL;
|
||||
s_test_tasks[0].data.buf_sz = 0;
|
||||
s_test_tasks[0].data.period = 1000000;
|
||||
s_test_tasks[0].timers_num = 1;
|
||||
s_test_tasks[0].timers = s_test_timers;
|
||||
|
||||
esp_apptrace_test(&test_cfg);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("App trace test (1 crashed task)", "[trace][ignore]")
|
||||
{
|
||||
esp_apptrace_test_task_arg_t s_test_tasks[1];
|
||||
esp_apptrace_test_cfg_t test_cfg = {
|
||||
.tasks_num = 1,
|
||||
.tasks = s_test_tasks,
|
||||
};
|
||||
|
||||
memset(s_test_tasks, 0, sizeof(s_test_tasks));
|
||||
|
||||
s_test_tasks[0].core = 0;
|
||||
s_test_tasks[0].prio = 3;
|
||||
s_test_tasks[0].task_func = esp_apptrace_test_task_crash;
|
||||
s_test_tasks[0].data.buf = s_bufs[0];
|
||||
s_test_tasks[0].data.buf_sz = sizeof(s_bufs[0]);
|
||||
s_test_tasks[0].data.period = 6000;
|
||||
s_test_tasks[0].timers_num = 0;
|
||||
s_test_tasks[0].timers = NULL;
|
||||
|
||||
esp_apptrace_test(&test_cfg);
|
||||
}
|
||||
|
||||
TEST_CASE("App trace test (2 tasks + 1 timer @ each core", "[trace][ignore]")
|
||||
{
|
||||
int ntask = 0;
|
||||
esp_apptrace_test_cfg_t test_cfg = {
|
||||
.tasks_num = 4,
|
||||
.tasks = s_test_tasks,
|
||||
};
|
||||
|
||||
memset(s_test_tasks, 0, sizeof(s_test_tasks));
|
||||
memset(s_test_timers, 0, sizeof(s_test_timers));
|
||||
|
||||
s_test_timers[0].group = TIMER_GROUP_0;
|
||||
s_test_timers[0].id = TIMER_0;
|
||||
s_test_timers[0].isr_func = esp_apptrace_test_timer_isr;
|
||||
s_test_timers[0].data.buf = s_bufs[0];
|
||||
s_test_timers[0].data.buf_sz = sizeof(s_bufs[0]);
|
||||
s_test_timers[0].data.period = 150;
|
||||
|
||||
s_test_timers[1].group = TIMER_GROUP_1;
|
||||
s_test_timers[1].id = TIMER_0;
|
||||
s_test_timers[1].isr_func = esp_apptrace_test_timer_isr;
|
||||
s_test_timers[1].data.buf = s_bufs[1];
|
||||
s_test_timers[1].data.buf_sz = sizeof(s_bufs[1]);
|
||||
s_test_timers[1].data.period = 150;
|
||||
|
||||
s_test_tasks[ntask].core = 0;
|
||||
s_test_tasks[ntask].prio = 4;
|
||||
s_test_tasks[ntask].task_func = esp_apptrace_test_task;
|
||||
s_test_tasks[ntask].data.buf = s_bufs[2];
|
||||
s_test_tasks[ntask].data.buf_sz = sizeof(s_bufs[2]);
|
||||
s_test_tasks[ntask].data.period = 1000;
|
||||
s_test_tasks[ntask].timers_num = 1;
|
||||
s_test_tasks[ntask].timers = &s_test_timers[0];
|
||||
ntask++;
|
||||
s_test_tasks[ntask].core = 0;
|
||||
s_test_tasks[ntask].prio = 3;
|
||||
s_test_tasks[ntask].task_func = esp_apptrace_test_task;
|
||||
s_test_tasks[ntask].data.buf = s_bufs[3];
|
||||
s_test_tasks[ntask].data.buf_sz = sizeof(s_bufs[3]);
|
||||
s_test_tasks[ntask].data.period = 0;
|
||||
s_test_tasks[ntask].timers_num = 0;
|
||||
s_test_tasks[ntask].timers = NULL;
|
||||
ntask++;
|
||||
s_test_tasks[ntask].core = 1;
|
||||
s_test_tasks[ntask].prio = 4;
|
||||
s_test_tasks[ntask].task_func = esp_apptrace_test_task;
|
||||
s_test_tasks[ntask].data.buf = s_bufs[4];
|
||||
s_test_tasks[ntask].data.buf_sz = sizeof(s_bufs[4]);
|
||||
s_test_tasks[ntask].data.period = 1000;
|
||||
s_test_tasks[ntask].timers_num = 1;
|
||||
s_test_tasks[ntask].timers = &s_test_timers[1];
|
||||
ntask++;
|
||||
s_test_tasks[ntask].core = 1;
|
||||
s_test_tasks[ntask].prio = 3;
|
||||
s_test_tasks[ntask].task_func = esp_apptrace_test_task;
|
||||
s_test_tasks[ntask].data.buf = s_bufs[5];
|
||||
s_test_tasks[ntask].data.buf_sz = sizeof(s_bufs[5]);
|
||||
s_test_tasks[ntask].data.period = 0;
|
||||
s_test_tasks[ntask].timers_num = 0;
|
||||
s_test_tasks[ntask].timers = NULL;
|
||||
ntask++;
|
||||
|
||||
esp_apptrace_test(&test_cfg);
|
||||
}
|
||||
|
||||
TEST_CASE("App trace test (1 task + 1 timer @ 1 core)", "[trace][ignore]")
|
||||
{
|
||||
esp_apptrace_test_cfg_t test_cfg = {
|
||||
.tasks_num = 1,
|
||||
.tasks = s_test_tasks,
|
||||
};
|
||||
|
||||
memset(s_test_timers, 0, sizeof(s_test_timers));
|
||||
memset(s_test_tasks, 0, sizeof(s_test_tasks));
|
||||
|
||||
s_test_timers[0].group = TIMER_GROUP_0;
|
||||
s_test_timers[0].id = TIMER_0;
|
||||
s_test_timers[0].isr_func = esp_apptrace_test_timer_isr;
|
||||
s_test_timers[0].data.buf = s_bufs[0];
|
||||
s_test_timers[0].data.buf_sz = sizeof(s_bufs[0]);
|
||||
s_test_timers[0].data.period = 150;
|
||||
|
||||
s_test_tasks[0].core = 0;
|
||||
s_test_tasks[0].prio = 3;
|
||||
s_test_tasks[0].task_func = esp_apptrace_test_task;
|
||||
s_test_tasks[0].data.buf = s_bufs[1];
|
||||
s_test_tasks[0].data.buf_sz = sizeof(s_bufs[1]);
|
||||
s_test_tasks[0].data.period = 0;
|
||||
s_test_tasks[0].timers_num = 1;
|
||||
s_test_tasks[0].timers = s_test_timers;
|
||||
|
||||
esp_apptrace_test(&test_cfg);
|
||||
}
|
||||
|
||||
TEST_CASE("App trace test (2 tasks (nowait): 1 @ each core)", "[trace][ignore]")
|
||||
{
|
||||
esp_apptrace_test_cfg_t test_cfg = {
|
||||
.tasks_num = 2,
|
||||
.tasks = s_test_tasks,
|
||||
};
|
||||
|
||||
memset(s_test_tasks, 0, sizeof(s_test_tasks));
|
||||
|
||||
s_test_tasks[0].nowait = 1;
|
||||
s_test_tasks[0].core = 0;
|
||||
s_test_tasks[0].prio = 3;
|
||||
s_test_tasks[0].task_func = esp_apptrace_test_task;
|
||||
s_test_tasks[0].data.buf = s_bufs[0];
|
||||
s_test_tasks[0].data.buf_sz = sizeof(s_bufs[0]);
|
||||
s_test_tasks[0].data.period = 6700;
|
||||
s_test_tasks[0].timers_num = 0;
|
||||
s_test_tasks[0].timers = NULL;
|
||||
|
||||
s_test_tasks[1].nowait = 1;
|
||||
s_test_tasks[1].core = 1;
|
||||
s_test_tasks[1].prio = 3;
|
||||
s_test_tasks[1].task_func = esp_apptrace_test_task;
|
||||
s_test_tasks[1].data.buf = s_bufs[1];
|
||||
s_test_tasks[1].data.buf_sz = sizeof(s_bufs[1]);
|
||||
s_test_tasks[1].data.period = 6700;
|
||||
s_test_tasks[1].timers_num = 0;
|
||||
s_test_tasks[1].timers = NULL;
|
||||
|
||||
esp_apptrace_test(&test_cfg);
|
||||
}
|
||||
|
||||
TEST_CASE("App trace test (2 tasks: 1 @ each core)", "[trace][ignore]")
|
||||
{
|
||||
esp_apptrace_test_cfg_t test_cfg = {
|
||||
.tasks_num = 2,
|
||||
.tasks = s_test_tasks,
|
||||
};
|
||||
|
||||
memset(s_test_tasks, 0, sizeof(s_test_tasks));
|
||||
|
||||
s_test_tasks[0].core = 0;
|
||||
s_test_tasks[0].prio = 3;
|
||||
s_test_tasks[0].task_func = esp_apptrace_test_task;
|
||||
s_test_tasks[0].data.buf = s_bufs[0];
|
||||
s_test_tasks[0].data.buf_sz = sizeof(s_bufs[0]);
|
||||
s_test_tasks[0].data.period = 0;
|
||||
s_test_tasks[0].timers_num = 0;
|
||||
s_test_tasks[0].timers = NULL;
|
||||
|
||||
s_test_tasks[1].core = 1;
|
||||
s_test_tasks[1].prio = 3;
|
||||
s_test_tasks[1].task_func = esp_apptrace_test_task;
|
||||
s_test_tasks[1].data.buf = s_bufs[1];
|
||||
s_test_tasks[1].data.buf_sz = sizeof(s_bufs[1]);
|
||||
s_test_tasks[1].data.period = 0;
|
||||
s_test_tasks[1].timers_num = 0;
|
||||
s_test_tasks[1].timers = NULL;
|
||||
|
||||
esp_apptrace_test(&test_cfg);
|
||||
}
|
||||
|
||||
TEST_CASE("App trace test (1 task)", "[trace][ignore]")
|
||||
{
|
||||
esp_apptrace_test_cfg_t test_cfg = {
|
||||
.tasks_num = 1,
|
||||
.tasks = s_test_tasks,
|
||||
};
|
||||
|
||||
memset(s_test_tasks, 0, sizeof(s_test_tasks));
|
||||
|
||||
s_test_tasks[0].core = 1;
|
||||
s_test_tasks[0].prio = 3;
|
||||
s_test_tasks[0].task_func = esp_apptrace_test_task;
|
||||
s_test_tasks[0].data.buf = s_bufs[0];
|
||||
s_test_tasks[0].data.buf_sz = sizeof(s_bufs[0]);
|
||||
s_test_tasks[0].data.period = 0;
|
||||
s_test_tasks[0].timers_num = 0;
|
||||
s_test_tasks[0].timers = NULL;
|
||||
|
||||
esp_apptrace_test(&test_cfg);
|
||||
}
|
||||
|
||||
static int esp_logtrace_printf(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
|
||||
int ret = esp_apptrace_vprintf_to(ESP_APPTRACE_DEST_TRAX, ESP_APPTRACE_TMO_INFINITE, fmt, ap);
|
||||
|
||||
va_end(ap);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
SemaphoreHandle_t done;
|
||||
} esp_logtrace_task_t;
|
||||
|
||||
static void esp_logtrace_task(void *p)
|
||||
{
|
||||
esp_logtrace_task_t *arg = (esp_logtrace_task_t *) p;
|
||||
|
||||
ESP_APPTRACE_TEST_LOGI("%x: run log test task", xTaskGetCurrentTaskHandle());
|
||||
|
||||
int i = 0;
|
||||
while (1) {
|
||||
esp_logtrace_printf("sample print %lx %hx %c\n", 2 * i + 0x10, 2 * i + 0x20, (2 * i + 0x30) & 0xFF);
|
||||
esp_logtrace_printf("sample print %lx %hx %c %lu %hu %d %d %d %d\n", i, i + 0x10, (i + 0x20) & 0xFF, i + 0x30, i + 0x40, i + 0x50, i + 0x60, i + 0x70, i + 0x80);
|
||||
ESP_LOGI(TAG, "%p: sample print 1", xTaskGetCurrentTaskHandle());
|
||||
ESP_LOGI(TAG, "%p: sample print 2 %u", xTaskGetCurrentTaskHandle(), (unsigned)i);
|
||||
ESP_LOGI(TAG, "%p: sample print 4 %c", xTaskGetCurrentTaskHandle(), ((i & 0xFF) % 95) + 32);
|
||||
ESP_LOGI(TAG, "%p: sample print 5 %f", xTaskGetCurrentTaskHandle(), 1.0);
|
||||
ESP_LOGI(TAG, "%p: sample print 6 %f", xTaskGetCurrentTaskHandle(), 3.45);
|
||||
ESP_LOGI(TAG, "%p: logtrace task work %d.%d", xTaskGetCurrentTaskHandle(), xPortGetCoreID(), i);
|
||||
if (++i == 10000) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
esp_err_t ret = esp_apptrace_flush(ESP_APPTRACE_DEST_TRAX, ESP_APPTRACE_TMO_INFINITE);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_APPTRACE_TEST_LOGE("Failed to flush printf buf (%d)!", ret);
|
||||
}
|
||||
|
||||
ESP_APPTRACE_TEST_LOGI("%x: finished", xTaskGetCurrentTaskHandle());
|
||||
|
||||
xSemaphoreGive(arg->done);
|
||||
vTaskDelay(1);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
TEST_CASE("Log trace test (1 task)", "[trace][ignore]")
|
||||
{
|
||||
TaskHandle_t thnd;
|
||||
|
||||
esp_logtrace_task_t arg1 = {
|
||||
.done = xSemaphoreCreateBinary(),
|
||||
};
|
||||
esp_logtrace_task_t arg2 = {
|
||||
.done = xSemaphoreCreateBinary(),
|
||||
};
|
||||
|
||||
xTaskCreatePinnedToCore(esp_logtrace_task, "logtrace0", 2048, &arg1, 3, &thnd, 0);
|
||||
ESP_APPTRACE_TEST_LOGI("Created task %x", thnd);
|
||||
xTaskCreatePinnedToCore(esp_logtrace_task, "logtrace1", 2048, &arg2, 3, &thnd, 1);
|
||||
ESP_APPTRACE_TEST_LOGI("Created task %x", thnd);
|
||||
|
||||
xSemaphoreTake(arg1.done, portMAX_DELAY);
|
||||
vSemaphoreDelete(arg1.done);
|
||||
xSemaphoreTake(arg2.done, portMAX_DELAY);
|
||||
vSemaphoreDelete(arg2.done);
|
||||
}
|
||||
#endif
|
Reference in New Issue
Block a user