/**************************************************************************** * 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 #include #include #include #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 *! */ __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"); } }