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