From 2e465bc4c2ec0c96b6fbb45b8f70ff3bbb6ee0fb Mon Sep 17 00:00:00 2001 From: Gabriel Wang Date: Mon, 11 Jan 2021 18:50:02 +0000 Subject: [PATCH] found some racing condition and fixed them - fixed the racing condition - introduce a minimal systick LOAD restriction - add some comments --- lib/perf_counter.h | 80 ++++++++++++++++++++++++++++++++++--------- lib/perf_counter.lib | Bin 7220 -> 7044 bytes perf_counter.c | 65 +++++++++++++++++++++++++++-------- perf_counter.h | 80 ++++++++++++++++++++++++++++++++++--------- 4 files changed, 176 insertions(+), 49 deletions(-) diff --git a/lib/perf_counter.h b/lib/perf_counter.h index e818de2..61cde2e 100644 --- a/lib/perf_counter.h +++ b/lib/perf_counter.h @@ -155,26 +155,21 @@ /*============================ LOCAL VARIABLES ===============================*/ /*============================ PROTOTYPES ====================================*/ -/* Function: initialise cycle counter service - * and don't forget to tell the function whether the systick is already - * used by user applications. - * Don't worry, this cycle counter service won't affect your existing - * systick service. - */ -extern void init_cycle_counter(bool bSysTickIsOccupied); -/* Function : start_time - This function will be called right before starting the timed portion of the benchmark. - Implementation may be capturing a system timer (as implemented in the example code) - or zeroing some system parameters - e.g. setting the cpu clocks cycles to 0. -*/ -extern void start_cycle_counter(void); -/* Function : stop_time - This function will be called right after ending the timed portion of the benchmark. - Implementation may be capturing a system timer (as implemented in the example code) - or other system parameters - e.g. reading the current value of cpu cycles counter. + +/*! \brief try to set a start pointer for the performance counter + *! \retval false the LOAD register is too small + *! \retval true performance counter starts */ +extern bool start_cycle_counter(void); + +/*! \brief calculate the elapsed cycle count since the last start point + *! + *! \note you can have multiple stop_cycle_counter following one start point + *! + *! \return the elapsed cycle count. + */ extern int32_t stop_cycle_counter(void); /* Function : delay specified us with the help from systick @@ -207,4 +202,55 @@ __attribute__((nothrow)) extern int64_t clock(void); #endif + +/*----------------------------------------------------------------------------* + * Please ignore the following APIs unless you have encountered some known * + * special conditions * + *----------------------------------------------------------------------------*/ + + +/*! \brief initialise cycle counter service + *! and don't forget to tell the function whether the systick is already + *! used by user applications. + *! Don't worry, this cycle counter service won't affect your existing + *! systick service. + *! + *! \note Usually the perf_counter can initialise itself with the help of + *! __attribute__((constructor(255))), this works fine in Arm Compiler + *! 5 (armcc), Arm Compiler 6 (armclang), arm gcc and llvm. It doesn't + *! work for IAR. So, when you are using IAR, please call this function + *! manually to initialise the perf_counter service. + *! + *! \note Perf_counter library assumes that: + *! a. Your project has already using SysTick + *! b. It assumes that you have already implemented the SysTick_Handler + *! c. It assumes that you have enabled the exception handling for + *! SysTick. + *! If these are not the case, please: + *! a. Add an empty SysTick_Handler to your project if you don't have + *! one + *! b. Make sure you have the SysTick Exception handling enabled + *! c. And call function init_cycle_counter(false) if you doesn't + *! use SysTick in your project at all. + *! + *! \param bSysTickIsOccupied A boolean value which indicates whether SysTick + *! is already used by user application. + */ +extern void init_cycle_counter(bool bSysTickIsOccupied); + + +/*! \note if you are using a compiler rather than armcc or armclang, e.g. iar, + *! arm gcc etc, the systick_wrapper_ual.o doesn't work with the linker + *! of your target toolchain as it use the $Super$$ which is only supported + *! by armlink. For this condition, you have to manually put this function + *! into your existing SysTick_Handler to make the perf_counter library + *! work. + *! + *! \note if you are using Arm Compiler 5 (armcc) or Arm Compiler 6 (armclang) + *! you do NOT have to insert this function into your SysTick_Handler, + *! the systick_wrapper_ual.s will do the work for you. + */ +extern void user_code_insert_to_systick_handler(void); + + #endif diff --git a/lib/perf_counter.lib b/lib/perf_counter.lib index 1202af2a0b714d21525bf90848d5fb21b98cdf1b..6b94bb05ba28b311b92f3823edf95c971978edf1 100644 GIT binary patch delta 1795 zcmZ{lZ)jUp6u{5D$xG5am)yK0O`B}?wbi^dO*LJ+u9ktZZvA72>xLgPC)k019ulF> zeb}JTR*Ftg={R5JKis~FIeih;!D1G5g=Y%Dz%EX$MS=F^QK835_jy)=LZG`Omk1ZZvgi-XT(nY zR?PdRY?aY(;nOlbwl<}bd7^bnv_}f_ z_tcqiMr?pNJt|E6Or~eG^{9O;sk+5&!#><1I#i+jlwr@2<77*DyvZ&kr$=Ly?!Qva ztq8OH$KU0-Yfg42y;|{(l*hm>kMm4r=M*dx=z%9?$Vg{GI3DJ+&y=}cg ziaHz$yeHn05UXY&eXwd@*lr)gU0U~nWezslRv0a<@!wm_L z!*{h*`vbJHE*`i@o^?sVv$&-_KKm*8*Che>EBW2U9ym|@5IIzNlsMDCcZoT_JMRZ1 zE;aBF@pJ9)Vva467O%|pQ(zm243B^J_XNjTc3g`uV4Py-zlEpb2elYj!+1GC^(EEQ}6+4 zWh-BU0L!=KPDq-?JlM2cN~flC0G6UN+eD1-Lnx(-tglUf(+gv*zjY}NQ-Ig zH7pUpqlOhZMK_UKUloaGjfAEG)L6V|bczN1j(l72zF}$aknce!&0thf{PCzIHt;nX zr|^9mHz`R1H)zhrE#eGDVwOA;z@b=L}4WQ*tqYGM=V)XII=3 zB|IFrv=ZI)N$gEUaWSs6t3+S89f&J=E`Z;}(>rW>4%g^QRl#ojD{gAe-`sLXv*G&( DSEUqS delta 1863 zcmY*aZAfHg6h7~rxjN(3@y?gy%vg>(-4V5NbR89zZ8Wo0TiUeuB)pzmr8Y}WVz35XJ;-4T~AlGi&vwq>MCDT%lxEf z@q*Tn`17v0H2Xo%580>cRkN8djE3wb z7*Ez0M*Ma@arV*OVXpQhY7X)6vs&D{bY*w=Z7s66x#Jx!948wd%g8kPgdMWi6NRzF zlxnWZzY4O#Na64Q3i|L14~yIw{O5;F#1d9ykM>072Wv%;!q42<&sAlQ+24XGX+N#J zHw=3nh5GID^{UC2FN}=Y)lw>7@%W>{G@jC@g;9erTJ5DV^1GUg8|>0 z>J5$R#jUuGLkt*UMZ+uXAkYdD_w>sfm_RP z9F9{ZTuIxoPnY2nz;+o{z%ym|2C6=LfCs4>gbgKyLGK|TRfY?|(jd5D)zTX9(K4Lp zuPPcB|AJPU;d+H>6}YnucLN_O!@mF@u*PT+_<%J=7I41gAERGU_D~uA{!o0fj%E)L zBq=Tg+ar}NYC!UYv@nf>L$0OpgtR2BLkgBl6VhTd0U&tJ!Icy);xvEg>sU02Dqt{z zL;;Z@cTQTDJ0GnZO02eaBIF?>Oym=ZD~M34>UA4pM2X~uG3toMMSqOwDZw$KNx`Ug z8dA%-a}$1<*Py0KcCZ^QMA(vnU?(1(kgFoZ zG8#E)UgZ7(K<*TBU$GF2$U=THGDXMzR%a1~W%ZNu5zuaVX&+YwEoE{xn9(|XJP9zt%fXB~&6k52 zeF|5&&k4O9H1%n;wU7S|wy1ZwDU?wccm&Uj2#)c&kcnE>fN%2`c;4dQLK*!I;)k60 z*08C5=l*a8r+Y4((W?D?K5VI@yaiRBZ#qvEk^NRU#`%b;Mf^M-vD6UHL%qP4A{l)F wuf|DU###3wrhXM@(>;0A)EfMph*~uZcyZ-3g3OfT6Va6J{8etxMne_<0|K!XRsaA1 diff --git a/perf_counter.c b/perf_counter.c index f60d73d..231277f 100644 --- a/perf_counter.c +++ b/perf_counter.c @@ -22,6 +22,10 @@ #include "cmsis_compiler.h" #include "perf_counter.h" +#ifndef PERF_CNT_COMPENSATION_THRESHOLD +# define PERF_CNT_COMPENSATION_THRESHOLD 16 +#endif + /*============================ MACROS ========================================*/ /* IO definitions (access restrictions to peripheral registers) */ /** @@ -192,6 +196,14 @@ void user_code_insert_to_systick_handler(void) s_lSystemClockCounts += wLoad; } +/*! \brief initialise cycle counter service + *! and don't forget to tell the function whether the systick is already + *! used by user applications. + *! Don't worry, this cycle counter service won't affect your existing + *! systick service. + *! \param bSysTickIsOccupied A boolean value which indicates whether SysTick + *! is already used by user application. + */ void init_cycle_counter(bool bSysTickIsOccupied) { if (!bSysTickIsOccupied) { @@ -205,16 +217,20 @@ void init_cycle_counter(bool bSysTickIsOccupied) __ensure_systick_wrapper(); } -/* Function : start_time - This function will be called right before starting the timed portion of the benchmark. - Implementation may be capturing a system timer (as implemented in the example code) - or zeroing some system parameters - e.g. setting the cpu clocks cycles to 0. +/*! \brief try to start the performance counter + *! \retval false the LOAD register is too small + *! \retval true performance counter starts */ -void start_cycle_counter(void) +bool start_cycle_counter(void) { + if (SysTick->LOAD < PERF_CNT_COMPENSATION_THRESHOLD) { + return false; + } + safe_atom_code(){ s_nCycleCounts = (int32_t)SysTick->VAL - (int32_t)SysTick->LOAD; } + return true; } /*! \note this function should only be called when irq is disabled @@ -225,22 +241,41 @@ static __attribute__((always_inline)) int32_t check_systick(void) { int32_t nTemp = (int32_t)SysTick->LOAD - (int32_t)SysTick->VAL; - /*! \note here is a corner case: SysTick->VAL is zero and SysTick Pending bit is set. - *! we should check this corner condition with (nTemp != SysTick->LOAD) + /*! \note Since we cannot stop counting temporarily, there are several + *! conditions which we should take into consideration: + *! Condition one: when assign nTemp with the register value (LOAD-VAL), + *! the underflow didn't happen and 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 big value) as + *! long as LOAD is bigger than (or equals to) the + *! PERF_CNT_COMPENSATION_THRESHOLD; + *! Condition two: when assign 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 Three: when initialising nTemp with the register value + *! 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 ( (SCB->ICSR & SCB_ICSR_PENDSTSET_Msk) - && (nTemp != SysTick->LOAD)) { - nTemp += SysTick->LOAD; + if (SCB->ICSR & SCB_ICSR_PENDSTSET_Msk){ + if (((int32_t)SysTick->LOAD - nTemp) >= PERF_CNT_COMPENSATION_THRESHOLD) { + nTemp += SysTick->LOAD; + } } return nTemp; } -/* Function : stop_time - This function will be called right after ending the timed portion of the benchmark. - Implementation may be capturing a system timer (as implemented in the example code) - or other system parameters - e.g. reading the current value of cpu cycles counter. -*/ +/*! \brief calculate the elapsed cycle count since the last start point + *! + *! \note you can have multiple stop_cycle_counter following one start point + *! + *! \return the elapsed cycle count. + */ int32_t stop_cycle_counter(void) { int32_t nTemp = 0; diff --git a/perf_counter.h b/perf_counter.h index e818de2..61cde2e 100644 --- a/perf_counter.h +++ b/perf_counter.h @@ -155,26 +155,21 @@ /*============================ LOCAL VARIABLES ===============================*/ /*============================ PROTOTYPES ====================================*/ -/* Function: initialise cycle counter service - * and don't forget to tell the function whether the systick is already - * used by user applications. - * Don't worry, this cycle counter service won't affect your existing - * systick service. - */ -extern void init_cycle_counter(bool bSysTickIsOccupied); -/* Function : start_time - This function will be called right before starting the timed portion of the benchmark. - Implementation may be capturing a system timer (as implemented in the example code) - or zeroing some system parameters - e.g. setting the cpu clocks cycles to 0. -*/ -extern void start_cycle_counter(void); -/* Function : stop_time - This function will be called right after ending the timed portion of the benchmark. - Implementation may be capturing a system timer (as implemented in the example code) - or other system parameters - e.g. reading the current value of cpu cycles counter. + +/*! \brief try to set a start pointer for the performance counter + *! \retval false the LOAD register is too small + *! \retval true performance counter starts */ +extern bool start_cycle_counter(void); + +/*! \brief calculate the elapsed cycle count since the last start point + *! + *! \note you can have multiple stop_cycle_counter following one start point + *! + *! \return the elapsed cycle count. + */ extern int32_t stop_cycle_counter(void); /* Function : delay specified us with the help from systick @@ -207,4 +202,55 @@ __attribute__((nothrow)) extern int64_t clock(void); #endif + +/*----------------------------------------------------------------------------* + * Please ignore the following APIs unless you have encountered some known * + * special conditions * + *----------------------------------------------------------------------------*/ + + +/*! \brief initialise cycle counter service + *! and don't forget to tell the function whether the systick is already + *! used by user applications. + *! Don't worry, this cycle counter service won't affect your existing + *! systick service. + *! + *! \note Usually the perf_counter can initialise itself with the help of + *! __attribute__((constructor(255))), this works fine in Arm Compiler + *! 5 (armcc), Arm Compiler 6 (armclang), arm gcc and llvm. It doesn't + *! work for IAR. So, when you are using IAR, please call this function + *! manually to initialise the perf_counter service. + *! + *! \note Perf_counter library assumes that: + *! a. Your project has already using SysTick + *! b. It assumes that you have already implemented the SysTick_Handler + *! c. It assumes that you have enabled the exception handling for + *! SysTick. + *! If these are not the case, please: + *! a. Add an empty SysTick_Handler to your project if you don't have + *! one + *! b. Make sure you have the SysTick Exception handling enabled + *! c. And call function init_cycle_counter(false) if you doesn't + *! use SysTick in your project at all. + *! + *! \param bSysTickIsOccupied A boolean value which indicates whether SysTick + *! is already used by user application. + */ +extern void init_cycle_counter(bool bSysTickIsOccupied); + + +/*! \note if you are using a compiler rather than armcc or armclang, e.g. iar, + *! arm gcc etc, the systick_wrapper_ual.o doesn't work with the linker + *! of your target toolchain as it use the $Super$$ which is only supported + *! by armlink. For this condition, you have to manually put this function + *! into your existing SysTick_Handler to make the perf_counter library + *! work. + *! + *! \note if you are using Arm Compiler 5 (armcc) or Arm Compiler 6 (armclang) + *! you do NOT have to insert this function into your SysTick_Handler, + *! the systick_wrapper_ual.s will do the work for you. + */ +extern void user_code_insert_to_systick_handler(void); + + #endif