drivers: clock_control: nrf: Fix race condition in clock start
If clock started immediately after requesting then request was
not yet placed in the list and user callback was not called.
Signed-off-by: Krzysztof Chruscinski <krzysztof.chruscinski@nordicsemi.no>
Signed-off-by: Peter Bigot <peter.bigot@nordicsemi.no>
diff --git a/drivers/clock_control/nrf_power_clock.c b/drivers/clock_control/nrf_power_clock.c
index 14f89d8..17ccb8a 100644
--- a/drivers/clock_control/nrf_power_clock.c
+++ b/drivers/clock_control/nrf_power_clock.c
@@ -45,6 +45,8 @@
nrf_clock_task_t stop_tsk; /* Clock stop task */
};
+static void clkstarted_handle(struct device *dev);
+
/* Return true if given event has enabled interrupt and is triggered. Event
* is cleared.
*/
@@ -145,38 +147,52 @@
__ASSERT_NO_MSG((data == NULL) ||
((data != NULL) && (data->cb != NULL)));
+ /* if node is in the list it means that it is scheduled for
+ * the second time.
+ */
+ if ((data != NULL)
+ && is_in_list(&clk_data->list, &data->node)) {
+ return -EBUSY;
+ }
+
key = irq_lock();
ref = ++clk_data->ref;
irq_unlock(key);
- if (clk_data->started) {
- if (data) {
+ if (data) {
+ bool already_started;
+
+ key = irq_lock();
+ already_started = clk_data->started;
+ if (!already_started) {
+ sys_slist_append(&clk_data->list, &data->node);
+ }
+ irq_unlock(key);
+
+ if (already_started) {
data->cb(dev, data->user_data);
}
- } else {
- if (ref == 1) {
- bool do_start;
+ }
- do_start = (config->start_handler) ?
- config->start_handler(dev) : true;
- if (do_start) {
- nrf_clock_task_trigger(NRF_CLOCK,
- config->start_tsk);
- DBG(dev, "Triggered start task");
- } else if (data) {
- data->cb(dev, data->user_data);
- }
- }
+ if (ref == 1) {
+ bool do_start;
- /* if node is in the list it means that it is scheduled for
- * the second time.
- */
- if (data) {
- if (is_in_list(&clk_data->list, &data->node)) {
- return -EALREADY;
- }
-
- sys_slist_append(&clk_data->list, &data->node);
+ do_start = (config->start_handler) ?
+ config->start_handler(dev) : true;
+ if (do_start) {
+ DBG(dev, "Triggering start task");
+ nrf_clock_task_trigger(NRF_CLOCK,
+ config->start_tsk);
+ } else {
+ /* If external start_handler indicated that clcok is
+ * still running (it may happen in case of LF RC clock
+ * which was requested to be stopped during ongoing
+ * calibration (clock will not be stopped in that case)
+ * and requested to be started before calibration is
+ * completed. In that case clock is still running and
+ * we can notify enlisted requests.
+ */
+ clkstarted_handle(dev);
}
}