1 /*
2 * Copyright (c) 2020 Nordic Semiconductor ASA
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <errno.h>
8
9 #include <soc.h>
10 #include <zephyr/kernel.h>
11 #include <zephyr/device.h>
12 #include <zephyr/init.h>
13 #include <zephyr/sys/__assert.h>
14 #include <zephyr/bluetooth/hci_types.h>
15
16 #include "hal/ticker.h"
17 #include "ticker/ticker.h"
18 #include "ll.h"
19
20 #include "soc_flash_nrf.h"
21
22 #define FLASH_RADIO_ABORT_DELAY_US 1500
23 #define FLASH_RADIO_WORK_DELAY_US 200
24
25 /* delay needed for start execution-window */
26 #define FLASH_SYNC_SWITCHING_TIME (FLASH_RADIO_ABORT_DELAY_US +\
27 FLASH_RADIO_WORK_DELAY_US)
28
29 static struct {
30 uint32_t interval; /* timeslot interval. */
31 uint32_t slot; /* timeslot length. */
32 uint32_t ticks_begin; /* timeslot begin timestamp */
33 uint32_t ticks_previous; /* timeslot previous reference */
34 int result;
35 } _ticker_sync_context;
36
37 /* semaphore for synchronization of flash operations */
38 static struct k_sem sem_sync;
39
ticker_stop_work_cb(uint32_t status,void * param)40 static void ticker_stop_work_cb(uint32_t status, void *param)
41 {
42 __ASSERT((status == TICKER_STATUS_SUCCESS ||
43 status == TICKER_STATUS_FAILURE),
44 "Failed to stop work ticker, ticker job busy.\n");
45
46 /* notify thread that data is available */
47 k_sem_give(&sem_sync);
48 }
49
ticker_stop_prepare_cb(uint32_t status,void * param)50 static void ticker_stop_prepare_cb(uint32_t status, void *param)
51 {
52 uint8_t instance_index;
53 uint8_t ticker_id;
54 uint32_t ret;
55
56 __ASSERT(status == TICKER_STATUS_SUCCESS,
57 "Failed to stop prepare ticker.\n");
58
59 /* Get the ticker instance and ticker id for flash operations */
60 ll_timeslice_ticker_id_get(&instance_index, &ticker_id);
61
62 /* Stop the work ticker, from ULL_LOW context */
63 ret = ticker_stop(instance_index, 2U, (ticker_id + 1U),
64 ticker_stop_work_cb, NULL);
65 __ASSERT((ret == TICKER_STATUS_SUCCESS ||
66 ret == TICKER_STATUS_BUSY),
67 "Failed to request the work ticker to stop.\n");
68 }
69
time_slot_callback_work(uint32_t ticks_at_expire,uint32_t ticks_drift,uint32_t remainder,uint16_t lazy,uint8_t force,void * context)70 static void time_slot_callback_work(uint32_t ticks_at_expire,
71 uint32_t ticks_drift,
72 uint32_t remainder,
73 uint16_t lazy, uint8_t force,
74 void *context)
75 {
76 struct flash_op_desc *op_desc;
77 int rc;
78
79 __ASSERT(ll_radio_state_is_idle(),
80 "Radio is on during flash operation.\n");
81
82 op_desc = context;
83 rc = op_desc->handler(op_desc->context);
84 if (rc != FLASH_OP_ONGOING) {
85 uint8_t instance_index;
86 uint8_t ticker_id;
87 uint32_t ret;
88
89 /* Get the ticker instance and ticker id for flash operations */
90 ll_timeslice_ticker_id_get(&instance_index, &ticker_id);
91
92 /* Stop the prepare ticker, from ULL_HIGH context */
93 ret = ticker_stop(instance_index, 1U, ticker_id,
94 ticker_stop_prepare_cb, NULL);
95 __ASSERT((ret == TICKER_STATUS_SUCCESS ||
96 ret == TICKER_STATUS_BUSY),
97 "Failed to stop ticker.\n");
98
99 _ticker_sync_context.result = (rc == FLASH_OP_DONE) ? 0 : rc;
100 }
101 }
102
time_slot_delay(uint32_t ticks_at_expire,uint32_t ticks_delay,ticker_timeout_func callback,void * context)103 static void time_slot_delay(uint32_t ticks_at_expire, uint32_t ticks_delay,
104 ticker_timeout_func callback, void *context)
105 {
106 uint8_t instance_index;
107 uint8_t ticker_id;
108 uint32_t ret;
109
110 /* Get the ticker instance and ticker id for flash operations */
111 ll_timeslice_ticker_id_get(&instance_index, &ticker_id);
112
113 /* Start a secondary one-shot ticker after ticks_delay,
114 * this will let any radio role to gracefully abort and release the
115 * Radio h/w.
116 */
117 ret = ticker_start(instance_index, /* Radio instance ticker */
118 1, /* user id for link layer ULL_HIGH */
119 /* (MAYFLY_CALL_ID_WORKER) */
120 (ticker_id + 1), /* ticker_id */
121 ticks_at_expire, /* current tick */
122 ticks_delay, /* one-shot delayed timeout */
123 0, /* periodic timeout */
124 0, /* periodic remainder */
125 0, /* lazy, voluntary skips */
126 0,
127 callback, /* handler for executing radio abort or */
128 /* flash work */
129 context, /* the context for the flash operation */
130 NULL, /* no op callback */
131 NULL);
132
133 if (ret != TICKER_STATUS_SUCCESS && ret != TICKER_STATUS_BUSY) {
134 /* Failed to enqueue the ticker start operation request */
135 _ticker_sync_context.result = 0;
136
137 /* Abort flash prepare ticker, from ULL_HIGH context */
138 ret = ticker_stop(instance_index, 1U, ticker_id,
139 ticker_stop_prepare_cb, NULL);
140 __ASSERT((ret == TICKER_STATUS_SUCCESS ||
141 ret == TICKER_STATUS_BUSY),
142 "Failed to stop ticker.\n");
143 }
144 }
145
time_slot_callback_abort(uint32_t ticks_at_expire,uint32_t ticks_drift,uint32_t remainder,uint16_t lazy,uint8_t force,void * context)146 static void time_slot_callback_abort(uint32_t ticks_at_expire,
147 uint32_t ticks_drift,
148 uint32_t remainder,
149 uint16_t lazy, uint8_t force,
150 void *context)
151 {
152 ll_radio_state_abort();
153
154 time_slot_delay(ticks_at_expire,
155 HAL_TICKER_US_TO_TICKS(FLASH_RADIO_WORK_DELAY_US),
156 time_slot_callback_work,
157 context);
158 }
159
time_slot_callback_prepare(uint32_t ticks_at_expire,uint32_t ticks_drift,uint32_t remainder,uint16_t lazy,uint8_t force,void * context)160 static void time_slot_callback_prepare(uint32_t ticks_at_expire,
161 uint32_t ticks_drift,
162 uint32_t remainder,
163 uint16_t lazy, uint8_t force,
164 void *context)
165 {
166 uint32_t ticks_elapsed;
167 uint32_t ticks_now;
168
169 /* Skip flash operation if in the past, this can be when requested in the past due to
170 * flash operations requested too often.
171 */
172 ticks_now = ticker_ticks_now_get();
173 ticks_elapsed = ticker_ticks_diff_get(ticks_now, ticks_at_expire);
174 if (ticks_elapsed > HAL_TICKER_US_TO_TICKS(FLASH_RADIO_WORK_DELAY_US)) {
175 return;
176 }
177
178 #if defined(CONFIG_BT_CTLR_LOW_LAT)
179 time_slot_callback_abort(ticks_at_expire, ticks_drift, remainder, lazy,
180 force, context);
181 #else /* !CONFIG_BT_CTLR_LOW_LAT */
182 time_slot_delay(ticks_at_expire,
183 HAL_TICKER_US_TO_TICKS(FLASH_RADIO_ABORT_DELAY_US),
184 time_slot_callback_abort,
185 context);
186 #endif /* CONFIG_BT_CTLR_LOW_LAT */
187 }
188
189
nrf_flash_sync_init(void)190 int nrf_flash_sync_init(void)
191 {
192 k_sem_init(&sem_sync, 0, 1);
193
194 return 0;
195 }
196
nrf_flash_sync_set_context(uint32_t duration)197 void nrf_flash_sync_set_context(uint32_t duration)
198 {
199 /* FLASH_SYNC_SWITCHING_TIME is delay which is always added by
200 * the slot calling mechanism
201 */
202 _ticker_sync_context.interval = duration - FLASH_SYNC_SWITCHING_TIME;
203 _ticker_sync_context.slot = duration;
204 }
205
nrf_flash_sync_exe(struct flash_op_desc * op_desc)206 int nrf_flash_sync_exe(struct flash_op_desc *op_desc)
207 {
208 uint8_t instance_index;
209 uint32_t ticks_elapsed;
210 uint32_t ticks_slot;
211 uint32_t ticks_now;
212 uint8_t ticker_id;
213 uint32_t ret;
214 int result;
215
216 /* Check if flash operation request too often that can starve connection events.
217 * We will request to start ticker in the past so that it does not `force` itself onto
218 * scheduling by causing connection events to be skipped.
219 */
220 ticks_now = ticker_ticks_now_get();
221 ticks_elapsed = ticker_ticks_diff_get(ticks_now, _ticker_sync_context.ticks_previous);
222 _ticker_sync_context.ticks_previous = ticks_now;
223 ticks_slot = HAL_TICKER_US_TO_TICKS(_ticker_sync_context.slot);
224 if (ticks_elapsed < ticks_slot) {
225 uint32_t ticks_interval = HAL_TICKER_US_TO_TICKS(_ticker_sync_context.interval);
226
227 /* Set ticker start reference one flash slot interval in the past */
228 ticks_now = ticker_ticks_diff_get(ticks_now, ticks_interval);
229 }
230
231 /* Get the ticker instance and ticker id for flash operations */
232 ll_timeslice_ticker_id_get(&instance_index, &ticker_id);
233
234 /* Start periodic flash operation prepare time slots */
235 ret = ticker_start(instance_index,
236 3, /* user id for thread mode */
237 /* (MAYFLY_CALL_ID_PROGRAM) */
238 ticker_id, /* flash ticker id */
239 ticks_now, /* current tick */
240 0, /* first int. immediately */
241 /* period */
242 HAL_TICKER_US_TO_TICKS(
243 _ticker_sync_context.interval),
244 /* period remainder */
245 HAL_TICKER_REMAINDER(_ticker_sync_context.interval),
246 0, /* lazy, voluntary skips */
247 HAL_TICKER_US_TO_TICKS(_ticker_sync_context.slot),
248 time_slot_callback_prepare,
249 op_desc,
250 NULL, /* no op callback */
251 NULL);
252
253 if (ret != TICKER_STATUS_SUCCESS && ret != TICKER_STATUS_BUSY) {
254 /* Failed to enqueue the ticker start operation request */
255 result = -ECANCELED;
256 } else if (k_sem_take(&sem_sync, K_MSEC(FLASH_TIMEOUT_MS)) != 0) {
257 /* Stop any scheduled jobs, from thread context */
258 ret = ticker_stop(instance_index, 3U, ticker_id, NULL, NULL);
259 __ASSERT((ret == TICKER_STATUS_SUCCESS ||
260 ret == TICKER_STATUS_BUSY),
261 "Failed to stop ticker.\n");
262
263 /* wait for operation's complete overrun*/
264 result = -ETIMEDOUT;
265 } else {
266 result = _ticker_sync_context.result;
267 }
268
269 return result;
270 }
271
nrf_flash_sync_is_required(void)272 bool nrf_flash_sync_is_required(void)
273 {
274 return ticker_is_initialized(0);
275 }
276
nrf_flash_sync_get_timestamp_begin(void)277 void nrf_flash_sync_get_timestamp_begin(void)
278 {
279 _ticker_sync_context.ticks_begin = ticker_ticks_now_get();
280 }
281
nrf_flash_sync_check_time_limit(uint32_t iteration)282 bool nrf_flash_sync_check_time_limit(uint32_t iteration)
283 {
284 if (IS_ENABLED(CONFIG_SOC_COMPATIBLE_NRF54LX)) {
285 /* Write operations are not constant time depending on the previous value present
286 * versus new value to be written. Hence, perform no more than one iteration.
287 */
288
289 ARG_UNUSED(iteration);
290
291 return true;
292 } else {
293 uint32_t ticks_diff;
294
295 ticks_diff = ticker_ticks_diff_get(ticker_ticks_now_get(),
296 _ticker_sync_context.ticks_begin);
297 if (ticks_diff + ticks_diff/iteration >
298 HAL_TICKER_US_TO_TICKS(_ticker_sync_context.slot)) {
299 return true;
300 }
301
302 return false;
303 }
304 }
305