Files
AC707N/SDK/apps/common/perf_counter/perf_counter.c
T
2025-12-03 11:12:34 +08:00

434 lines
14 KiB
C

/****************************************************************************
* Copyright 2022 Gorgon Meducer (Email:embedded_zhuoran@hotmail.com) *
* *
* 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. *
* *
****************************************************************************/
/*============================ INCLUDES ======================================*/
#undef __PERF_COUNT_PLATFORM_SPECIFIC_HEADER__
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#define BOOL_DEFINE_CONFLICT
#include "app_config.h"
#include "system/includes.h"
/* #include "asm/clock.h" */
#include "spinlock.h"
//static spinlock_t perfc_lock;
#define PERF_COUNTER_LOCK() // spin_lock(&perfc_lock)
#define PERF_COUNTER_UNLOCK() // spin_unlock(&perfc_lock)
#define __IMPLEMENT_PERF_COUNTER
#include "perf_counter.h"
#if 1 //沿用操作系统ticktimer
#define TIMER_USE_ISR_SOLVE_OVERFLOW_PROBLEM 1 // 用中断解决溢出问题
#define PERF_COUNTER_TIMER_CON q32DSP(0)->TTMR_CON
#define PERF_COUNTER_TIMER_CNT q32DSP(0)->TTMR_CNT
#define PERF_COUNTER_TIMER_PRD q32DSP(0)->TTMR_PRD
#define g_overflow_cnt jiffies
uint32_t SystemCoreClock;
void perf_counter_init(void)
{
SystemCoreClock = clk_get("sys");
printf("####^*^### FIX_ME-> %s, %d,%d \r\n", __FUNCTION__, __LINE__, (int)SystemCoreClock);
update_perf_counter();
}
#else //占据一个硬件TIMER
#define TIMER_USE_ISR_SOLVE_OVERFLOW_PROBLEM 1 // 1:用中断解决溢出问题, 0:查pending再使用jiffies纠正
#define PERF_COUNTER_TIMER_CON JL_TIMER5->CON
#define PERF_COUNTER_TIMER_CNT JL_TIMER5->CNT
#define PERF_COUNTER_TIMER_FLOW_PRD JL_TIMER5->PRD
#define PERF_COUNTER_TIMER_ISR_IDX IRQ_TIMER5_IDX
#define PERF_COUNTER_TIMER_PRD 0xffffffff
#define PERF_COUNTER_TIMER_PENDSTSET_Msk BIT(15)
#define PERF_COUNTER_TIMER_PENDSTSET_Msk_Clr BIT(14)
//修改TIMER频率,需要同时修改 PERF_CNT_DELAY_US_COMPENSATION, 精度小于1us才可以调用perfc_delay_us
#define TIMER_CLK 24000000
/*#define TIMER_CLK 6000000 */
/*#define TIMER_CLK 750000*/
/*#define TIMER_CLK 93750*/
uint32_t SystemCoreClock = TIMER_CLK;
volatile uint32_t g_overflow_cnt;
#if TIMER_USE_ISR_SOLVE_OVERFLOW_PROBLEM
___interrupt
static void perf_counter_timer_isr(void)
{
PERF_COUNTER_TIMER_CON |= BIT(14);
++g_overflow_cnt;
}
#endif
static int perf_counter_init(void)
{
#if TIMER_USE_ISR_SOLVE_OVERFLOW_PROBLEM
request_irq(PERF_COUNTER_TIMER_ISR_IDX, 0, perf_counter_timer_isr, 0);
#endif
PERF_COUNTER_TIMER_CON = BIT(14) | (6 << 10) ;
PERF_COUNTER_TIMER_FLOW_PRD = -1;
#if TIMER_CLK == 24000000 //精度 1/24=0.0416us,2^32/1000/1000/60/ 24=2.98分钟溢出
#elif TIMER_CLK == 6000000 //精度 4/24=0.16us,2^32/1000/1000/60/ 6=11.9分钟溢出
PERF_COUNTER_TIMER_CON |= BIT(4);
#elif TIMER_CLK == 750000 //精度 32/24=1.33us,2^32/1000/1000/60/ 0.75=95.4分钟溢出
PERF_COUNTER_TIMER_CON |= BIT(5) | BIT(6);
#elif TIMER_CLK == 93750 //精度 256/24=10.6us,2^32/1000/1000/60/60 0.09375=31.8小时溢出
PERF_COUNTER_TIMER_CON |= BIT(7);
#endif
PERF_COUNTER_TIMER_CON |= BIT(0);
PERF_COUNTER_TIMER_CNT = 0;
update_perf_counter();
return 0;
}
__initcall(perf_counter_init);
#endif
/*============================ MACROS ========================================*/
#ifndef PERF_CNT_COMPENSATION_THRESHOLD
# define PERF_CNT_COMPENSATION_THRESHOLD 16
#endif
#ifndef PERF_CNT_DELAY_US_COMPENSATION
# define PERF_CNT_DELAY_US_COMPENSATION 158 //WL82_320M:255
#endif
/*============================ GLOBAL VARIABLES ==============================*/
/*============================ LOCAL VARIABLES ===============================*/
int64_t g_lLastTimeStamp;
static uint32_t s_lOldTimeCnt, s_lOldOverFlowCnt;
static int32_t s_nUSUnit;
static int32_t s_nMSUnit = 0x7fffffff;
/*============================ PROTOTYPES ====================================*/
/*============================ IMPLEMENTATION ================================*/
/*============================ INCLUDES ======================================*/
void update_perf_counter(void)
{
s_nUSUnit = SystemCoreClock / 1000000ul;
s_nMSUnit = SystemCoreClock / 1000ul;
g_lLastTimeStamp = get_system_ticks();
__perf_counter_printf__("update_perf_counter SystemCoreClock=%dMhz,PERF_CNT_DELAY_US_COMPENSATION=%d", (int)SystemCoreClock / 1000000, PERF_CNT_DELAY_US_COMPENSATION);
}
/*! \note this function should only be called when irq is disabled
* hence SysTick-LOAD and (SCB->ICSR & SCB_ICSR_PENDSTSET_Msk)
* won't change.
*/
static __inline int64_t check_systick(void)
{
int64_t lTemp;
PERF_COUNTER_LOCK();
int64_t lOldTimestamp = s_lOldTimeCnt + (int64_t)(s_lOldOverFlowCnt * (int64_t)PERF_COUNTER_TIMER_PRD);
PERF_COUNTER_UNLOCK();
uint32_t nTemp = PERF_COUNTER_TIMER_CNT;
uint32_t nTempOverflowCnt = g_overflow_cnt;
/* Since we cannot stop counting temporarily, there are several
* conditions which we should take into consideration:
* - Condition 1: when assigning nTemp with the register value (LOAD-VAL),
* the underflow didn't happen but when we check the PENDSTSET bit,
* the underflow happens, for this condition, we should not
* do any compensation. When this happens, the (LOAD-nTemp) is
* smaller than PERF_CNT_COMPENSATION_THRESHOLD (a small value) as
* long as LOAD is bigger than (or equals to) the
* PERF_CNT_COMPENSATION_THRESHOLD;
* - Condition 2: when assigning nTemp with the register value (LOAD-VAL),
* the VAL is zero and underflow happened and the PENDSTSET bit
* is set, for this condition, we should not do any compensation.
* When this happens, the (LOAD-nTemp) is equals to zero.
* - Condition 3: when assigning nTemp with the register value (LOAD-VAL),
* the underflow has already happened, hence the PENDSTSET
* is set, for this condition, we should compensate the return
* value. When this happens, the (LOAD-nTemp) is bigger than (or
* equals to) PERF_CNT_COMPENSATION_THRESHOLD.
* The following code implements an equivalent logic.
*/
#if !TIMER_USE_ISR_SOLVE_OVERFLOW_PROBLEM
if (PERF_COUNTER_TIMER_CON & PERF_COUNTER_TIMER_PENDSTSET_Msk) {
PERF_COUNTER_TIMER_CON |= PERF_COUNTER_TIMER_PENDSTSET_Msk_Clr;
nTemp = PERF_COUNTER_TIMER_CNT; //还是有概率会造成1次溢出周期的精度的误差
uint32_t overflow_timer = timer_get_sec() / (PERF_COUNTER_TIMER_PRD / SystemCoreClock);
if (overflow_timer == 0) {
overflow_timer = 1;
}
g_overflow_cnt += overflow_timer;
/*if (((int32_t)SysTick->LOAD - nTemp) >= PERF_CNT_COMPENSATION_THRESHOLD) {*/
/*nTemp += SysTick->LOAD + 1;*/
/*}*/
}
#endif
/* When calling get_system_ticks() in an exception handler that has a
* higher priority than the SysTick_Handler, in some rare cases, the
* lTemp might be temporarily smaller than the previous value (i.e.
* s_lOldTimestamp), to mitigate the adverse effects of this problem,
* we use the following code to avoid time-rolling-back issue.
*
* NOTE: the issue mentioned above doesn't accumulate or have long-lasting
* effects.
*/
lTemp = nTemp + (int64_t)(nTempOverflowCnt * (int64_t)PERF_COUNTER_TIMER_PRD);
if (lTemp < lOldTimestamp) {
/*
timer计数回滚了,但是还没来得及进中断更新g_overflow_cnt(尤其多核情况会出现), 所以小于上次计数, 属于正常情况,
另外一种情况是: timer计数回滚了,并且 TIMER被关闭计数不进中断导致jiffies不更新也会导致一直进来
*/
/*__perf_counter_printf__("[perf_counter] time-rolling-back happend!!! %lld, %lld, %d, %d\r\n",lOldTimestamp,lTemp,nTemp,nTempOverflowCnt); */
lTemp += PERF_COUNTER_TIMER_PRD;//补偿回滚导致的损失
/*
如果这一时刻开始被切走cpu,导致在另外一处地方更新了一个更大的s_lOldOverFlowCnt, 这时候返回的值就是比lOldTimestamp偏小错误的,
但是没办法避免,即使把整个函数互斥起来,也只能够保证这个函数不被切走cpu,互斥一打开就有可能切走,
因此不会在这里做额外的处理,下次再进来s_lOldOverFlowCnt会得到修复
*/
} else {
/*注意这里 不互斥的情形下, 另外一个线程会重入造成非原子操作,即使分开两个U32变量,仅是为了不造成错误,避免不了改写顺序问题,
一种特殊的情形是,在这一个时刻,CPU切走,s_lOldOverFlowCnt被更新了一个更大的值,等CPU再回来后,两个全局U32变量中的一个或者两个被覆盖了旧的较小的值,
但是在下一轮进来会纠正,并且不影响返回结果
*/
PERF_COUNTER_LOCK();
s_lOldOverFlowCnt = nTempOverflowCnt;
s_lOldTimeCnt = nTemp;
PERF_COUNTER_UNLOCK();
}
return lTemp;
}
__attribute__((always_inline))
static void delay_us_use_nop(unsigned int us)
{
extern int sys_clk_get(void);
if (JL_SFC->CON0 & BIT(0)) {
us *= clk_get("sys") / 1000000 / 8;
} else {
us *= clk_get("sys") / 1000000 / 4;
}
while (us--) {
asm volatile("nop");
}
}
void delay_us(uint32_t nUs)
{
if (cpu_irq_disabled()) {
delay_us_use_nop(nUs);
return;
}
int64_t lUs = (int64_t)nUs * (int64_t)s_nUSUnit;
if (lUs <= PERF_CNT_DELAY_US_COMPENSATION) {
ASSERT(s_nUSUnit);//perf_counter 还未初始化就调用
return ;
}
lUs -= PERF_CNT_DELAY_US_COMPENSATION;
lUs += get_system_ticks();
while (get_system_ticks() < lUs);
}
void delay_ms(uint32_t nMs)
{
if (cpu_irq_disabled()) {
delay_us_use_nop(nMs * 1000);
return;
}
int64_t lUs = (int64_t)nMs * (int64_t)s_nMSUnit;
if (lUs <= PERF_CNT_DELAY_US_COMPENSATION) {
ASSERT(s_nMSUnit);//perf_counter 还未初始化就调用
return ;
}
lUs -= PERF_CNT_DELAY_US_COMPENSATION;
lUs += get_system_ticks();
while (get_system_ticks() < lUs);
}
__attribute__((noinline))
int64_t get_system_ticks(void)
{
return check_systick();
}
/*! \note the prototype of this clock() is different from the one defined in
*! time.h. As clock_t is usually defined as unsigned int, it is
*! not big enough in Cortex-M system to hold a time-stamp. clock()
*! defined here returns the timestamp since the begining of main()
*! and its unit is clock cycle (rather than 1ms). Hence, for a system
*! running under several hundreds MHz or even 1GHz, e.g. RT10xx from
*! NXP, it is very easy to see a counter overflow as clock_t is
*! defined as uint32_t in timer.h.
*! Since we are not allowed to change the defintion of clock_t in
*! official header file, i.e. time.h, I use a compatible prototype
*! after I checked the AAPCS spec. So, the return of the clock() is
*! int64_t, which will use the R0 to store the lower 32bits and R1
*! to store the higher 32bits. When you are using the prototype from
*! timer.h, caller will only take the lower 32bits stored in R0 and
*! the higher 32bits stored in R1 will be ignored.
*!
*! If you want to use the non-overflow version of this clock(), please
*! 1) define the MACRO: __PERF_CNT_USE_LONG_CLOCK__ in your project
*! and 2) do not include system header file <time.h>
*!
*/
__attribute__((nothrow))
int64_t clock(void)
{
return get_system_ticks();
}
uint32_t get_system_ms(void)
{
return (check_systick()) / s_nMSUnit;
}
uint32_t timer_get_ms(void)
{
return (check_systick()) / s_nMSUnit;
}
uint32_t timer_get_sec(void)
{
return timer_get_ms() / 1000;
}
int64_t get_system_us(void)
{
return (check_systick()) / s_nUSUnit;
}
int64_t perfc_convert_ticks_to_ms(int64_t lTick)
{
return lTick / (int64_t)s_nMSUnit;
}
int64_t perfc_convert_ms_to_ticks(uint32_t wMS)
{
int64_t lResult = (int64_t)s_nMSUnit * (int64_t)wMS;
return lResult ? lResult : 1;
}
int64_t perfc_convert_ticks_to_us(int64_t lTick)
{
return lTick / (int64_t)s_nUSUnit;
}
int64_t perfc_convert_us_to_ticks(uint32_t wMS)
{
int64_t lResult = (int64_t)s_nUSUnit * (int64_t)wMS;
return lResult ? lResult : 1;
}
bool __perfc_is_time_out(int64_t lPeriod, int64_t *plTimestamp, bool bAutoReload)
{
if (NULL == plTimestamp) {
return false;
}
int64_t lTimestamp = get_system_ticks();
if (0 == *plTimestamp) {
*plTimestamp = lPeriod;
*plTimestamp += lTimestamp;
return false;
}
if (lTimestamp >= *plTimestamp) {
if (bAutoReload) {
*plTimestamp = lPeriod + lTimestamp;
}
return true;
}
return false;
}
unsigned int time_lapse(unsigned int *handle, unsigned int time_out)//2^32/1000/60/60/24 后超时
{
unsigned int t, t2;
if (*handle == 0) {
*handle = timer_get_ms();
return 0;
}
t = timer_get_ms();
t2 = t - *handle;
if (t2 > time_out) {
*handle = t;
return t2;
}
return 0;
}
__attribute__((always_inline))
void delay(unsigned int ncycle)
{
while (ncycle--) {
asm volatile("nop");
}
}