mirror of
https://github.com/apache/nuttx.git
synced 2026-10-03 12:18:06 +00:00
arch/arm/stm32: fix compare-match race and zero-period hang in tickless
This patch addresses two issues in the single-timer capture/compare tickless OS drivers for STM32 families (common m3m4 v1 for F1/F2/F3/F4/G4, F7, H7, and WB): 1. Zero-period handling: when up_timer_start() is called with a zero or negative duration (or period converts to 0 ticks), the driver now enables the compare match interrupt and immediately fires an event via EGR (CCxG), avoiding missed events or unexpected counter behavior. 2. Compare-match race condition: after programming CCR and enabling the compare interrupt, a post-check validates whether the free-running counter already reached or passed count + period during register configuration. If elapsed, the interrupt is forced immediately via EGR, preventing the counter from missing the match and hanging until a full 32-bit rollover (approx. 71 minutes at 1 MHz). Verified on real hardware: - STM32H743ZI (IED R550): validated with ping, sleep, and usleep. - STM32G431KB (Nucleo-G431KB): validated with uptime, sleep, and usleep. Signed-off-by: Daniel P. Carvalho <danieloak@gmail.com>
This commit is contained in:
parent
877d1537df
commit
09423194bf
4 changed files with 253 additions and 78 deletions
|
|
@ -207,6 +207,15 @@ static inline void stm32_tickless_ackint(int channel)
|
|||
stm32_putreg16(STM32_BTIM_SR_OFFSET, ~(1 << channel));
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: stm32_tickless_trigint
|
||||
****************************************************************************/
|
||||
|
||||
static inline void stm32_tickless_trigint(int channel)
|
||||
{
|
||||
stm32_putreg16(STM32_ATIM_EGR_OFFSET, 1 << channel);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: stm32_tickless_getint
|
||||
****************************************************************************/
|
||||
|
|
@ -835,15 +844,9 @@ int up_timer_cancel(struct timespec *ts)
|
|||
(unsigned long)period, (unsigned long)count);
|
||||
|
||||
#ifndef HAVE_32BIT_TICKLESS
|
||||
if (count > period)
|
||||
{
|
||||
/* Handle rollover */
|
||||
|
||||
period += UINT16_MAX;
|
||||
}
|
||||
else if (count == period)
|
||||
if ((int16_t)(period - count) <= 0)
|
||||
#else
|
||||
if (count >= period)
|
||||
if ((int32_t)(period - count) <= 0)
|
||||
#endif
|
||||
{
|
||||
/* No time remaining */
|
||||
|
|
@ -861,8 +864,13 @@ int up_timer_cancel(struct timespec *ts)
|
|||
* usecs = (ticks * USEC_PER_SEC) / frequency;
|
||||
*/
|
||||
|
||||
usec = (((uint64_t)(period - count)) * USEC_PER_SEC) /
|
||||
#ifndef HAVE_32BIT_TICKLESS
|
||||
usec = (((uint64_t)(uint16_t)(period - count)) * USEC_PER_SEC) /
|
||||
g_tickless.frequency;
|
||||
#else
|
||||
usec = (((uint64_t)(uint32_t)(period - count)) * USEC_PER_SEC) /
|
||||
g_tickless.frequency;
|
||||
#endif
|
||||
|
||||
/* Return the time remaining in the correct form */
|
||||
|
||||
|
|
@ -929,18 +937,35 @@ int up_timer_start(const struct timespec *ts)
|
|||
|
||||
/* Express the delay in microseconds */
|
||||
|
||||
usec = ts->tv_sec * USEC_PER_SEC +
|
||||
(ts->tv_nsec / NSEC_PER_USEC);
|
||||
if (ts->tv_sec < 0 || (ts->tv_sec == 0 && ts->tv_nsec <= 0))
|
||||
{
|
||||
period = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
usec = ts->tv_sec * USEC_PER_SEC +
|
||||
(ts->tv_nsec / NSEC_PER_USEC);
|
||||
|
||||
/* Get the timer counter frequency and determine the number of counts need
|
||||
* to achieve the requested delay.
|
||||
*
|
||||
* frequency = ticks / second
|
||||
* ticks = seconds * frequency
|
||||
* = (usecs * frequency) / USEC_PER_SEC;
|
||||
*/
|
||||
/* Get the timer counter frequency and determine the number of counts
|
||||
* need to achieve the requested delay.
|
||||
*
|
||||
* frequency = ticks / second
|
||||
* ticks = seconds * frequency
|
||||
* = (usecs * frequency) / USEC_PER_SEC;
|
||||
*/
|
||||
|
||||
period = (usec * (uint64_t)g_tickless.frequency) / USEC_PER_SEC;
|
||||
}
|
||||
|
||||
if (period == 0)
|
||||
{
|
||||
stm32_tickless_enableint(g_tickless.channel);
|
||||
stm32_tickless_trigint(g_tickless.channel);
|
||||
g_tickless.pending = true;
|
||||
leave_critical_section(flags);
|
||||
return OK;
|
||||
}
|
||||
|
||||
period = (usec * (uint64_t)g_tickless.frequency) / USEC_PER_SEC;
|
||||
count = STM32_TIM_GETCOUNTER(g_tickless.tch);
|
||||
|
||||
tmrinfo("usec=%llu period=%08llx\n", usec, period);
|
||||
|
|
@ -968,6 +993,24 @@ int up_timer_start(const struct timespec *ts)
|
|||
stm32_tickless_enableint(g_tickless.channel);
|
||||
|
||||
g_tickless.pending = true;
|
||||
|
||||
/* Check if the counter already reached or passed the compare target
|
||||
* while we were configuring the registers.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_32BIT_TICKLESS
|
||||
if ((uint32_t)(STM32_TIM_GETCOUNTER(g_tickless.tch) - count) >=
|
||||
(uint32_t)period)
|
||||
#else
|
||||
if ((uint16_t)(STM32_TIM_GETCOUNTER(g_tickless.tch) - (uint16_t)count) >=
|
||||
(uint16_t)period)
|
||||
#endif
|
||||
{
|
||||
/* Target time already elapsed; force the interrupt immediately */
|
||||
|
||||
stm32_tickless_trigint(g_tickless.channel);
|
||||
}
|
||||
|
||||
leave_critical_section(flags);
|
||||
return OK;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -218,6 +218,15 @@ static inline void stm32_tickless_ackint(int channel)
|
|||
stm32_putreg16(STM32_BTIM_SR_OFFSET, ~(1 << channel));
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: stm32_tickless_trigint
|
||||
****************************************************************************/
|
||||
|
||||
static inline void stm32_tickless_trigint(int channel)
|
||||
{
|
||||
stm32_putreg16(STM32_GTIM_EGR_OFFSET, 1 << channel);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: stm32_tickless_getint
|
||||
****************************************************************************/
|
||||
|
|
@ -379,9 +388,9 @@ static int stm32_tickless_handler(int irq, void *context, void *arg)
|
|||
return OK;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SCHED_TICKLESS_ALARM
|
||||
/****************************************************************************
|
||||
* Name: stm32_get_counter
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static uint64_t stm32_get_counter(void)
|
||||
|
|
@ -394,6 +403,7 @@ static uint64_t stm32_get_counter(void)
|
|||
STM32_TIM_GETCOUNTER(g_tickless.tch);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
/****************************************************************************
|
||||
* Public Functions
|
||||
|
|
@ -877,15 +887,9 @@ int up_timer_cancel(struct timespec *ts)
|
|||
(unsigned long)period, (unsigned long)count);
|
||||
|
||||
#ifndef HAVE_32BIT_TICKLESS
|
||||
if (count > period)
|
||||
{
|
||||
/* Handle rollover */
|
||||
|
||||
period += UINT16_MAX;
|
||||
}
|
||||
else if (count == period)
|
||||
if ((int16_t)(period - count) <= 0)
|
||||
#else
|
||||
if (count >= period)
|
||||
if ((int32_t)(period - count) <= 0)
|
||||
#endif
|
||||
{
|
||||
/* No time remaining */
|
||||
|
|
@ -903,8 +907,13 @@ int up_timer_cancel(struct timespec *ts)
|
|||
* usecs = (ticks * USEC_PER_SEC) / frequency;
|
||||
*/
|
||||
|
||||
usec = (((uint64_t)(period - count)) * USEC_PER_SEC) /
|
||||
#ifndef HAVE_32BIT_TICKLESS
|
||||
usec = (((uint64_t)(uint16_t)(period - count)) * USEC_PER_SEC) /
|
||||
g_tickless.frequency;
|
||||
#else
|
||||
usec = (((uint64_t)(uint32_t)(period - count)) * USEC_PER_SEC) /
|
||||
g_tickless.frequency;
|
||||
#endif
|
||||
|
||||
/* Return the time remaining in the correct form */
|
||||
|
||||
|
|
@ -973,18 +982,35 @@ int up_timer_start(const struct timespec *ts)
|
|||
|
||||
/* Express the delay in microseconds */
|
||||
|
||||
usec = ts->tv_sec * USEC_PER_SEC +
|
||||
(ts->tv_nsec / NSEC_PER_USEC);
|
||||
if (ts->tv_sec < 0 || (ts->tv_sec == 0 && ts->tv_nsec <= 0))
|
||||
{
|
||||
period = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
usec = ts->tv_sec * USEC_PER_SEC +
|
||||
(ts->tv_nsec / NSEC_PER_USEC);
|
||||
|
||||
/* Get the timer counter frequency and determine the number of counts need
|
||||
* to achieve the requested delay.
|
||||
*
|
||||
* frequency = ticks / second
|
||||
* ticks = seconds * frequency
|
||||
* = (usecs * frequency) / USEC_PER_SEC;
|
||||
*/
|
||||
/* Get the timer counter frequency and determine the number of counts
|
||||
* need to achieve the requested delay.
|
||||
*
|
||||
* frequency = ticks / second
|
||||
* ticks = seconds * frequency
|
||||
* = (usecs * frequency) / USEC_PER_SEC;
|
||||
*/
|
||||
|
||||
period = (usec * (uint64_t)g_tickless.frequency) / USEC_PER_SEC;
|
||||
}
|
||||
|
||||
if (period == 0)
|
||||
{
|
||||
stm32_tickless_enableint(g_tickless.channel);
|
||||
stm32_tickless_trigint(g_tickless.channel);
|
||||
g_tickless.pending = true;
|
||||
leave_critical_section(flags);
|
||||
return OK;
|
||||
}
|
||||
|
||||
period = (usec * (uint64_t)g_tickless.frequency) / USEC_PER_SEC;
|
||||
count = STM32_TIM_GETCOUNTER(g_tickless.tch);
|
||||
|
||||
tmrinfo("usec=%llu period=%08llx\n", usec, period);
|
||||
|
|
@ -1012,6 +1038,24 @@ int up_timer_start(const struct timespec *ts)
|
|||
stm32_tickless_enableint(g_tickless.channel);
|
||||
|
||||
g_tickless.pending = true;
|
||||
|
||||
/* Check if the counter already reached or passed the compare target
|
||||
* while we were configuring the registers.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_32BIT_TICKLESS
|
||||
if ((uint32_t)(STM32_TIM_GETCOUNTER(g_tickless.tch) - count) >=
|
||||
(uint32_t)period)
|
||||
#else
|
||||
if ((uint16_t)(STM32_TIM_GETCOUNTER(g_tickless.tch) - (uint16_t)count) >=
|
||||
(uint16_t)period)
|
||||
#endif
|
||||
{
|
||||
/* Target time already elapsed; force the interrupt immediately */
|
||||
|
||||
stm32_tickless_trigint(g_tickless.channel);
|
||||
}
|
||||
|
||||
leave_critical_section(flags);
|
||||
return OK;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -205,6 +205,15 @@ static inline void stm32_tickless_ackint(int channel)
|
|||
stm32_putreg16(STM32_BTIM_SR_OFFSET, ~(1 << channel));
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: stm32_tickless_trigint
|
||||
****************************************************************************/
|
||||
|
||||
static inline void stm32_tickless_trigint(int channel)
|
||||
{
|
||||
stm32_putreg16(STM32_GTIM_EGR_OFFSET, 1 << channel);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: stm32_tickless_getint
|
||||
****************************************************************************/
|
||||
|
|
@ -366,9 +375,9 @@ static int stm32_tickless_handler(int irq, void *context, void *arg)
|
|||
return OK;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SCHED_TICKLESS_ALARM
|
||||
/****************************************************************************
|
||||
* Name: stm32_get_counter
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static uint64_t stm32_get_counter(void)
|
||||
|
|
@ -381,6 +390,7 @@ static uint64_t stm32_get_counter(void)
|
|||
STM32_TIM_GETCOUNTER(g_tickless.tch);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
/****************************************************************************
|
||||
* Public Functions
|
||||
|
|
@ -851,15 +861,9 @@ int up_timer_cancel(struct timespec *ts)
|
|||
(unsigned long)period, (unsigned long)count);
|
||||
|
||||
#ifndef HAVE_32BIT_TICKLESS
|
||||
if (count > period)
|
||||
{
|
||||
/* Handle rollover */
|
||||
|
||||
period += UINT16_MAX;
|
||||
}
|
||||
else if (count == period)
|
||||
if ((int16_t)(period - count) <= 0)
|
||||
#else
|
||||
if (count >= period)
|
||||
if ((int32_t)(period - count) <= 0)
|
||||
#endif
|
||||
{
|
||||
/* No time remaining */
|
||||
|
|
@ -877,8 +881,13 @@ int up_timer_cancel(struct timespec *ts)
|
|||
* usecs = (ticks * USEC_PER_SEC) / frequency;
|
||||
*/
|
||||
|
||||
usec = (((uint64_t)(period - count)) * USEC_PER_SEC) /
|
||||
#ifndef HAVE_32BIT_TICKLESS
|
||||
usec = (((uint64_t)(uint16_t)(period - count)) * USEC_PER_SEC) /
|
||||
g_tickless.frequency;
|
||||
#else
|
||||
usec = (((uint64_t)(uint32_t)(period - count)) * USEC_PER_SEC) /
|
||||
g_tickless.frequency;
|
||||
#endif
|
||||
|
||||
/* Return the time remaining in the correct form */
|
||||
|
||||
|
|
@ -947,18 +956,35 @@ int up_timer_start(const struct timespec *ts)
|
|||
|
||||
/* Express the delay in microseconds */
|
||||
|
||||
usec = ts->tv_sec * USEC_PER_SEC +
|
||||
(ts->tv_nsec / NSEC_PER_USEC);
|
||||
if (ts->tv_sec < 0 || (ts->tv_sec == 0 && ts->tv_nsec <= 0))
|
||||
{
|
||||
period = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
usec = ts->tv_sec * USEC_PER_SEC +
|
||||
(ts->tv_nsec / NSEC_PER_USEC);
|
||||
|
||||
/* Get the timer counter frequency and determine the number of counts need
|
||||
* to achieve the requested delay.
|
||||
*
|
||||
* frequency = ticks / second
|
||||
* ticks = seconds * frequency
|
||||
* = (usecs * frequency) / USEC_PER_SEC;
|
||||
*/
|
||||
/* Get the timer counter frequency and determine the number of counts
|
||||
* need to achieve the requested delay.
|
||||
*
|
||||
* frequency = ticks / second
|
||||
* ticks = seconds * frequency
|
||||
* = (usecs * frequency) / USEC_PER_SEC;
|
||||
*/
|
||||
|
||||
period = (usec * (uint64_t)g_tickless.frequency) / USEC_PER_SEC;
|
||||
}
|
||||
|
||||
if (period == 0)
|
||||
{
|
||||
stm32_tickless_enableint(g_tickless.channel);
|
||||
stm32_tickless_trigint(g_tickless.channel);
|
||||
g_tickless.pending = true;
|
||||
leave_critical_section(flags);
|
||||
return OK;
|
||||
}
|
||||
|
||||
period = (usec * (uint64_t)g_tickless.frequency) / USEC_PER_SEC;
|
||||
count = STM32_TIM_GETCOUNTER(g_tickless.tch);
|
||||
|
||||
tmrinfo("usec=%llu period=%08llx\n", usec, period);
|
||||
|
|
@ -986,6 +1012,24 @@ int up_timer_start(const struct timespec *ts)
|
|||
stm32_tickless_enableint(g_tickless.channel);
|
||||
|
||||
g_tickless.pending = true;
|
||||
|
||||
/* Check if the counter already reached or passed the compare target
|
||||
* while we were configuring the registers.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_32BIT_TICKLESS
|
||||
if ((uint32_t)(STM32_TIM_GETCOUNTER(g_tickless.tch) - count) >=
|
||||
(uint32_t)period)
|
||||
#else
|
||||
if ((uint16_t)(STM32_TIM_GETCOUNTER(g_tickless.tch) - (uint16_t)count) >=
|
||||
(uint16_t)period)
|
||||
#endif
|
||||
{
|
||||
/* Target time already elapsed; force the interrupt immediately */
|
||||
|
||||
stm32_tickless_trigint(g_tickless.channel);
|
||||
}
|
||||
|
||||
leave_critical_section(flags);
|
||||
return OK;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -187,6 +187,15 @@ static inline void stm32_tickless_ackint(int channel)
|
|||
stm32_putreg16(STM32_TIM_SR_OFFSET, ~(1 << channel));
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: stm32_tickless_trigint
|
||||
****************************************************************************/
|
||||
|
||||
static inline void stm32_tickless_trigint(int channel)
|
||||
{
|
||||
stm32_putreg16(STM32_TIM_EGR_OFFSET, 1 << channel);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: stm32_tickless_getint
|
||||
****************************************************************************/
|
||||
|
|
@ -701,15 +710,9 @@ int up_timer_cancel(struct timespec *ts)
|
|||
(unsigned long)period, (unsigned long)count);
|
||||
|
||||
#ifndef HAVE_32BIT_TICKLESS
|
||||
if (count > period)
|
||||
{
|
||||
/* Handle rollover */
|
||||
|
||||
period += UINT16_MAX;
|
||||
}
|
||||
else if (count == period)
|
||||
if ((int16_t)(period - count) <= 0)
|
||||
#else
|
||||
if (count >= period)
|
||||
if ((int32_t)(period - count) <= 0)
|
||||
#endif
|
||||
{
|
||||
/* No time remaining */
|
||||
|
|
@ -727,8 +730,13 @@ int up_timer_cancel(struct timespec *ts)
|
|||
* usecs = (ticks * USEC_PER_SEC) / frequency;
|
||||
*/
|
||||
|
||||
usec = (((uint64_t)(period - count)) * USEC_PER_SEC) /
|
||||
#ifndef HAVE_32BIT_TICKLESS
|
||||
usec = (((uint64_t)(uint16_t)(period - count)) * USEC_PER_SEC) /
|
||||
g_tickless.frequency;
|
||||
#else
|
||||
usec = (((uint64_t)(uint32_t)(period - count)) * USEC_PER_SEC) /
|
||||
g_tickless.frequency;
|
||||
#endif
|
||||
|
||||
/* Return the time remaining in the correct form */
|
||||
|
||||
|
|
@ -795,18 +803,35 @@ int up_timer_start(const struct timespec *ts)
|
|||
|
||||
/* Express the delay in microseconds */
|
||||
|
||||
usec = ts->tv_sec * USEC_PER_SEC +
|
||||
(ts->tv_nsec / NSEC_PER_USEC);
|
||||
if (ts->tv_sec < 0 || (ts->tv_sec == 0 && ts->tv_nsec <= 0))
|
||||
{
|
||||
period = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
usec = ts->tv_sec * USEC_PER_SEC +
|
||||
(ts->tv_nsec / NSEC_PER_USEC);
|
||||
|
||||
/* Get the timer counter frequency and determine the number of counts need
|
||||
* to achieve the requested delay.
|
||||
*
|
||||
* frequency = ticks / second
|
||||
* ticks = seconds * frequency
|
||||
* = (usecs * frequency) / USEC_PER_SEC;
|
||||
*/
|
||||
/* Get the timer counter frequency and determine the number of counts
|
||||
* need to achieve the requested delay.
|
||||
*
|
||||
* frequency = ticks / second
|
||||
* ticks = seconds * frequency
|
||||
* = (usecs * frequency) / USEC_PER_SEC;
|
||||
*/
|
||||
|
||||
period = (usec * (uint64_t)g_tickless.frequency) / USEC_PER_SEC;
|
||||
}
|
||||
|
||||
if (period == 0)
|
||||
{
|
||||
stm32_tickless_enableint(g_tickless.channel);
|
||||
stm32_tickless_trigint(g_tickless.channel);
|
||||
g_tickless.pending = true;
|
||||
leave_critical_section(flags);
|
||||
return OK;
|
||||
}
|
||||
|
||||
period = (usec * (uint64_t)g_tickless.frequency) / USEC_PER_SEC;
|
||||
count = STM32_TIM_GETCOUNTER(g_tickless.tch);
|
||||
|
||||
tmrinfo("usec=%llu period=%08llx\n", usec, period);
|
||||
|
|
@ -814,6 +839,7 @@ int up_timer_start(const struct timespec *ts)
|
|||
/* Set interval compare value. Rollover is fine,
|
||||
* channel will trigger on the next period.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_32BIT_TICKLESS
|
||||
DEBUGASSERT(period <= UINT32_MAX);
|
||||
g_tickless.period = (uint32_t)(period + count);
|
||||
|
|
@ -833,6 +859,24 @@ int up_timer_start(const struct timespec *ts)
|
|||
stm32_tickless_enableint(g_tickless.channel);
|
||||
|
||||
g_tickless.pending = true;
|
||||
|
||||
/* Check if the counter already reached or passed the compare target
|
||||
* while we were configuring the registers.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_32BIT_TICKLESS
|
||||
if ((uint32_t)(STM32_TIM_GETCOUNTER(g_tickless.tch) - count) >=
|
||||
(uint32_t)period)
|
||||
#else
|
||||
if ((uint16_t)(STM32_TIM_GETCOUNTER(g_tickless.tch) - (uint16_t)count) >=
|
||||
(uint16_t)period)
|
||||
#endif
|
||||
{
|
||||
/* Target time already elapsed; force the interrupt immediately */
|
||||
|
||||
stm32_tickless_trigint(g_tickless.channel);
|
||||
}
|
||||
|
||||
leave_critical_section(flags);
|
||||
return OK;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue