1100 lines
32 KiB
C++
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;
|
||
|
}
|