/*---------------------------------------------------------------------------- * Copyright (c) Fenda Technologies Co., Ltd. 2020. All rights reserved. * * Description: log_api.c * * Author: saimen * * Create: 2022-7-16 *--------------------------------------------------------------------------*/ //lib #include #include #include #include //os //sdk #include "apollo4b.h" //#include "am_mcu_apollo.h" //drv #include "sys_typedef.h" #include "sys_config.h" //user #include "crc32.h" #include "log_api.h" #include "log_port.h" #define ENABLE_STATIC_PRINT false extern uint32_t am_util_stdio_printf(const char *pcFmt, ...); #define static_print_remind(...) am_util_stdio_printf(__VA_ARGS__) #if ENABLE_STATIC_PRINT #define static_print_info(...) am_util_stdio_printf(__VA_ARGS__) #else #define static_print_info(...) #endif #define ENABLE_PRINT_FILE_TXT OFF /** TODO:Update 增加缓冲机制,该缓冲空间同时用于行为日志,错误日志及hardfault日志记录. 1.用于行为日志及错误日志是为了优化写入性能; 2.用于hardfault日志是避免在hardfault中断中动态申请空间引发其他错误(不使用栈空间是避免过多侵入式修改cmbacktrace库); 3.行为日志及错误日志同时存在两块cache,一块等待读出保存时,另一块进行写入. 4.用户调用接口时,只将数据打包放入缓冲,缓冲满时发消息给任务处理保存到文件. 5.空间分配如下图示意所示. |----------------------| | Log cache Block 1 | |----------------------| | Log cache Block 2 | |----------------------| */ //可自定义 #define HEAD_ACTION 0xFA //行为日志的标记头 #define HEAD_ERROR 0xFB //错误日志的标记头 #define HEAD_FAULT 0xFB //硬件错误日志的标记头 #define LOG_BUF_TOTAL_SIZE 2048 //日志缓冲总大小 // #define LOG_INFO_VERSION 1 //日志系统版本 #define LOG_DATA_STRUCT_LEN 12 //日志数据结构体大小. #define LOG_BUF_BLOCK_NUMBER 2 //行为日志及错误日志需要双缓冲,把缓冲分成两个块 #define LOG_BUF_BLOCK_INVALID 0xFF //标记该缓冲块是无效的 #define LOG_BUF_BLOCK_SIZE ((LOG_BUF_TOTAL_SIZE)/ LOG_BUF_BLOCK_NUMBER) //日志单个缓冲块大小 //日志描述单条可写入最大尺寸 #define LOG_DATA_DES_MAX_LEN (LOG_BUF_BLOCK_SIZE - LOG_DATA_STRUCT_LEN) #define LOG_FILE_PATH_MAX 64 //日志数据文件绝对路径的最大长度 //日志种类枚举 typedef enum { ACTION_EVT_TYPE, //行为日志 ERROR_EVT_TYPE, //错误日志 FAULT_EVT_TYPE, //Fault日志 } DATA_LOG_TYPE; #pragma pack(1) //编译时按1个字节对齐 typedef union { uint32_t val; struct _field { uint32_t info3:8; //小类 uint32_t info2:8; //中类 uint32_t info1:8; //大类 uint32_t evtId:8; //事件ID } detail; } LogInfoField; typedef struct { uint8_t head; //日志头 uint8_t rev; //保留 uint16_t len; //日志内容长度 uint32_t timestamp; //日志内容:时间戳 LogInfoField field; //日志内容:详情 //char dsc[0]; //日志内容:描述(可选) } log_data_t; #pragma pack() //编译时恢复默认字节对齐 //日志缓冲结构体定义 typedef struct { uint32_t init_flag:8; //标记模块是否初始化。=0 无效数据;=1 正常数据;=2 恢复数据。 uint32_t version:8; //结构体版本 uint32_t curUseBlockId:8; //正在使用的缓冲块ID uint32_t needSaveBlockId:8; //需要保存的缓冲块ID uint32_t blockWrtMaxSize; //缓冲块可写入的最大数量 uint32_t blockStartTime[LOG_BUF_BLOCK_NUMBER]; //缓冲块所存数据的起始时间戳 uint8_t *pBufBlock[LOG_BUF_BLOCK_NUMBER]; //指向缓冲块的指针数组 uint32_t blockBufIdx[LOG_BUF_BLOCK_NUMBER]; //缓冲块指针偏移 uint32_t crc32; uint32_t log_buf[LOG_BUF_TOTAL_SIZE/4]; //所有日志的缓冲 } log_info_t; static log_info_t g_log_info; //存储行为日志缓冲块相关信息 static uint32_t g_log_count = 0; //日志流水号 static char g_full_path[LOG_FILE_PATH_MAX] = {0}; //debug start------------------------------------------------------ static void DEBUGHex(const uint8_t *dat, uint16_t len) { uint16_t i; uint8_t column = 0; uint8_t length = 0; char buf[50]; for(i = 0; i < len; i++) { length += snprintf(&buf[length], 50 - length, "%02X ", dat[i]); column++; if(column >= 16) { column = 0; length = 0; static_print_info("%s\r\n", buf); } else if((i+1) >= len){ static_print_info("%s\r\n", buf); } } } /** * @brief 打印结构化的日志数据. * @param path:const char *|日志路径. * @param startAddrOft:UINT32|起始地址偏移. * @param logNumlimit:UINT16|限制读取的结构块个数,0表示不限制. * @retval void|无. * @note 只能打印ACTION_EVT_TYPE及ERROR_EVT_TYPE两类,fault日志不符合结构化数据. */ void log_print_bin_file(char *path, uint32_t startAddrOft, uint16_t logNumlimit) { #if 1 int err = 0; uint32_t offset = startAddrOft; log_data_t logDat; char dsc[LOG_DATA_DES_MAX_LEN + 1]={0}; uint16_t cnt = 0; uint16_t i = 0; do { err = log_port_read_file(path, offset, (uint8_t*)&logDat, sizeof(log_data_t)); if(err == 0) { static_print_info("%s find EOF before read head info\r\n", path); break; } else if(err == sizeof(log_data_t)) { offset += err; if(logDat.head == HEAD_ACTION || logDat.head == HEAD_ERROR) { if(logDat.len > LOG_DATA_STRUCT_LEN) { err = log_port_read_file(path, offset, (uint8_t*)dsc, (logDat.len - LOG_DATA_STRUCT_LEN)); if(err == 0) { static_print_info("%s find EOF before read description info\r\n", path); break; } else if(err == (logDat.len - LOG_DATA_STRUCT_LEN)) { dsc[err] = '\0'; offset += err; } else { static_print_remind("read description failed, invalid length [%d|%d]\r\n",(logDat.len - LOG_DATA_STRUCT_LEN), err); break; } static_print_info("offset:0x%x len:%d TM:%d Code:%08X Dsc:%s\r\n", offset, logDat.len, logDat.timestamp, logDat.field.val, dsc); } else { static_print_info("offset:0x%x len:%d TM:%d Code:%08X Dsc:NULL\r\n", offset, logDat.len, logDat.timestamp, logDat.field.val); } } else { static_print_info("error:offset:0x%x logDat.head=0x%x\r\n", offset, logDat.head); break; } cnt++; if(logNumlimit != 0 && cnt >= logNumlimit) { static_print_info("!!! read log number reach limit %d, exit read file\r\n", logNumlimit); break; } } else { static_print_remind("read head info failed, file:[%s] err:%d\r\n", path, err); break; } } while(1); #endif } //debug end------------------------------------------------------ /** * @brief 将日志数据写入到缓冲. * @param type:DATA_LOG_TYPE|日志种类. * @param path:const char *|文件的绝对路径. * @param dat:const uint8_t *|数据指针. * @param len:UINT16|数据的长度. * @param timestamp:UINT32|该条日志对应的时间. * @retval int|返回写入的数据长度,负数表示错误码. */ static int log_write_to_buffer(DATA_LOG_TYPE type, uint32_t timestamp, uint8_t evtId, uint8_t info1, uint8_t info2, uint8_t info3, const char *dsc) //static int log_write_to_buffer(DATA_LOG_TYPE type, const uint8_t *dat, uint16_t len, uint32_t timestamp) { bool switch_block = false; uint8_t *p_cur_block; log_info_t *pLogBuf = NULL; log_data_t *p_log_data; uint16_t len = 0; uint16_t des_len = 0; pLogBuf = &g_log_info; if(dsc) { des_len = strlen(dsc); des_len = (des_len+3)&0xfffc;//四字节对齐 } if(des_len >=LOG_DATA_DES_MAX_LEN) { static_print_info("log dsc info is too large, auto truncation length:(%d -> %d)\r\n", des_len, LOG_DATA_DES_MAX_LEN); des_len = LOG_DATA_DES_MAX_LEN; } len = LOG_DATA_STRUCT_LEN + des_len; if(pLogBuf->blockBufIdx[pLogBuf->curUseBlockId] + len > pLogBuf->blockWrtMaxSize) { pLogBuf->needSaveBlockId = pLogBuf->curUseBlockId; pLogBuf->curUseBlockId = ((pLogBuf->curUseBlockId + 1) % LOG_BUF_BLOCK_NUMBER); pLogBuf->blockBufIdx[pLogBuf->curUseBlockId] = 0; pLogBuf->blockStartTime[pLogBuf->curUseBlockId] = timestamp; switch_block = true; static_print_info("og cur use buf block is full, change block, block id info[using:%d|toSave:%d]\r\n", pLogBuf->curUseBlockId, pLogBuf->needSaveBlockId); } else { if(pLogBuf->blockBufIdx[pLogBuf->curUseBlockId] == 0) { static_print_info("type #%d# log cur block idx is 0, fill new buf start timestamp: %d\r\n", type, timestamp); pLogBuf->blockStartTime[pLogBuf->curUseBlockId] = timestamp; } } static_print_info("g_log_count:%d curUseBlockId:%d blockBufIdx:%d needSaveBlockId:%d\r\n", g_log_count,pLogBuf->curUseBlockId,pLogBuf->blockBufIdx[pLogBuf->curUseBlockId],pLogBuf->needSaveBlockId); p_cur_block = pLogBuf->pBufBlock[pLogBuf->curUseBlockId]; p_log_data = (log_data_t*)&p_cur_block[pLogBuf->blockBufIdx[pLogBuf->curUseBlockId]]; if(type == ACTION_EVT_TYPE) { p_log_data->head = HEAD_ACTION; } else if(type == ERROR_EVT_TYPE) { p_log_data->head = HEAD_ERROR; } else if(type == FAULT_EVT_TYPE) { p_log_data->head = HEAD_FAULT; } else { return 0; } p_log_data->rev = g_log_count++; p_log_data->field.detail.evtId = evtId; p_log_data->field.detail.info1 = info1; p_log_data->field.detail.info2 = info2; p_log_data->field.detail.info3 = info3; p_log_data->len = len-4; p_log_data->timestamp = timestamp; pLogBuf->blockBufIdx[pLogBuf->curUseBlockId] += LOG_DATA_STRUCT_LEN; if(des_len) { memcpy(&p_cur_block[pLogBuf->blockBufIdx[pLogBuf->curUseBlockId]], dsc, des_len); pLogBuf->blockBufIdx[pLogBuf->curUseBlockId] += des_len; } //if(switch_block==true) if(pLogBuf->needSaveBlockId != LOG_BUF_BLOCK_INVALID)//防止因为高优化级中断错过打点 { #if 0 //TODO: //!!!此处暂时直接同步保存缓冲数据到文件,因为多个锁的存在,可能存在死锁问题,后续测试若存在,需改成发送消息给任务,在任务中调用保存接口,同时避免长时间占用锁. static_print_info("!!! ready write buf[len:%d] to file\r\n", pLogBuf->blockBufIdx[pLogBuf->needSaveBlockId]); saveLen = SaveDataLogBufToFile(type); static_print_info("!!! write ret: %d\r\n", saveLen); #else //TODO: //!!!因需要支持硬中断打点,故不再适合使用同步写入方式,采用信号量实现异步写入 if (__get_IPSR() == 0) { // not interrupt log_port_notify(LOG_EVENT_CACHE_FULL); } #endif } return 0; } /** * @brief 将日志缓冲有效数据写入到文件.一般关机时使用 * @retval int|返回写入的数据长度,负数表示错误码. */ static int log_write_valid_cache_to_file(void) { uint16_t ret=0; uint16_t len; uint32_t timestamp = 0; uint8_t *buf; log_info_t *pLogBuf = NULL; pLogBuf = &g_log_info; static_print_info("log using block=%d data len=%d,save block=%d\r\n", pLogBuf->curUseBlockId, pLogBuf->blockBufIdx[pLogBuf->curUseBlockId],pLogBuf->needSaveBlockId); if(pLogBuf->needSaveBlockId != LOG_BUF_BLOCK_INVALID) { timestamp = pLogBuf->blockStartTime[pLogBuf->needSaveBlockId]; len = pLogBuf->blockBufIdx[pLogBuf->needSaveBlockId]; buf = pLogBuf->pBufBlock[pLogBuf->needSaveBlockId]; log_port_write_file(g_full_path, buf, len); ret += len; // memcpy(buf, pLogBuf->pBufBlock[pLogBuf->needSaveBlockId], len); static_print_info("log unsaved block %d data len %d\r\n", pLogBuf->needSaveBlockId, len); } if(pLogBuf->blockBufIdx[pLogBuf->curUseBlockId] > 0) { timestamp = pLogBuf->blockStartTime[pLogBuf->curUseBlockId]; len = pLogBuf->blockBufIdx[pLogBuf->curUseBlockId]; buf = pLogBuf->pBufBlock[pLogBuf->curUseBlockId]; log_port_write_file(g_full_path, buf, len); ret += len; static_print_info("log using block %d data len %d\r\n", pLogBuf->curUseBlockId, pLogBuf->blockBufIdx[pLogBuf->curUseBlockId]); } pLogBuf->blockStartTime[0] = 0; pLogBuf->blockStartTime[1] = 0; pLogBuf->blockBufIdx[0] = 0; pLogBuf->blockBufIdx[1] = 0; pLogBuf->curUseBlockId = 0; pLogBuf->needSaveBlockId = LOG_BUF_BLOCK_INVALID; return ret; } /** * @brief 获取选定日志所有未保存缓冲数据. * @param type:DATA_LOG_TYPE|日志种类. * @param buf:UINT8 *|用来保存缓冲数据的缓存指针. * @param timestamp:UINT32 *|unix时间戳. * @retval UINT32|返回数据长度,负数表示错误码. */ static int log_get_not_saved_cache(uint8_t *buf, uint32_t *timestamp) { uint32_t len = 0; log_info_t *pLogBuf = NULL; pLogBuf = &g_log_info; if (__get_IPSR() == 0) { // not interrupt log_port_lock(); } if(pLogBuf->needSaveBlockId != LOG_BUF_BLOCK_INVALID) { *timestamp = pLogBuf->blockStartTime[pLogBuf->needSaveBlockId]; len = pLogBuf->blockBufIdx[pLogBuf->needSaveBlockId]; memcpy(buf, pLogBuf->pBufBlock[pLogBuf->needSaveBlockId], len); static_print_info("get log unsaved block %d data len %d\r\n", pLogBuf->needSaveBlockId, len); } else { *timestamp = pLogBuf->blockStartTime[pLogBuf->curUseBlockId]; } if(pLogBuf->blockBufIdx[pLogBuf->curUseBlockId] > 0) { memcpy(buf + len, pLogBuf->pBufBlock[pLogBuf->curUseBlockId], pLogBuf->blockBufIdx[pLogBuf->curUseBlockId]); len += pLogBuf->blockBufIdx[pLogBuf->curUseBlockId]; static_print_info("get log using block %d data len %d\r\n", pLogBuf->curUseBlockId, pLogBuf->blockBufIdx[pLogBuf->curUseBlockId]); } if (__get_IPSR() == 0) { // not interrupt log_port_unlock(); } return (len == 0) ? 0 : len; } static int log_api_restore(void) { log_info_t *p_log_info = &g_log_info; static_print_info("log_api_restore\n"); p_log_info->init_flag = true; p_log_info->version = LOG_INFO_VERSION; p_log_info->curUseBlockId = 0; p_log_info->needSaveBlockId = LOG_BUF_BLOCK_INVALID; p_log_info->blockWrtMaxSize = LOG_BUF_BLOCK_SIZE; p_log_info->pBufBlock[0] = (uint8_t *)&g_log_info.log_buf[0]; p_log_info->pBufBlock[1] = (uint8_t *)&g_log_info.log_buf[LOG_BUF_BLOCK_SIZE]; p_log_info->blockStartTime[0] = 0; p_log_info->blockStartTime[1] = 0; p_log_info->blockBufIdx[0] = 0; p_log_info->blockBufIdx[1] = 0; } //log api start------------------------------------------------- /** * @brief 创建日志模块嵌套互斥锁 * @retval int| 返回错误码,0=成功. */ int log_api_create_lock(void) { return log_port_create_lock(); } /** * @brief 初始化日志数据. * @retval int|返回结果,0=成功,负数表示错误码. */ int log_api_init(void) { #if 1 int err = 0; log_info_t *p_log_info = NULL; uint32_t ui32_crc; p_log_info = &g_log_info; //获取日志路径 log_port_get_full_path(NULL, g_full_path); static_print_remind("[%s],sizeof(log_info_t) = %d\r\n",g_full_path,sizeof(log_info_t)); if(sizeof(log_data_t) != LOG_DATA_STRUCT_LEN || sizeof(log_data_t) > LOG_BUF_TOTAL_SIZE) { err = true; static_print_remind("error:sizeof(log_data_t)"); } if(LOG_BUF_BLOCK_NUMBER != 2) { err = true; static_print_remind("error:LOG_BUF_BLOCK_NUMBER"); } if(err) { p_log_info->init_flag = false; return RET_ERROR; } ui32_crc = crc32_ex(0,(uint8_t*)&g_log_info, (sizeof(g_log_info) - 4- LOG_BUF_TOTAL_SIZE)); if(ui32_crc != g_log_info.crc32 || g_log_info.version != LOG_INFO_VERSION) { log_api_restore(); } //保存缓冲区数据 log_write_valid_cache_to_file(); //打几条log // log_print_bin_file(g_full_path,0,10); return RET_SUCCESS; #endif } /** * @brief 将选定的缓冲数据保存到对应的日志文件. * @retval int|返回写入的数据长度,负数表示错误码. */ int log_api_save_at_cache_full(void) { int err = 0; char file_path[LOG_FILE_PATH_MAX]; log_info_t *pLogBuf = NULL; pLogBuf = &g_log_info; static_print_info("log_api_save_at_cache_full()\r\n"); if(pLogBuf->needSaveBlockId == LOG_BUF_BLOCK_INVALID || pLogBuf->needSaveBlockId >= LOG_BUF_BLOCK_NUMBER ||\ pLogBuf->blockBufIdx[pLogBuf->needSaveBlockId] > pLogBuf->blockWrtMaxSize) { static_print_remind("save log to file failed, verify not passed\r\n"); return -1; } err = log_port_write_file(g_full_path, pLogBuf->pBufBlock[pLogBuf->needSaveBlockId], \ pLogBuf->blockBufIdx[pLogBuf->needSaveBlockId]); pLogBuf->blockBufIdx[pLogBuf->needSaveBlockId] = 0; pLogBuf->needSaveBlockId = LOG_BUF_BLOCK_INVALID; return err; } /** * @brief 在正常关机前保存所有未保存的缓冲数据. * @retval UINT32|返回数据长度,负数表示错误码. */ int log_api_save_at_power_off(void) { uint16_t len; uint32_t timestamp = 0; uint8_t *buf; log_info_t *pLogBuf = NULL; static_print_info("log_api_save_at_power_off()\r\n"); pLogBuf = &g_log_info; if (__get_IPSR() == 0) { // not interrupt log_port_lock(); } len = log_write_valid_cache_to_file(); if (__get_IPSR() == 0) { // not interrupt log_port_unlock(); } return len; } /** * @brief 记录单条行为日志. * @param timestamp:UINT32|unix时间戳. * @param evtId:UINT8|日志事件ID. * @param info1:UINT8|日志事件大类. * @param info2:UINT8|日志事件中类. * @param info3:UINT8|日志事件小类. * @param dsc:const char *|日志事件描述. * @retval UINT16|返回记录的数据长度,负数表示错误码. */ int log_api_record_action(uint32_t timestamp, uint8_t evtId, uint8_t info1, uint8_t info2, uint8_t info3, const char *dsc) { int err = 0; /* if (__get_IPSR() != 0) { static_print_info("!!! not support record action log in isr[%d:%d:%d:%d]\r\n", evtId, info1, info2, info3); return -1; } */ if(g_log_info.init_flag == false) { static_print_info("!!! record action log failed, not initialization\r\n"); return -1; } log_port_lock(); log_write_to_buffer(ACTION_EVT_TYPE, timestamp, evtId, info1, info2, info3, dsc); log_port_unlock(); static_print_info("action log:[0x%08X]\r\n", (evtId<<24)+(info1<<16)+(info2<<8)+info3); return err; } /** * @brief 记录单条错误日志. * @param timestamp:UINT32|unix时间戳. * @param evtId:UINT8|日志事件ID. * @param info1:UINT8|日志事件大类. * @param info2:UINT8|日志事件中类. * @param info3:UINT8|日志事件小类. * @param dsc:const char *|日志事件描述. * @retval UINT16|返回记录的数据长度,负数表示错误码. */ int log_api_record_error(uint32_t timestamp, uint8_t evtId, uint8_t info1, uint8_t info2, uint8_t info3, const char *dsc) { int err = 0; /* if (__get_IPSR() != 0) { static_print_info("!!! not support record error log in isr\r\n"); return -1; } */ if(g_log_info.init_flag == false) { static_print_info("!!! record error log failed, not initialization\r\n"); return -1; } log_port_lock(); log_write_to_buffer(ERROR_EVT_TYPE, timestamp, evtId, info1, info2, info3, dsc); log_port_unlock(); static_print_info("error log:[0x%08X]\r\n", (evtId<<24)+(info1<<16)+(info2<<8)+info3); return err; } /** * @brief 记录单条行为日志. * @param timestamp:UINT32|unix时间戳. * @param evtId:UINT8|日志事件ID. * @param info1:UINT8|日志事件大类. * @param info2:UINT8|日志事件中类. * @param info3:UINT8|日志事件小类. * @param dsc:const char *|日志事件描述. * @retval UINT16|返回记录的数据长度,负数表示错误码. */ int log_api_record_hardfault(uint32_t timestamp, uint8_t evtId, uint8_t info1, uint8_t info2, uint8_t info3, const char *dsc) { int err = 0; /* if (__get_IPSR() != 0) { static_print_info("!!! not support record action log in isr[%d:%d:%d:%d]\r\n", evtId, info1, info2, info3); return -1; } */ if(g_log_info.init_flag == false) { static_print_info("!!! record action log failed, not initialization\r\n"); return -1; } log_port_lock(); log_write_to_buffer(FAULT_EVT_TYPE, timestamp, evtId, info1, info2, info3, dsc); log_port_unlock(); static_print_info("action log:[0x%08X]\r\n", (evtId<<24)+(info1<<16)+(info2<<8)+info3); return err; } //外部存储使用 void* log_api_get_info(uint32_t *len, char **path) { g_log_info.crc32 = crc32_ex(0,(uint8_t*)&g_log_info, (sizeof(g_log_info) - 4- LOG_BUF_TOTAL_SIZE)); *len = sizeof(g_log_info); *path = (char*)g_full_path; return &g_log_info; } void log_api_set_save_flag(uint8_t value) { if(value == false) { log_api_restore(); } } uint8_t log_api_get_save_flag(void) { if(g_log_info.needSaveBlockId == LOG_BUF_BLOCK_INVALID && g_log_info.blockBufIdx[g_log_info.curUseBlockId]==0) { return false; } else { return true; } } //log api end-------------------------------------------------