OVMS3/OVMS.V3/main/ovms_module.cpp

1100 lines
32 KiB
C++

/*
; Project: Open Vehicle Monitor System
; Date: 14th March 2017
;
; Changes:
; 1.0 Initial release
;
; (C) 2011 Michael Stegen / Stegen Electronics
; (C) 2011-2017 Mark Webb-Johnson
; (C) 2011 Sonny Chen @ EPRO/DX
;
; Permission is hereby granted, free of charge, to any person obtaining a copy
; of this software and associated documentation files (the "Software"), to deal
; in the Software without restriction, including without limitation the rights
; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
; copies of the Software, and to permit persons to whom the Software is
; furnished to do so, subject to the following conditions:
;
; The above copyright notice and this permission notice shall be included in
; all copies or substantial portions of the Software.
;
; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
; THE SOFTWARE.
*/
#include "ovms_log.h"
static const char *TAG = "module";
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/FreeRTOSConfig.h"
#include "esp_heap_caps.h"
#include <esp_system.h>
#include "ovms_module.h"
#include "ovms_peripherals.h"
#include "ovms_events.h"
#include "ovms_config.h"
#include "ovms_command.h"
#include "metrics_standard.h"
#ifdef CONFIG_HEAP_TASK_TRACKING
#include "esp_heap_task_info.h"
#endif
#include "ovms_boot.h"
#include "ovms_mutex.h"
#include "ovms_notify.h"
#include "string_writer.h"
#define MAX_TASKS 30
#define DUMPSIZE 1000
#define NUMTASKS 32
#define NAMELEN 16
#define TASKLIST 10
#define NOT_FOUND (TaskHandle_t)0xFFFFFFFF
#ifndef CONFIG_HEAP_TASK_TRACKING
#define NOGO 1
#endif
#if configUSE_TRACE_FACILITY==0
#define NOGO 1
#endif
#ifdef CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION
#define NOGO 1
#endif
#ifdef NOGO
static void must(OvmsWriter* writer)
{
writer->printf("To use these debugging tools, must set CONFIG_HEAP_TASK_TRACKING=y\n");
writer->printf("and have updated openvehicles/esp-idf\n");
}
static void module_memory(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
size_t free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL);
size_t free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT|MALLOC_CAP_INTERNAL);
size_t lgst_8bit = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL);
size_t free_spiram = heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
size_t lgst_spiram = heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM);
writer->printf("Free RAM: 8bit=%zu-%zu 32bit=%zu SPIRAM=%zu-%zu bytes\n\n",
lgst_8bit, free_8bit, free_32bit-free_8bit, lgst_spiram, free_spiram);
must(writer);
}
static void module_tasks(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
must(writer);
}
static void module_tasks_data(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
must(writer);
}
static void module_check(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
must(writer);
}
void AddTaskToMap(TaskHandle_t task) {}
#else
enum region_types {
DRAM = 0,
D_IRAM = 1,
IRAM = 2,
SPIRAM = 3,
NUM_REGIONS = 4
};
#if NUM_HEAP_TASK_CAPS < NUM_REGIONS
#error NUM_HEAP_TASK_CAPS must be >= NUM_REGIONS
#endif
class HeapTotals;
static heap_task_info_params_t* params = NULL;
static TaskHandle_t* tasklist = NULL;
static TaskStatus_t* taskstatus = NULL;
static uint32_t taskstatus_cnt = 0;
static uint32_t taskstatus_time = 0;
static uint32_t totalruntime = 0;
static OvmsMutex taskstatus_mutex;
static heap_task_block_t* before = NULL;
static heap_task_block_t* after = NULL;
static size_t numbefore = 0, numafter = 0;
static HeapTotals* changes = NULL;
static const char* states[] = {"Run", "Rdy", "Blk", "Sus", "Del"};
class Name
{
public:
inline Name() {}
inline Name(const char *name)
{
for (int i = 0; i < NAMELEN/4; ++i)
words[i] = 0;
strncpy(bytes, name, NAMELEN-1);
}
inline Name(const Name& name)
{
for (int i = 0; i < NAMELEN/4; ++i)
words[i] = name.words[i];
bytes[NAMELEN-1] = '\0';
}
inline bool operator==(Name& a)
{
for (int i = 0; i < NAMELEN/4 - 1; ++i)
if (a.words[i] != words[i]) return false;
if (a.words[NAMELEN/4-1] != (words[NAMELEN/4-1] & 0x7FFFFFFF)) return false;
return true;
}
inline char operator[](int i) { return (words[i/4] >> ((i & 3) * 8)) & 0xFF; }
inline void set(int i, char c)
{
words[i/4] &= ~(0xFF << ((i & 3) * 8));
words[i/4] |= c << ((i & 3) * 8);
}
public:
union
{
char bytes[NAMELEN];
int words[NAMELEN/4];
};
};
class TaskMap
{
private:
TaskMap() : count(0) {}
typedef struct
{
TaskHandle_t id;
Name name;
} TaskPair;
public:
inline static TaskMap* instance()
{
if (!taskmap)
{
void* p = heap_caps_malloc(sizeof(TaskMap), MALLOC_CAP_32BIT);
if (!p)
return NULL;
taskmap = new(p) TaskMap;
}
return taskmap;
}
bool insert(TaskHandle_t taskid, const char* name)
{
int i;
for (i = 0; i < count; ++i)
{
if (map[i].id == taskid)
break;
}
if (i == count)
{
if (count == NUMTASKS)
return false;
++count;
}
map[i].id = taskid;
map[i].name = Name(name);
return true;
}
bool find(TaskHandle_t taskid, Name& name)
{
for (int i = 0; i < count; ++i)
{
if (map[i].id == taskid)
{
name = map[i].name;
return true;
}
}
sprintf(name.bytes, "%08X*", (unsigned int)taskid);
name.bytes[NAMELEN-1] = 0x80;
return false;
}
TaskHandle_t find(Name& name)
{
for (int i = 0; i < count; ++i)
{
if (map[i].name == name)
return map[i].id;
}
char* end;
TaskHandle_t task = (TaskHandle_t)strtoul(name.bytes, &end, 16);
if (*end == '\0')
return task;
return NOT_FOUND;
}
UBaseType_t populate()
{
for (int i = 0; i < count; ++i)
{
int j = 0;
for ( ; j < NAMELEN-2; ++j)
{
if (map[i].name[j] == '*' || map[i].name[j] == '\0')
break;
}
map[i].name.set(j++, '*');
map[i].name.set(j, '\0');
map[i].name.words[NAMELEN/4-1] |= 0x80000000;
}
taskstatus_cnt = uxTaskGetSystemState(taskstatus, MAX_TASKS, &totalruntime);
taskstatus_time = xTaskGetTickCount() / configTICK_RATE_HZ;
for (UBaseType_t i = 0; i < taskstatus_cnt; ++i)
{
insert(taskstatus[i].xHandle, taskstatus[i].pcTaskName);
}
return taskstatus_cnt;
}
bool zero(TaskHandle_t taskid)
{
for (int i = 0; i < count; ++i)
{
if (map[i].id == taskid)
{
if (map[i].name.words[NAMELEN/4-1] > 0)
return false;
for (++i ; i < count; ++i)
{
map[i-1] = map[i];
}
--count;
return true;
}
}
return false;
}
void dump()
{
for (int i = 0; i < count; ++i)
{
Name name = map[i].name;
::printf("taskmap %d %p %.15s\n", i, map[i].id, name.bytes);
}
}
private:
static TaskMap* taskmap;
int count;
TaskPair map[NUMTASKS];
};
TaskMap* TaskMap::taskmap = NULL;
class FreeHeap
{
public:
inline FreeHeap()
{
m_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL);
m_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT|MALLOC_CAP_INTERNAL) - m_free_8bit;
m_free_spi = heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
if (!m_total_8bit)
{
m_total_8bit = TotalSize(MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL);
m_total_32bit = TotalSize(MALLOC_CAP_32BIT|MALLOC_CAP_INTERNAL) - FreeHeap::m_total_8bit;
m_total_spi = TotalSize(MALLOC_CAP_SPIRAM);
}
}
size_t Free8bit() { return m_free_8bit; }
size_t Free32bit() { return m_free_32bit; }
size_t FreeSPI() { return m_free_spi; }
size_t Total8bit() { return m_total_8bit; }
size_t Total32bit() { return m_total_32bit; }
size_t TotalSPI() { return m_total_spi; }
private:
size_t TotalSize(uint32_t caps)
{
multi_heap_info_t info;
heap_caps_get_info(&info, caps);
size_t total = info.total_free_bytes + info.total_allocated_bytes +
info.allocated_blocks*20 + info.free_blocks*4;
return total;
}
private:
size_t m_free_8bit;
size_t m_free_32bit;
size_t m_free_spi;
static size_t m_total_8bit;
static size_t m_total_32bit;
static size_t m_total_spi;
};
size_t FreeHeap::m_total_8bit = 0;
size_t FreeHeap::m_total_32bit = 0;
size_t FreeHeap::m_total_spi = 0;
class HeapTask
{
public:
HeapTask()
{
totals.task = 0;
for (int i = 0; i < NUM_HEAP_TASK_CAPS; ++i)
totals.size[i] = 0;
}
heap_task_totals_t totals;
};
class HeapTotals
{
public:
HeapTotals() : count(0) {}
int begin() { return 0; }
int end() { return count; }
heap_task_totals_t* array() { return &after[0].totals; }
size_t* size() { return (size_t*)&count; }
TaskHandle_t Task(int task) { return after[task].totals.task; }
int Before(int task, int type) { return before[task].totals.size[type]; }
int After(int task, int type) { return after[task].totals.size[type]; }
void transfer()
{
TaskMap* tm = TaskMap::instance();
int k = 0;
for (int i = 0; i < count; ++i)
{
if (tm && After(i, DRAM) == 0 && After(i, D_IRAM) == 0 &&
After(i, IRAM) == 0 && After(i, SPIRAM) == 0)
{
tm->zero(Task(i));
continue;
}
before[k].totals.task = after[i].totals.task;
for (int j = 0; j < NUM_HEAP_TASK_CAPS; ++j)
before[k].totals.size[j] = after[i].totals.size[j];
++k;
}
count = k;
}
int find(TaskHandle_t task)
{
for (int i = 0; i < count; ++i)
if (task == after[i].totals.task)
return i;
return -1;
}
void append(HeapTask& t)
{
if (count < NUMTASKS)
{
after[count] = t;
++count;
}
}
private:
HeapTask before[NUMTASKS];
HeapTask after[NUMTASKS];
int count;
};
void AddTaskToMap(TaskHandle_t task)
{
TaskMap::instance()->insert(task, pcTaskGetTaskName(task));
}
static void print_blocks(OvmsWriter* writer, TaskHandle_t task, bool leaks)
{
int count = 0, total = 0, size = 0;;
bool separate = false;
Name name;
TaskMap* tm = TaskMap::instance();
char pbuf[32];
tm->find(task, name);
for (int i = 0; i < numbefore; ++i)
{
if (before[i].task != task)
continue;
int j = 0;
for ( ; j < numafter; ++j)
if (before[i].address == after[j].address && before[i].size == after[j].size)
break;
if (j == numafter)
{
writer->printf("- t=%.15s s=%4d a=%p\n", name.bytes,
before[i].size, before[i].address);
++count;
}
}
if (count)
separate = true;
total += count;
size = 0;
count = 0;
for (int i = 0; i < numafter; ++i)
{
if (after[i].task != task)
continue;
int j = 0;
for ( ; j < numbefore; ++j)
if (after[i].address == before[j].address && after[i].size == before[j].size)
break;
if (j < numbefore)
{
int* p = (int*)after[i].address;
if (separate)
{
writer->printf("----------------------------\n");
separate = false;
}
if (!leaks)
{
for (int pi=0; pi<32; pi++)
pbuf[pi] = isprint(((char*)p)[pi]) ? ((char*)p)[pi] : '.';
writer->printf(" t=%.15s s=%4d a=%p %08X %08X %08X %08X %08X %08X %08X %08X | %-32.32s\n",
name.bytes, after[i].size, p, p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], pbuf);
}
size += after[i].size;
++count;
}
}
if (count)
{
separate = true;
if (leaks)
writer->printf(" t=%.15s s=%4d in %d blocks still allocated\n", name.bytes, size, count);
}
total += count;
size = 0;
count = 0;
for (int i = 0; i < numafter; ++i)
{
if (after[i].task != task)
continue;
int j = 0;
for ( ; j < numbefore; ++j)
if (after[i].address == before[j].address && after[i].size == before[j].size)
break;
if (j == numbefore)
{
int* p = (int*)after[i].address;
if (separate)
{
writer->printf("----------------------------\n");
separate = false;
}
if (numbefore > 0 || !leaks)
{
for (int pi=0; pi<32; pi++)
pbuf[pi] = isprint(((char*)p)[pi]) ? ((char*)p)[pi] : '.';
writer->printf("+ t=%.15s s=%4d a=%p %08X %08X %08X %08X %08X %08X %08X %08X | %-32.32s\n",
name.bytes, after[i].size, p, p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], pbuf);
}
size += after[i].size;
++count;
}
}
if (numbefore == 0 && leaks && count)
writer->printf(" t=%.15s s=%d in %d blocks allocated\n", name.bytes, size, count);
total += count;
if (total)
writer->printf("============================\n");
}
static bool allocate()
{
if (!before)
{
before = (heap_task_block_t*)heap_caps_malloc(sizeof(heap_task_block_t)*DUMPSIZE, MALLOC_CAP_32BIT);
after = (heap_task_block_t*)heap_caps_malloc(sizeof(heap_task_block_t)*DUMPSIZE, MALLOC_CAP_32BIT);
taskstatus = (TaskStatus_t*)heap_caps_malloc(sizeof(TaskStatus_t)*MAX_TASKS, MALLOC_CAP_32BIT);
tasklist = (TaskHandle_t*)heap_caps_malloc(sizeof(TaskHandle_t)*TASKLIST, MALLOC_CAP_32BIT);
params = (heap_task_info_params_t*)heap_caps_malloc(sizeof(heap_task_info_params_t), MALLOC_CAP_32BIT);
void* p = heap_caps_malloc(sizeof(HeapTotals), MALLOC_CAP_32BIT);
changes = new(p) HeapTotals();
if (!before || !after || !taskstatus || !tasklist || !params || !changes)
{
if (before)
free(before);
if (after)
free(after);
if (taskstatus)
free(taskstatus);
if (tasklist)
free(tasklist);
if (params)
free(params);
if (changes)
delete(changes);
return false;
}
}
return true;
}
static UBaseType_t get_tasks()
{
TaskMap* tm = TaskMap::instance();
UBaseType_t numtasks = 0;
if (tm)
numtasks = tm->populate();
return numtasks;
}
static void get_memory(TaskHandle_t* tasks, size_t taskslen)
{
params->mask[DRAM] = MALLOC_CAP_8BIT | MALLOC_CAP_EXEC | MALLOC_CAP_INTERNAL;
params->caps[DRAM] = MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL;
params->mask[D_IRAM] = MALLOC_CAP_8BIT | MALLOC_CAP_EXEC | MALLOC_CAP_INTERNAL;
params->caps[D_IRAM] = MALLOC_CAP_8BIT | MALLOC_CAP_EXEC | MALLOC_CAP_INTERNAL;
params->mask[IRAM] = MALLOC_CAP_8BIT | MALLOC_CAP_EXEC | MALLOC_CAP_INTERNAL;
params->caps[IRAM] = MALLOC_CAP_EXEC | MALLOC_CAP_INTERNAL;
params->mask[SPIRAM] = MALLOC_CAP_SPIRAM;
params->caps[SPIRAM] = MALLOC_CAP_SPIRAM;
params->tasks = tasks;
params->num_tasks = taskslen;
params->totals = changes->array();
params->num_totals = changes->size();
params->max_totals = NUMTASKS;
params->blocks = after;
params->max_blocks = DUMPSIZE;
numafter = heap_caps_get_per_task_info(params);
}
static void module_memory(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
OvmsMutexLock lock(&taskstatus_mutex);
static const char* ctasks[] = {}; // { "tiT", "wifi"};
const char* const* tasks = ctasks;
bool all = false;
bool leaks = *(cmd->GetName()) == 'l';
if (argc > 0)
{
if (**argv == '*')
tasks = NULL;
else if (**argv == '=')
{
all = true;
argc = sizeof(ctasks) / sizeof(char*);
}
else
tasks = argv;
}
else
argc = sizeof(ctasks) / sizeof(char*);
if (!allocate())
{
writer->printf("Can't allocate storage for memory diagnostics\n");
return;
}
UBaseType_t n = get_tasks();
TaskMap* tm = TaskMap::instance();
TaskHandle_t* tl = NULL;
size_t tln = 0;
if (tasks)
{
tl = tasklist;
for ( ; argc > 0; --argc, ++tasks)
{
Name name(*tasks);
TaskHandle_t tk = tm->find(name);
if (tk != NOT_FOUND)
{
*tl++ = tk;
if (++tln == TASKLIST)
break;
}
else
writer->printf("Task %s unknown\n", *tasks);
}
tl = tasklist;
}
get_memory(tl, tln);
FreeHeap now;
writer->printf("Free 8-bit %zu/%zu, 32-bit %zu/%zu, SPIRAM %zu/%zu\n",
now.Free8bit(), now.Total8bit(), now.Free32bit(), now.Total32bit(),
now.FreeSPI(), now.TotalSPI());
bool first = true;
UBaseType_t num = 0;
for (int done = changes->begin(); done < changes->end(); ++num)
{
TaskHandle_t taskid = 0;
if (num != 0)
{
UBaseType_t t;
for (t = 0; t < n; ++t)
if (taskstatus[t].xTaskNumber == num)
break;
if (t == n)
continue;
taskid = taskstatus[t].xHandle;
}
for (int i = changes->begin(); i < changes->end(); ++i)
{
if (num == 0)
{
UBaseType_t t;
for (t = 0; t < n; ++t)
if (taskstatus[t].xHandle == (*changes).Task(i))
break;
if (t < n)
continue;
}
else if ((*changes).Task(i) != taskid)
continue;
int change[NUM_HEAP_TASK_CAPS];
bool any = false;
for (int j = 0; j < NUM_HEAP_TASK_CAPS; ++j)
{
change[j] = (*changes).After(i, j) - (*changes).Before(i, j);
if (change[j])
any = true;
}
if (any || all)
{
if (first)
{
writer->printf("--Task-- Total DRAM D/IRAM IRAM SPIRAM"
" +/- DRAM D/IRAM IRAM SPIRAM\n");
first = false;
}
Name name("NoTaskMap");
if (tm)
tm->find((*changes).Task(i), name);
writer->printf("%-15.15s %7d%7d%7d%7d %+7d%+7d%+7d%+7d\n", name.bytes,
(*changes).After(i, DRAM), (*changes).After(i, D_IRAM),
(*changes).After(i, IRAM), (*changes).After(i, SPIRAM),
change[DRAM], change[D_IRAM], change[IRAM], change[SPIRAM]);
}
++done;
if (num > 0)
break;
}
}
if (tln)
{
writer->printf("============================ blocks dumped = %d%s\n",
numafter, numafter < DUMPSIZE ? "" : " (limited)");
tl = tasklist;
for (int i = 0; i < tln; ++i, ++tl)
print_blocks(writer, *tl, leaks);
}
else if (!tasks)
{
bool headline = true;
for (int i = changes->begin(); i < changes->end(); ++i)
{
bool any = false;
for (int j = 0; j < NUM_HEAP_TASK_CAPS; ++j)
{
if ((*changes).After(i, j) - (*changes).Before(i, j) != 0)
any = true;
}
if (any)
{
if (headline)
{
writer->printf("============================ blocks dumped = %d%s\n",
numafter, numafter < DUMPSIZE ? "" : " (limited)");
headline = false;
}
print_blocks(writer, (*changes).Task(i), leaks);
}
}
}
changes->transfer();
for (int i = 0; i < numafter; ++i)
{
before[i] = after[i];
}
numbefore = numafter;
}
static void module_tasks(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
OvmsMutexLock lock(&taskstatus_mutex);
UBaseType_t num = uxTaskGetNumberOfTasks();
writer->printf("Number of Tasks =%3u%s Stack: Now Max Total Heap 32-bit SPIRAM C# PRI CPU%% BPR/MH\n", num,
num > MAX_TASKS ? ">max" : " ");
if (!allocate())
{
writer->printf("Can't allocate storage for task diagnostics\n");
return;
}
bool showStack = (strcmp(cmd->GetName(),"stack") == 0);
// sample current task activity if last taskstatus fetched is too old:
uint32_t now = xTaskGetTickCount() / configTICK_RATE_HZ;
if (now - taskstatus_time > 3600)
{
get_tasks();
vTaskDelay(pdMS_TO_TICKS(2000));
}
// copy last taskstatus for comparison:
std::map<UBaseType_t, uint32_t> last_runtime;
for (int i = 0; i < taskstatus_cnt; i++)
last_runtime[taskstatus[i].xTaskNumber] = taskstatus[i].ulRunTimeCounter;
uint32_t last_totalruntime = totalruntime;
// update taskstatus:
UBaseType_t n = get_tasks();
get_memory(tasklist, 0);
uint32_t diff_totalruntime = totalruntime - last_totalruntime;
// output task info sorted by xTaskNumber:
num = 0;
for (UBaseType_t j = 0; j < n; )
{
for (UBaseType_t i = 0; i < n; ++i)
{
if (taskstatus[i].xTaskNumber == num)
{
int k = changes->find(taskstatus[i].xHandle);
int heaptotal = 0, heap32bit = 0, heapspi = 0;
if (k >= 0)
{
heaptotal = (*changes).After(k, DRAM) + (*changes).After(k, D_IRAM);
heap32bit = (*changes).After(k, IRAM);
heapspi = (*changes).After(k, SPIRAM);
}
uint32_t total = (uint32_t)taskstatus[i].pxStackBase >> 16;
uint32_t used = total - ((uint32_t)taskstatus[i].pxStackBase & 0xFFFF);
int core = xTaskGetAffinity(taskstatus[i].xHandle);
uint32_t runtime = taskstatus[i].ulRunTimeCounter - last_runtime[taskstatus[i].xTaskNumber];
writer->printf("%08X %4u %s %-15s %5u %5u %5u %7u%7u%7u %c %3d %3.0f%% %3d/%2d\n", taskstatus[i].xHandle,
taskstatus[i].xTaskNumber, states[taskstatus[i].eCurrentState], taskstatus[i].pcTaskName,
used, total - taskstatus[i].usStackHighWaterMark, total, heaptotal, heap32bit, heapspi,
(core == tskNO_AFFINITY) ? '*' : '0'+core, taskstatus[i].uxCurrentPriority,
diff_totalruntime ? ((float) runtime / diff_totalruntime * 100) : 0.0f,
taskstatus[i].uxBasePriority, taskstatus[i].uxMutexesHeld);
if (showStack)
{
uint32_t* stack = (uint32_t*)(pxTaskGetStackStart(taskstatus[i].xHandle) + total);
uint32_t* topstack = (uint32_t*)((uint8_t*)stack - used);
while (topstack < stack)
{
uint32_t word = *topstack++;
if ((word & 0xFF000000) == 0x80000000)
{
writer->printf(" %p", word - 0x40000000);
}
}
writer->printf("\n");
}
++j;
break;
}
}
++num;
}
}
static void module_tasks_data(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
OvmsMutexLock lock(&taskstatus_mutex);
StringWriter buf;
buf.reserve(2048);
UBaseType_t num = uxTaskGetNumberOfTasks();
if (!allocate())
{
if (writer)
writer->printf("Can't allocate storage for task diagnostics\n");
return;
}
// sample current task activity if last taskstatus fetched is too old:
uint32_t now = xTaskGetTickCount() / configTICK_RATE_HZ;
if (now - taskstatus_time > 3600)
{
get_tasks();
vTaskDelay(pdMS_TO_TICKS(2000));
}
// copy last taskstatus for comparison:
std::map<UBaseType_t, uint32_t> last_runtime;
for (int i = 0; i < taskstatus_cnt; i++)
last_runtime[taskstatus[i].xTaskNumber] = taskstatus[i].ulRunTimeCounter;
uint32_t last_totalruntime = totalruntime;
// update taskstatus:
UBaseType_t n = get_tasks();
get_memory(tasklist, 0);
uint32_t diff_totalruntime = totalruntime - last_totalruntime;
// output task count & total runtime diff:
buf.printf("*-OVM-DebugTasks,1,86400,%u,%u", n, diff_totalruntime);
// output tasks sorted by xTaskNumber:
num = 0;
for (UBaseType_t j = 0; j < n; )
{
for (UBaseType_t i = 0; i < n; ++i)
{
if (taskstatus[i].xTaskNumber == num)
{
int k = changes->find(taskstatus[i].xHandle);
int heaptotal = 0, heap32bit = 0, heapspi = 0;
if (k >= 0)
{
heaptotal = (*changes).After(k, DRAM) + (*changes).After(k, D_IRAM);
heap32bit = (*changes).After(k, IRAM);
heapspi = (*changes).After(k, SPIRAM);
}
uint32_t total = (uint32_t)taskstatus[i].pxStackBase >> 16;
uint32_t used = total - ((uint32_t)taskstatus[i].pxStackBase & 0xFFFF);
uint32_t runtime = taskstatus[i].ulRunTimeCounter - last_runtime[taskstatus[i].xTaskNumber];
// output task info block:
// ,<num>,<name>,<state>
// ,<stack_now>,<stack_max>,<stack_total>
// ,<heaptotal>,<heap32bit>,<heapspi>
// ,<runtime>
buf.printf(",%u,%-.15s,%s,%u,%u,%u,%u,%u,%u,%u",
taskstatus[i].xTaskNumber, taskstatus[i].pcTaskName, states[taskstatus[i].eCurrentState],
used, total - taskstatus[i].usStackHighWaterMark, total,
heaptotal, heap32bit, heapspi, runtime);
++j;
break;
}
}
++num;
}
if (writer)
{
writer->puts(buf.substr(25).c_str());
}
else
{
ESP_LOGD(TAG, "Task stats: %s", buf.substr(25).c_str());
MyNotify.NotifyString("data", "debug.tasks", buf.c_str());
}
}
static void module_check(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
heap_caps_check_integrity_all(true);
}
#endif // NOGO
static void module_fault(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
ESP_LOGI(TAG,"Abort faulting module (on command)");
abort();
}
static void module_trigger_twdt(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
ESP_LOGI(TAG,"Triggering task watchdog (on command)");
// trigger twdt on event task by blocking all events:
static auto covid19 = [](std::string event, void* data) { vTaskDelay(portMAX_DELAY); };
MyEvents.RegisterEvent(TAG, "ticker.1", covid19);
writer->puts(
"Task watchdog will be triggered in " STR(CONFIG_TASK_WDT_TIMEOUT_S) " seconds.\n"
"Note: important events will cause reset as soon as queue is full.");
}
static void module_reset(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
writer->puts("Resetting system...");
vTaskDelay(1000/portTICK_PERIOD_MS);
MyBoot.Restart();
}
static void module_perform_factoryreset(OvmsWriter* writer)
{
const esp_partition_t* p = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, "store");
if (p == NULL)
{
if (writer)
writer->puts("DATA/FAT Partition 'store' could not be found - factory reset aborted");
else
ESP_LOGE(TAG, "DATA/FAT Partition 'store' could not be found - factory reset aborted");
return;
}
if (writer)
{
writer->printf("Store partition is at %08x size %08x\n", p->address, p->size);
writer->puts("Unmounting configuration store...");
}
else
{
ESP_LOGI(TAG, "Store partition is at %08x size %08x", p->address, p->size);
ESP_LOGI(TAG, "Unmounting configuration store...");
}
MyConfig.unmount();
if (writer)
writer->printf("Erasing %d bytes of flash...\n",p->size);
else
ESP_LOGI(TAG, "Erasing %d bytes of flash...", p->size);
spi_flash_erase_range(p->address, p->size);
if (writer)
writer->puts("Factory reset of configuration store complete and reboot now...");
else
ESP_LOGW(TAG, "Factory reset of configuration store complete and reboot now...");
vTaskDelay(1000/portTICK_PERIOD_MS);
MyBoot.Restart();
}
bool module_factory_reset_yesno(OvmsWriter* writer, void* ctx, char ch)
{
writer->printf("%c\n",ch);
if (ch != 'y')
{
writer->puts("Factory reset aborted");
return false;
}
module_perform_factoryreset(writer);
// not reached, just for gcc:
return false;
}
static void module_factory_reset(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
if (argc > 0)
{
if (strcmp(argv[0], "-noconfirm") != 0)
{
cmd->PutUsage(writer);
return;
}
module_perform_factoryreset(writer);
}
else
{
writer->printf("Reset configuration store to factory defaults, and lose all configuration data (y/n): ");
writer->RegisterInsertCallback(module_factory_reset_yesno, NULL);
}
}
static void module_eventhandler(std::string event, void* data)
{
if (event == "ticker.300")
{
if (MyConfig.GetParamValueBool("module", "debug.tasks", false))
module_tasks_data(0, NULL, NULL, 0, NULL);
}
#ifdef CONFIG_OVMS_COMP_SDCARD
else if (event == "sd.mounted")
{
if (unlink("/sd/factoryreset.txt") == 0)
{
ESP_LOGW(TAG, "Found file '/sd/factoryreset.txt' => performing factory reset");
module_perform_factoryreset(NULL);
}
}
else if (event == "ticker.1")
{
static int module_sw2_pushcnt = 0;
// SW2 is connected to SD_DATA0 so can be 0 due to SD card activity.
// Is there a clean way to detect if SD transmissions are currently running?
// The sdmmc semaphore and status variables are all static…
// Workaround: only watch SW2 if no SD card is mounted:
if (MyPeripherals->m_sdcard->isinserted() || gpio_get_level((gpio_num_t)MODULE_GPIO_SW2) != 0)
{
module_sw2_pushcnt = 0;
return;
}
module_sw2_pushcnt++;
if (module_sw2_pushcnt < 10)
ESP_LOGW(TAG, "SW2 pushed, factory reset in %d seconds", 10-module_sw2_pushcnt);
else
{
ESP_LOGW(TAG, "SW2 held for 10 seconds => performing factory reset");
module_perform_factoryreset(NULL);
}
}
#endif
}
static void module_summary(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
writer->puts("OVMS MODULE SUMMARY");
writer->puts("\nModule");
writer->printf(" Version: %s\n",StandardMetrics.ms_m_version->AsString().c_str());
writer->printf(" Hardware: %s\n",StandardMetrics.ms_m_hardware->AsString().c_str());
writer->printf(" 12v: %0.1fv\n",StandardMetrics.ms_v_bat_12v_voltage->AsFloat());
#ifdef CONFIG_OVMS_COMP_CELLULAR
writer->puts("");
MyPeripherals->m_cellular_modem->SupportSummary(writer, true);
#endif // #ifdef CONFIG_OVMS_COMP_CELLULAR
MyConfig.SupportSummary(writer);
writer->puts("\nREPORT ENDS");
}
class OvmsModuleInit
{
public:
OvmsModuleInit()
{
ESP_LOGI(TAG, "Initialising MODULE (5100)");
#ifndef NOGO
TaskMap::instance()->insert(0x00000000, "no task");
#endif
#ifdef CONFIG_OVMS_COMP_SDCARD
MyEvents.RegisterEvent(TAG, "sd.mounted", module_eventhandler);
MyEvents.RegisterEvent(TAG, "ticker.1", module_eventhandler);
#endif //CONFIG_OVMS_COMP_SDCARD
MyEvents.RegisterEvent(TAG, "ticker.300", module_eventhandler);
OvmsCommand* cmd_module = MyCommandApp.RegisterCommand("module","MODULE framework");
cmd_module->RegisterCommand("memory","Show module memory usage",module_memory,"[<task names or ids>|*|=]",0,TASKLIST);
cmd_module->RegisterCommand("leaks","Show module memory changes",module_memory,"[<task names or ids>|*|=]",0,TASKLIST);
OvmsCommand* cmd_tasks = cmd_module->RegisterCommand("tasks","Show module task usage",module_tasks);
cmd_tasks->RegisterCommand("stack","Show module task usage with stack",module_tasks);
cmd_tasks->RegisterCommand("data","Output module task stats record",module_tasks_data);
cmd_module->RegisterCommand("fault","Abort fault the module",module_fault);
OvmsCommand* cmd_trigger = cmd_module->RegisterCommand("trigger","Trigger framework");
cmd_trigger->RegisterCommand("twdt","Trigger task watchdog timeout",module_trigger_twdt);
cmd_module->RegisterCommand("reset","Reset module",module_reset);
cmd_module->RegisterCommand("check","Check heap integrity",module_check);
cmd_module->RegisterCommand("summary","Show module summary",module_summary);
OvmsCommand* cmd_factory = cmd_module->RegisterCommand("factory","MODULE FACTORY framework");
cmd_factory->RegisterCommand("reset","Factory Reset module",module_factory_reset,"[-noconfirm]",0,1);
}
} MyOvmsModuleInit __attribute__ ((init_priority (5100)));
// Returns the value of the stack pointer in the calling function.
// The stack frame for this function is 32 bytes, hence the add.
void* stack() {
__asm__(
" addi a2, sp, 32\n"
" retw.n\n"
);
return 0;
}