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(×tamp_radio);
61 sample_ticks(×tamp_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(×tamp_lll);
73 sample_ticks(×tamp_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(×tamp_ull_high);
85 sample_ticks(×tamp_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(×tamp_ull_low);
97 sample_ticks(×tamp_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