1 /*
2  * Copyright (c) 2018-2019 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <stdint.h>
8 #include <stddef.h>
9 #include <errno.h>
10 
11 #include <zephyr/toolchain.h>
12 
13 #include "hal/ccm.h"
14 #include "hal/radio.h"
15 
16 #include "util/memq.h"
17 
18 #include "ticker/ticker.h"
19 
20 #include "pdu_df.h"
21 #include "pdu_vendor.h"
22 #include "pdu.h"
23 
24 #include "lll.h"
25 
26 static int send(struct node_rx_pdu *rx);
27 static uint16_t latency_get(void);
28 static inline void sample(uint32_t *timestamp);
29 static inline void sample_ticks(uint32_t *timestamp_ticks);
30 static inline void delta(uint32_t timestamp, uint16_t *cputime);
31 static inline void delta_ticks(uint32_t timestamp_ticks, uint8_t *cputime_ticks);
32 
33 static uint32_t timestamp_radio;
34 static uint32_t timestamp_lll;
35 static uint32_t timestamp_ull_high;
36 static uint32_t timestamp_ull_low;
37 static uint16_t cputime_radio;
38 static uint16_t cputime_lll;
39 static uint16_t cputime_ull_high;
40 static uint16_t cputime_ull_low;
41 static uint16_t latency_min = UINT16_MAX;
42 static uint16_t latency_max;
43 static uint16_t latency_prev;
44 static uint16_t cputime_min = UINT16_MAX;
45 static uint16_t cputime_max;
46 static uint16_t cputime_prev;
47 static uint32_t timestamp_latency;
48 
49 static uint32_t timestamp_ticks_radio;
50 static uint32_t timestamp_ticks_lll;
51 static uint32_t timestamp_ticks_ull_high;
52 static uint32_t timestamp_ticks_ull_low;
53 static uint8_t  cputime_ticks_radio;
54 static uint8_t  cputime_ticks_lll;
55 static uint8_t  cputime_ticks_ull_high;
56 static uint8_t  cputime_ticks_ull_low;
57 
lll_prof_enter_radio(void)58 void lll_prof_enter_radio(void)
59 {
60 	sample(&timestamp_radio);
61 	sample_ticks(&timestamp_ticks_radio);
62 }
63 
lll_prof_exit_radio(void)64 void lll_prof_exit_radio(void)
65 {
66 	delta(timestamp_radio, &cputime_radio);
67 	delta_ticks(timestamp_ticks_radio, &cputime_ticks_radio);
68 }
69 
lll_prof_enter_lll(void)70 void lll_prof_enter_lll(void)
71 {
72 	sample(&timestamp_lll);
73 	sample_ticks(&timestamp_ticks_lll);
74 }
75 
lll_prof_exit_lll(void)76 void lll_prof_exit_lll(void)
77 {
78 	delta(timestamp_lll, &cputime_lll);
79 	delta_ticks(timestamp_ticks_lll, &cputime_ticks_lll);
80 }
81 
lll_prof_enter_ull_high(void)82 void lll_prof_enter_ull_high(void)
83 {
84 	sample(&timestamp_ull_high);
85 	sample_ticks(&timestamp_ticks_ull_high);
86 }
87 
lll_prof_exit_ull_high(void)88 void lll_prof_exit_ull_high(void)
89 {
90 	delta(timestamp_ull_high, &cputime_ull_high);
91 	delta_ticks(timestamp_ticks_ull_high, &cputime_ticks_ull_high);
92 }
93 
lll_prof_enter_ull_low(void)94 void lll_prof_enter_ull_low(void)
95 {
96 	sample(&timestamp_ull_low);
97 	sample_ticks(&timestamp_ticks_ull_low);
98 }
99 
lll_prof_exit_ull_low(void)100 void lll_prof_exit_ull_low(void)
101 {
102 	delta(timestamp_ull_low, &cputime_ull_low);
103 	delta_ticks(timestamp_ticks_ull_low, &cputime_ticks_ull_low);
104 }
105 
lll_prof_latency_capture(void)106 void lll_prof_latency_capture(void)
107 {
108 	/* sample the packet timer, use it to calculate ISR latency
109 	 * and generate the profiling event at the end of the ISR.
110 	 */
111 	radio_tmr_sample();
112 
113 	/* Initialize so that if we call lll_prof_latency_get before it is
114 	 * set, we can set it.
115 	 */
116 	timestamp_latency = UINT16_MAX;
117 }
118 
lll_prof_latency_get(void)119 uint16_t lll_prof_latency_get(void)
120 {
121 	uint16_t latency;
122 
123 	/* We are here before lll_prof_cputime_capture was called */
124 	if (timestamp_latency == UINT16_MAX) {
125 		/* get the ISR latency sample */
126 		timestamp_latency = radio_tmr_sample_get();
127 	}
128 
129 	/* Get the elapsed time in us since on-air radio packet end to ISR
130 	 * entry.
131 	 */
132 	latency = latency_get();
133 
134 	return latency;
135 }
136 
137 #if defined(HAL_RADIO_GPIO_HAVE_PA_PIN)
138 static uint32_t timestamp_radio_end;
139 
lll_prof_radio_end_backup(void)140 uint32_t lll_prof_radio_end_backup(void)
141 {
142 	/* PA enable is overwriting packet end used in ISR profiling, hence
143 	 * back it up for later use.
144 	 */
145 	timestamp_radio_end = radio_tmr_end_get();
146 
147 	return timestamp_radio_end;
148 }
149 #endif /* !HAL_RADIO_GPIO_HAVE_PA_PIN */
150 
lll_prof_cputime_capture(void)151 void lll_prof_cputime_capture(void)
152 {
153 	/* get the ISR latency sample */
154 	timestamp_latency = radio_tmr_sample_get();
155 
156 	/* sample the packet timer again, use it to calculate ISR execution time
157 	 * and use it in profiling event
158 	 */
159 	radio_tmr_sample();
160 }
161 
lll_prof_send(void)162 void lll_prof_send(void)
163 {
164 	struct node_rx_pdu *rx;
165 
166 	/* Generate only if spare node rx is available */
167 	rx = ull_pdu_rx_alloc_peek(3);
168 	if (rx) {
169 		(void)send(NULL);
170 	}
171 }
172 
lll_prof_reserve(void)173 struct node_rx_pdu *lll_prof_reserve(void)
174 {
175 	struct node_rx_pdu *rx;
176 
177 	rx = ull_pdu_rx_alloc_peek(3);
178 	if (!rx) {
179 		return NULL;
180 	}
181 
182 	ull_pdu_rx_alloc();
183 
184 	return rx;
185 }
186 
lll_prof_reserve_send(struct node_rx_pdu * rx)187 void lll_prof_reserve_send(struct node_rx_pdu *rx)
188 {
189 	if (rx) {
190 		int err;
191 
192 		err = send(rx);
193 		if (err) {
194 			rx->hdr.type = NODE_RX_TYPE_PROFILE;
195 
196 			ull_rx_put_sched(rx->hdr.link, rx);
197 		}
198 	}
199 }
200 
send(struct node_rx_pdu * rx)201 static int send(struct node_rx_pdu *rx)
202 {
203 	uint16_t latency, cputime, prev;
204 	struct pdu_data *pdu;
205 	struct profile *p;
206 	uint8_t chg = 0U;
207 
208 	/* Get the elapsed time in us since on-air radio packet end to ISR
209 	 * entry.
210 	 */
211 	latency = latency_get();
212 
213 	/* check changes in min, avg and max of latency */
214 	if (latency > latency_max) {
215 		latency_max = latency;
216 		chg = 1U;
217 	}
218 	if (latency < latency_min) {
219 		latency_min = latency;
220 		chg = 1U;
221 	}
222 
223 	/* check for +/- 1us change */
224 	prev = ((uint16_t)latency_prev + latency) >> 1;
225 	if (prev != latency_prev) {
226 		latency_prev = latency;
227 		chg = 1U;
228 	}
229 
230 	/* calculate the elapsed time in us since ISR entry */
231 	cputime = radio_tmr_sample_get() - timestamp_latency;
232 
233 	/* check changes in min, avg and max */
234 	if (cputime > cputime_max) {
235 		cputime_max = cputime;
236 		chg = 1U;
237 	}
238 
239 	if (cputime < cputime_min) {
240 		cputime_min = cputime;
241 		chg = 1U;
242 	}
243 
244 	/* check for +/- 1us change */
245 	prev = ((uint16_t)cputime_prev + cputime) >> 1;
246 	if (prev != cputime_prev) {
247 		cputime_prev = cputime;
248 		chg = 1U;
249 	}
250 
251 	/* generate event if any change */
252 	if (!chg) {
253 		return -ENODATA;
254 	}
255 
256 	/* Allocate if not already allocated */
257 	if (!rx) {
258 		rx = ull_pdu_rx_alloc();
259 		if (!rx) {
260 			return -ENOMEM;
261 		}
262 	}
263 
264 	/* Generate event with the allocated node rx */
265 	rx->hdr.type = NODE_RX_TYPE_PROFILE;
266 	rx->hdr.handle = NODE_RX_HANDLE_INVALID;
267 
268 	pdu = (void *)rx->pdu;
269 	p = &pdu->profile;
270 	p->lcur = latency;
271 	p->lmin = latency_min;
272 	p->lmax = latency_max;
273 	p->cur = cputime;
274 	p->min = cputime_min;
275 	p->max = cputime_max;
276 	p->radio = cputime_radio;
277 	p->lll = cputime_lll;
278 	p->ull_high = cputime_ull_high;
279 	p->ull_low = cputime_ull_low;
280 	p->radio_ticks = cputime_ticks_radio;
281 	p->lll_ticks = cputime_ticks_lll;
282 	p->ull_high_ticks = cputime_ticks_ull_high;
283 	p->ull_low_ticks = cputime_ticks_ull_low;
284 
285 	ull_rx_put_sched(rx->hdr.link, rx);
286 
287 	return 0;
288 }
289 
latency_get(void)290 static uint16_t latency_get(void)
291 {
292 	uint16_t latency;
293 
294 	/* calculate the elapsed time in us since on-air radio packet end
295 	 * to ISR entry
296 	 */
297 	if (!IS_ENABLED(CONFIG_BT_CTLR_SW_SWITCH_SINGLE_TIMER)) {
298 #if defined(HAL_RADIO_GPIO_HAVE_PA_PIN)
299 		latency = timestamp_latency - timestamp_radio_end;
300 #else /* !HAL_RADIO_GPIO_HAVE_PA_PIN */
301 		latency = timestamp_latency - radio_tmr_end_get();
302 #endif /* !HAL_RADIO_GPIO_HAVE_PA_PIN */
303 	} else {
304 		latency = timestamp_latency;
305 	}
306 
307 	return latency;
308 }
309 
sample(uint32_t * timestamp)310 static inline void sample(uint32_t *timestamp)
311 {
312 	radio_tmr_sample();
313 	*timestamp = radio_tmr_sample_get();
314 }
315 
sample_ticks(uint32_t * timestamp_ticks)316 static inline void sample_ticks(uint32_t *timestamp_ticks)
317 {
318 	*timestamp_ticks = ticker_ticks_now_get();
319 }
320 
delta(uint32_t timestamp,uint16_t * cputime)321 static inline void delta(uint32_t timestamp, uint16_t *cputime)
322 {
323 	uint32_t delta;
324 
325 	radio_tmr_sample();
326 	delta = radio_tmr_sample_get() - timestamp;
327 	if (delta < UINT16_MAX && delta > *cputime) {
328 		*cputime = delta;
329 	}
330 }
331 
delta_ticks(uint32_t timestamp_ticks,uint8_t * cputime_ticks)332 static inline void delta_ticks(uint32_t timestamp_ticks, uint8_t *cputime_ticks)
333 {
334 	uint32_t delta;
335 
336 	delta = ticker_ticks_now_get() - timestamp_ticks;
337 	if (delta < UINT8_MAX && delta > *cputime_ticks) {
338 		*cputime_ticks = delta;
339 	}
340 }
341