Skip to content

Commit f8d68ef

Browse files
committed
Merge branch 'bugfix/atomic_gptimer_fsm' into 'master'
gptimer: fix race condition between start and stop See merge request espressif/esp-idf!22620
2 parents d5f53fb + 2d52334 commit f8d68ef

File tree

11 files changed

+153
-74
lines changed

11 files changed

+153
-74
lines changed

components/driver/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ endif()
7575
# GPTimer related source files
7676
if(CONFIG_SOC_GPTIMER_SUPPORTED)
7777
list(APPEND srcs "gptimer/gptimer.c"
78+
"gptimer/gptimer_priv.c"
7879
"deprecated/timer_legacy.c")
7980
endif()
8081

components/driver/gptimer/README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# GPTimer Driver Design
2+
3+
## State Transition
4+
5+
> State transition is achieved by using the primitives provided by `<stdatomic.h>`.
6+
7+
```mermaid
8+
stateDiagram-v2
9+
[*] --> init: gptimer_new_timer
10+
init --> enable: gptimer_enable
11+
enable --> init: gptimer_disable
12+
enable --> run: gptimer_start*
13+
run --> enable: gptimer_stop*
14+
init --> [*]: gptimer_del_timer
15+
```
16+
17+
Other functions won't change the driver state. The functions above labeled with `*` are allowed to be used in the interrupt context.
18+
19+
## Concurrency
20+
21+
There might be race conditions when the user calls the APIs from a thread and interrupt at the same time. e.g. a Task is just running the `gptimer_start`, and suddenly an interrupt occurs, where the user calls `gptimer_stop` for the same timer handle. Which is possible to make a "stopped" timer continue to run if the interrupt is returned before the Task.
22+
23+
```mermaid
24+
stateDiagram-v2
25+
state Race-Condition {
26+
Thread --> gptimer_start
27+
state gptimer_start {
28+
state is_enabled <<choice>>
29+
[*] --> is_enabled: Enabled?
30+
is_enabled --> run_wait: yes
31+
is_enabled --> [*] : no
32+
run_wait --> run: call HAL/LL functions to start timer
33+
}
34+
--
35+
Interrupt --> gptimer_stop
36+
state gptimer_stop {
37+
state is_running <<choice>>
38+
[*] --> is_running: Running?
39+
is_running --> enable_wait: yes
40+
is_running --> [*] : no
41+
enable_wait --> enable: call HAL/LL functions to stop timer
42+
}
43+
}
44+
```
45+
46+
By introducing a "middle" state like `run_wait` and `enable_wait`, we make sure that the timer is in a safe state before we start/stop it. And if the state is invalid, it can return an error code to the user.

components/driver/gptimer/gptimer.c

Lines changed: 35 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,8 @@ esp_err_t gptimer_new_timer(const gptimer_config_t *config, gptimer_handle_t *re
136136
portEXIT_CRITICAL(&group->spinlock);
137137
// initialize other members of timer
138138
timer->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
139-
timer->fsm = GPTIMER_FSM_INIT; // put the timer into init state
139+
// put the timer driver to the init state
140+
atomic_init(&timer->fsm, GPTIMER_FSM_INIT);
140141
timer->direction = config->direction;
141142
timer->flags.intr_shared = config->flags.intr_shared;
142143
ESP_LOGD(TAG, "new gptimer (%d,%d) at %p, resolution=%"PRIu32"Hz", group_id, timer_id, timer, timer->resolution_hz);
@@ -153,7 +154,7 @@ esp_err_t gptimer_new_timer(const gptimer_config_t *config, gptimer_handle_t *re
153154
esp_err_t gptimer_del_timer(gptimer_handle_t timer)
154155
{
155156
ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
156-
ESP_RETURN_ON_FALSE(timer->fsm == GPTIMER_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "timer not in init state");
157+
ESP_RETURN_ON_FALSE(atomic_load(&timer->fsm) == GPTIMER_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "timer not in init state");
157158
gptimer_group_t *group = timer->group;
158159
gptimer_clock_source_t clk_src = timer->clk_src;
159160
int group_id = group->group_id;
@@ -231,7 +232,7 @@ esp_err_t gptimer_register_event_callbacks(gptimer_handle_t timer, const gptimer
231232

232233
// lazy install interrupt service
233234
if (!timer->intr) {
234-
ESP_RETURN_ON_FALSE(timer->fsm == GPTIMER_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "timer not in init state");
235+
ESP_RETURN_ON_FALSE(atomic_load(&timer->fsm) == GPTIMER_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "timer not in init state");
235236
// if user wants to control the interrupt allocation more precisely, we can expose more flags in `gptimer_config_t`
236237
int isr_flags = timer->flags.intr_shared ? ESP_INTR_FLAG_SHARED | GPTIMER_INTR_ALLOC_FLAGS : GPTIMER_INTR_ALLOC_FLAGS;
237238
ESP_RETURN_ON_ERROR(esp_intr_alloc_intrstatus(timer_group_periph_signals.groups[group_id].timer_irq_id[timer_id], isr_flags,
@@ -283,63 +284,79 @@ esp_err_t gptimer_set_alarm_action(gptimer_handle_t timer, const gptimer_alarm_c
283284
esp_err_t gptimer_enable(gptimer_handle_t timer)
284285
{
285286
ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
286-
ESP_RETURN_ON_FALSE(timer->fsm == GPTIMER_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "timer not in init state");
287+
gptimer_fsm_t expected_fsm = GPTIMER_FSM_INIT;
288+
ESP_RETURN_ON_FALSE(atomic_compare_exchange_strong(&timer->fsm, &expected_fsm, GPTIMER_FSM_ENABLE),
289+
ESP_ERR_INVALID_STATE, TAG, "timer not in init state");
287290

288291
// acquire power manager lock
289292
if (timer->pm_lock) {
290293
ESP_RETURN_ON_ERROR(esp_pm_lock_acquire(timer->pm_lock), TAG, "acquire pm_lock failed");
291294
}
295+
292296
// enable interrupt service
293297
if (timer->intr) {
294298
ESP_RETURN_ON_ERROR(esp_intr_enable(timer->intr), TAG, "enable interrupt service failed");
295299
}
296300

297-
timer->fsm = GPTIMER_FSM_ENABLE;
298301
return ESP_OK;
299302
}
300303

301304
esp_err_t gptimer_disable(gptimer_handle_t timer)
302305
{
303306
ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
304-
ESP_RETURN_ON_FALSE(timer->fsm == GPTIMER_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "timer not in enable state");
307+
gptimer_fsm_t expected_fsm = GPTIMER_FSM_ENABLE;
308+
ESP_RETURN_ON_FALSE(atomic_compare_exchange_strong(&timer->fsm, &expected_fsm, GPTIMER_FSM_INIT),
309+
ESP_ERR_INVALID_STATE, TAG, "timer not in enable state");
305310

306311
// disable interrupt service
307312
if (timer->intr) {
308313
ESP_RETURN_ON_ERROR(esp_intr_disable(timer->intr), TAG, "disable interrupt service failed");
309314
}
315+
310316
// release power manager lock
311317
if (timer->pm_lock) {
312318
ESP_RETURN_ON_ERROR(esp_pm_lock_release(timer->pm_lock), TAG, "release pm_lock failed");
313319
}
314320

315-
timer->fsm = GPTIMER_FSM_INIT;
316321
return ESP_OK;
317322
}
318323

319324
esp_err_t gptimer_start(gptimer_handle_t timer)
320325
{
321326
ESP_RETURN_ON_FALSE_ISR(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
322-
ESP_RETURN_ON_FALSE_ISR(timer->fsm == GPTIMER_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "timer not enabled yet");
323327

324-
portENTER_CRITICAL_SAFE(&timer->spinlock);
325-
timer_ll_enable_counter(timer->hal.dev, timer->timer_id, true);
326-
timer_ll_enable_alarm(timer->hal.dev, timer->timer_id, timer->flags.alarm_en);
327-
portEXIT_CRITICAL_SAFE(&timer->spinlock);
328+
gptimer_fsm_t expected_fsm = GPTIMER_FSM_ENABLE;
329+
if (atomic_compare_exchange_strong(&timer->fsm, &expected_fsm, GPTIMER_FSM_RUN_WAIT)) {
330+
// the register used by the following LL functions are shared with other API,
331+
// which is possible to run along with this function, so we need to protect
332+
portENTER_CRITICAL_SAFE(&timer->spinlock);
333+
timer_ll_enable_counter(timer->hal.dev, timer->timer_id, true);
334+
timer_ll_enable_alarm(timer->hal.dev, timer->timer_id, timer->flags.alarm_en);
335+
portEXIT_CRITICAL_SAFE(&timer->spinlock);
336+
} else {
337+
ESP_RETURN_ON_FALSE_ISR(false, ESP_ERR_INVALID_STATE, TAG, "timer is not enabled yet");
338+
}
328339

340+
atomic_store(&timer->fsm, GPTIMER_FSM_RUN);
329341
return ESP_OK;
330342
}
331343

332344
esp_err_t gptimer_stop(gptimer_handle_t timer)
333345
{
334346
ESP_RETURN_ON_FALSE_ISR(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
335-
ESP_RETURN_ON_FALSE_ISR(timer->fsm == GPTIMER_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "timer not enabled yet");
336347

337-
// disable counter, alarm, auto-reload
338-
portENTER_CRITICAL_SAFE(&timer->spinlock);
339-
timer_ll_enable_counter(timer->hal.dev, timer->timer_id, false);
340-
timer_ll_enable_alarm(timer->hal.dev, timer->timer_id, false);
341-
portEXIT_CRITICAL_SAFE(&timer->spinlock);
348+
gptimer_fsm_t expected_fsm = GPTIMER_FSM_RUN;
349+
if (atomic_compare_exchange_strong(&timer->fsm, &expected_fsm, GPTIMER_FSM_ENABLE_WAIT)) {
350+
// disable counter, alarm, auto-reload
351+
portENTER_CRITICAL_SAFE(&timer->spinlock);
352+
timer_ll_enable_counter(timer->hal.dev, timer->timer_id, false);
353+
timer_ll_enable_alarm(timer->hal.dev, timer->timer_id, false);
354+
portEXIT_CRITICAL_SAFE(&timer->spinlock);
355+
} else {
356+
ESP_RETURN_ON_FALSE_ISR(false, ESP_ERR_INVALID_STATE, TAG, "timer is not running");
357+
}
342358

359+
atomic_store(&timer->fsm, GPTIMER_FSM_ENABLE);
343360
return ESP_OK;
344361
}
345362

@@ -498,21 +515,3 @@ IRAM_ATTR static void gptimer_default_isr(void *args)
498515
portYIELD_FROM_ISR();
499516
}
500517
}
501-
502-
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
503-
///// The Following APIs are for internal use only (e.g. unit test) /////////////////////////////////////////////////
504-
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
505-
506-
esp_err_t gptimer_get_intr_handle(gptimer_handle_t timer, intr_handle_t *ret_intr_handle)
507-
{
508-
ESP_RETURN_ON_FALSE(timer && ret_intr_handle, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
509-
*ret_intr_handle = timer->intr;
510-
return ESP_OK;
511-
}
512-
513-
esp_err_t gptimer_get_pm_lock(gptimer_handle_t timer, esp_pm_lock_handle_t *ret_pm_lock)
514-
{
515-
ESP_RETURN_ON_FALSE(timer && ret_pm_lock, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
516-
*ret_pm_lock = timer->pm_lock;
517-
return ESP_OK;
518-
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include "esp_check.h"
8+
#include "esp_private/gptimer.h"
9+
#include "gptimer_priv.h"
10+
11+
static const char *TAG = "gptimer";
12+
13+
esp_err_t gptimer_get_intr_handle(gptimer_handle_t timer, intr_handle_t *ret_intr_handle)
14+
{
15+
ESP_RETURN_ON_FALSE(timer && ret_intr_handle, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
16+
*ret_intr_handle = timer->intr;
17+
return ESP_OK;
18+
}
19+
20+
esp_err_t gptimer_get_pm_lock(gptimer_handle_t timer, esp_pm_lock_handle_t *ret_pm_lock)
21+
{
22+
ESP_RETURN_ON_FALSE(timer && ret_pm_lock, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
23+
*ret_pm_lock = timer->pm_lock;
24+
return ESP_OK;
25+
}

components/driver/gptimer/gptimer_priv.h

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
/*
2-
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66

77
#pragma once
88

99
#include <stdint.h>
10+
#include <stdatomic.h>
1011
#include "sdkconfig.h"
1112
#include "freertos/FreeRTOS.h"
1213
#include "esp_err.h"
@@ -45,8 +46,11 @@ typedef struct gptimer_group_t {
4546
} gptimer_group_t;
4647

4748
typedef enum {
48-
GPTIMER_FSM_INIT,
49-
GPTIMER_FSM_ENABLE,
49+
GPTIMER_FSM_INIT, // Timer is initialized, but not enabled
50+
GPTIMER_FSM_ENABLE, // Timer is enabled, but is not running
51+
GPTIMER_FSM_ENABLE_WAIT, // Timer is in the middle of the enable process (Intermediate state)
52+
GPTIMER_FSM_RUN, // Timer is in running
53+
GPTIMER_FSM_RUN_WAIT, // Timer is in the middle of the run process (Intermediate state)
5054
} gptimer_fsm_t;
5155

5256
struct gptimer_t {
@@ -57,7 +61,7 @@ struct gptimer_t {
5761
uint64_t alarm_count;
5862
gptimer_count_direction_t direction;
5963
timer_hal_context_t hal;
60-
gptimer_fsm_t fsm;
64+
_Atomic gptimer_fsm_t fsm;
6165
intr_handle_t intr;
6266
portMUX_TYPE spinlock; // to protect per-timer resources concurrent accessed by task and ISR handler
6367
gptimer_alarm_cb_t on_alarm;

components/driver/gptimer/include/driver/gptimer.h

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ typedef struct {
3232
/**
3333
* @brief Create a new General Purpose Timer, and return the handle
3434
*
35-
* @note The newly created timer is put in the init state.
35+
* @note The newly created timer is put in the "init" state.
3636
*
3737
* @param[in] config GPTimer configuration
3838
* @param[out] ret_timer Returned timer handle
@@ -48,8 +48,7 @@ esp_err_t gptimer_new_timer(const gptimer_config_t *config, gptimer_handle_t *re
4848
/**
4949
* @brief Delete the GPTimer handle
5050
*
51-
* @note A timer can't be in the enable state when this function is invoked.
52-
* See also `gptimer_disable` for how to disable a timer.
51+
* @note A timer must be in the "init" state before it can be deleted.
5352
*
5453
* @param[in] timer Timer handle created by `gptimer_new_timer`
5554
* @return
@@ -65,7 +64,8 @@ esp_err_t gptimer_del_timer(gptimer_handle_t timer);
6564
*
6665
* @note When updating the raw count of an active timer, the timer will immediately start counting from the new value.
6766
* @note This function is allowed to run within ISR context
68-
* @note This function is allowed to be executed when Cache is disabled, by enabling `CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM`
67+
* @note If `CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` is enabled, this function will be placed in the IRAM by linker,
68+
* makes it possible to execute even when the Flash Cache is disabled.
6969
*
7070
* @param[in] timer Timer handle created by `gptimer_new_timer`
7171
* @param[in] value Count value to be set
@@ -82,7 +82,8 @@ esp_err_t gptimer_set_raw_count(gptimer_handle_t timer, uint64_t value);
8282
* @note This function will trigger a software capture event and then return the captured count value.
8383
* @note With the raw count value and the resolution returned from `gptimer_get_resolution`, you can convert the count value into seconds.
8484
* @note This function is allowed to run within ISR context
85-
* @note This function is allowed to be executed when Cache is disabled, by enabling `CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM`
85+
* @note If `CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` is enabled, this function will be placed in the IRAM by linker,
86+
* makes it possible to execute even when the Flash Cache is disabled.
8687
*
8788
* @param[in] timer Timer handle created by `gptimer_new_timer`
8889
* @param[out] value Returned GPTimer count value
@@ -96,7 +97,8 @@ esp_err_t gptimer_get_raw_count(gptimer_handle_t timer, uint64_t *value);
9697
/**
9798
* @brief Return the real resolution of the timer
9899
*
99-
* @note usually the timer resolution is same as what you configured in the `gptimer_config_t::resolution_hz`, but for some unstable clock source (e.g. RC_FAST), which needs a calibration, the real resolution may be different from the configured one.
100+
* @note usually the timer resolution is same as what you configured in the `gptimer_config_t::resolution_hz`,
101+
* but some unstable clock source (e.g. RC_FAST) will do a calibration, the real resolution can be different from the configured one.
100102
*
101103
* @param[in] timer Timer handle created by `gptimer_new_timer`
102104
* @param[out] out_resolution Returned timer resolution, in Hz
@@ -110,9 +112,10 @@ esp_err_t gptimer_get_resolution(gptimer_handle_t timer, uint32_t *out_resolutio
110112
/**
111113
* @brief Get GPTimer captured count value
112114
*
113-
* @note The capture action can be issued either by external event or by software (see also `gptimer_get_raw_count`).
115+
* @note The capture action can be issued either by ETM event or by software (see also `gptimer_get_raw_count`).
114116
* @note This function is allowed to run within ISR context
115-
* @note This function is allowed to be executed when Cache is disabled, by enabling `CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM`
117+
* @note If `CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` is enabled, this function will be placed in the IRAM by linker,
118+
* makes it possible to execute even when the Flash Cache is disabled.
116119
*
117120
* @param[in] timer Timer handle created by `gptimer_new_timer`
118121
* @param[out] value Returned captured count value
@@ -165,7 +168,8 @@ typedef struct {
165168
* @brief Set alarm event actions for GPTimer.
166169
*
167170
* @note This function is allowed to run within ISR context, so that user can set new alarm action immediately in the ISR callback.
168-
* @note This function is allowed to be executed when Cache is disabled, by enabling `CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM`
171+
* @note If `CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` is enabled, this function will be placed in the IRAM by linker,
172+
* makes it possible to execute even when the Flash Cache is disabled.
169173
*
170174
* @param[in] timer Timer handle created by `gptimer_new_timer`
171175
* @param[in] config Alarm configuration, especially, set config to NULL means disabling the alarm function
@@ -179,7 +183,7 @@ esp_err_t gptimer_set_alarm_action(gptimer_handle_t timer, const gptimer_alarm_c
179183
/**
180184
* @brief Enable GPTimer
181185
*
182-
* @note This function will transit the timer state from init to enable.
186+
* @note This function will transit the timer state from "init" to "enable".
183187
* @note This function will enable the interrupt service, if it's lazy installed in `gptimer_register_event_callbacks`.
184188
* @note This function will acquire a PM lock, if a specific source clock (e.g. APB) is selected in the `gptimer_config_t`, while `CONFIG_PM_ENABLE` is enabled.
185189
* @note Enable a timer doesn't mean to start it. See also `gptimer_start` for how to make the timer start counting.
@@ -196,7 +200,9 @@ esp_err_t gptimer_enable(gptimer_handle_t timer);
196200
/**
197201
* @brief Disable GPTimer
198202
*
199-
* @note This function will do the opposite work to the `gptimer_enable`
203+
* @note This function will transit the timer state from "enable" to "init".
204+
* @note This function will disable the interrupt service if it's installed.
205+
* @note This function will release the PM lock if it's acquired in the `gptimer_enable`.
200206
* @note Disable a timer doesn't mean to stop it. See also `gptimer_stop` for how to make the timer stop counting.
201207
*
202208
* @param[in] timer Timer handle created by `gptimer_new_timer`
@@ -211,31 +217,33 @@ esp_err_t gptimer_disable(gptimer_handle_t timer);
211217
/**
212218
* @brief Start GPTimer (internal counter starts counting)
213219
*
214-
* @note This function should be called when the timer is in the enable state (i.e. after calling `gptimer_enable`)
220+
* @note This function will transit the timer state from "enable" to "run".
215221
* @note This function is allowed to run within ISR context
216-
* @note This function will be placed into IRAM if `CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` is on, so that it's allowed to be executed when Cache is disabled
222+
* @note If `CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` is enabled, this function will be placed in the IRAM by linker,
223+
* makes it possible to execute even when the Flash Cache is disabled.
217224
*
218225
* @param[in] timer Timer handle created by `gptimer_new_timer`
219226
* @return
220227
* - ESP_OK: Start GPTimer successfully
221228
* - ESP_ERR_INVALID_ARG: Start GPTimer failed because of invalid argument
222-
* - ESP_ERR_INVALID_STATE: Start GPTimer failed because the timer is not enabled yet
229+
* - ESP_ERR_INVALID_STATE: Start GPTimer failed because the timer is not enabled or is already in running
223230
* - ESP_FAIL: Start GPTimer failed because of other error
224231
*/
225232
esp_err_t gptimer_start(gptimer_handle_t timer);
226233

227234
/**
228235
* @brief Stop GPTimer (internal counter stops counting)
229236
*
230-
* @note This function should be called when the timer is in the enable state (i.e. after calling `gptimer_enable`)
237+
* @note This function will transit the timer state from "run" to "enable".
231238
* @note This function is allowed to run within ISR context
232-
* @note This function will be placed into IRAM if `CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` is on, so that it's allowed to be executed when Cache is disabled
239+
* @note If `CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` is enabled, this function will be placed in the IRAM by linker,
240+
* makes it possible to execute even when the Flash Cache is disabled.
233241
*
234242
* @param[in] timer Timer handle created by `gptimer_new_timer`
235243
* @return
236244
* - ESP_OK: Stop GPTimer successfully
237245
* - ESP_ERR_INVALID_ARG: Stop GPTimer failed because of invalid argument
238-
* - ESP_ERR_INVALID_STATE: Stop GPTimer failed because the timer is not enabled yet
246+
* - ESP_ERR_INVALID_STATE: Stop GPTimer failed because the timer is not in running.
239247
* - ESP_FAIL: Stop GPTimer failed because of other error
240248
*/
241249
esp_err_t gptimer_stop(gptimer_handle_t timer);

0 commit comments

Comments
 (0)