1 /*
2 * Copyright (c) 2021 Demant
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6 #include <string.h>
7 #include <stdlib.h>
8
9 #include <zephyr/types.h>
10 #include <sys/types.h>
11 #include <zephyr/toolchain.h>
12 #include <zephyr/sys/util.h>
13
14 #include <zephyr/kernel.h>
15
16 #include <zephyr/bluetooth/hci_types.h>
17 #include <zephyr/bluetooth/conn.h>
18
19 #include <zephyr/sys/byteorder.h>
20
21 #include "util/memq.h"
22
23 #include "hal/ccm.h"
24 #include "hal/ticker.h"
25
26 #include "pdu_df.h"
27 #include "lll/pdu_vendor.h"
28 #include "pdu.h"
29
30 #include "ll.h"
31 #include "lll.h"
32 #include "lll_conn_iso.h"
33 #include "lll_iso_tx.h"
34 #include "isoal.h"
35 #include "ull_iso_types.h"
36
37 #include <zephyr/logging/log.h>
38
39 LOG_MODULE_REGISTER(bt_ctlr_isoal, CONFIG_BT_CTLR_ISOAL_LOG_LEVEL);
40
41 #define ISOAL_LOG_DBG(...) LOG_DBG(__VA_ARGS__)
42
43 #if defined(CONFIG_BT_CTLR_ISOAL_LOG_DBG_VERBOSE)
44 #define ISOAL_LOG_DBGV(...) LOG_DBG(__VA_ARGS__)
45 #else
46 #define ISOAL_LOG_DBGV(...) (void) 0
47 #endif /* CONFIG_BT_CTLR_ISOAL_LOG_DBG_VERBOSE */
48
49 #include "hal/debug.h"
50
51 #define FSM_TO_STR(s) (s == ISOAL_START ? "START" : \
52 (s == ISOAL_CONTINUE ? "CONTINUE" : \
53 (s == ISOAL_ERR_SPOOL ? "ERR SPOOL" : "???")))
54
55 #define STATE_TO_STR(s) (s == BT_ISO_SINGLE ? "SINGLE" : \
56 (s == BT_ISO_START ? "START" : \
57 (s == BT_ISO_CONT ? "CONT" : \
58 (s == BT_ISO_END ? "END" : "???"))))
59
60 #if defined(CONFIG_BT_CTLR_ADV_ISO) || defined(CONFIG_BT_CTLR_CONN_ISO)
61 /* Given the minimum payload, this defines the minimum number of bytes that
62 * should be remaining in a TX PDU such that it would make inserting a new
63 * segment worthwhile during the segmentation process.
64 * [Payload (min) + Segmentation Header + Time Offset]
65 */
66 #define ISOAL_TX_SEGMENT_MIN_SIZE (CONFIG_BT_CTLR_ISO_TX_SEG_PLAYLOAD_MIN + \
67 PDU_ISO_SEG_HDR_SIZE + \
68 PDU_ISO_SEG_TIMEOFFSET_SIZE)
69 #endif /* CONFIG_BT_CTLR_ADV_ISO || CONFIG_BT_CTLR_CONN_ISO */
70
71 /* Defined the wrapping point and mid point in the range of time input values,
72 * which depend on range of the controller's clock in microseconds.
73 */
74 #define ISOAL_TIME_WRAPPING_POINT_US (HAL_TICKER_TICKS_TO_US(HAL_TICKER_CNTR_MASK))
75 #define ISOAL_TIME_MID_POINT_US (ISOAL_TIME_WRAPPING_POINT_US / 2)
76 #define ISOAL_TIME_SPAN_FULL_US (ISOAL_TIME_WRAPPING_POINT_US + 1)
77 #define ISOAL_TIME_SPAN_HALF_US (ISOAL_TIME_SPAN_FULL_US / 2)
78
79 /** Allocation state */
80 typedef uint8_t isoal_alloc_state_t;
81 #define ISOAL_ALLOC_STATE_FREE ((isoal_alloc_state_t) 0x00)
82 #define ISOAL_ALLOC_STATE_TAKEN ((isoal_alloc_state_t) 0x01)
83
84 struct
85 {
86 #if defined(CONFIG_BT_CTLR_SYNC_ISO) || defined(CONFIG_BT_CTLR_CONN_ISO)
87 isoal_alloc_state_t sink_allocated[CONFIG_BT_CTLR_ISOAL_SINKS];
88 struct isoal_sink sink_state[CONFIG_BT_CTLR_ISOAL_SINKS];
89 #endif /* CONFIG_BT_CTLR_SYNC_ISO || CONFIG_BT_CTLR_CONN_ISO */
90
91 #if defined(CONFIG_BT_CTLR_ADV_ISO) || defined(CONFIG_BT_CTLR_CONN_ISO)
92 isoal_alloc_state_t source_allocated[CONFIG_BT_CTLR_ISOAL_SOURCES];
93 struct isoal_source source_state[CONFIG_BT_CTLR_ISOAL_SOURCES];
94 #endif /* CONFIG_BT_CTLR_ADV_ISO || CONFIG_BT_CTLR_CONN_ISO */
95 } isoal_global;
96
97 /**
98 * @brief Internal reset
99 * Zero-init entire ISO-AL state
100 */
isoal_init_reset(void)101 static isoal_status_t isoal_init_reset(void)
102 {
103 memset(&isoal_global, 0, sizeof(isoal_global));
104 return ISOAL_STATUS_OK;
105 }
106
107 /**
108 * @brief Initialize ISO-AL
109 */
isoal_init(void)110 isoal_status_t isoal_init(void)
111 {
112 isoal_status_t err = ISOAL_STATUS_OK;
113
114 err = isoal_init_reset();
115
116 return err;
117 }
118
119 /** Clean up and reinitialize */
isoal_reset(void)120 isoal_status_t isoal_reset(void)
121 {
122 isoal_status_t err = ISOAL_STATUS_OK;
123
124 err = isoal_init_reset();
125
126 return err;
127 }
128
129 /**
130 * @brief Wraps given time within the range of 0 to ISOAL_TIME_WRAPPING_POINT_US
131 * @param time_now Current time value
132 * @param time_diff Time difference (signed)
133 * @return Wrapped time after difference
134 */
isoal_get_wrapped_time_us(uint32_t time_now_us,int32_t time_diff_us)135 uint32_t isoal_get_wrapped_time_us(uint32_t time_now_us, int32_t time_diff_us)
136 {
137 LL_ASSERT(time_now_us <= ISOAL_TIME_WRAPPING_POINT_US);
138
139 uint32_t result = ((uint64_t)time_now_us + ISOAL_TIME_SPAN_FULL_US + time_diff_us) %
140 ((uint64_t)ISOAL_TIME_SPAN_FULL_US);
141
142 return result;
143 }
144
145 /**
146 * @brief Check if a time difference calculation is valid and return the difference.
147 * @param time_before Subtrahend
148 * @param time_after Minuend
149 * @param result Difference if valid
150 * @return Validity - valid if time_after leads time_before with
151 * consideration for wrapping such that the
152 * difference can be calculated.
153 */
isoal_get_time_diff(uint32_t time_before,uint32_t time_after,uint32_t * result)154 static bool isoal_get_time_diff(uint32_t time_before, uint32_t time_after, uint32_t *result)
155 {
156 bool valid = false;
157
158 LL_ASSERT(time_before <= ISOAL_TIME_WRAPPING_POINT_US);
159 LL_ASSERT(time_after <= ISOAL_TIME_WRAPPING_POINT_US);
160
161 if (time_before > time_after) {
162 if (time_before >= ISOAL_TIME_MID_POINT_US &&
163 time_after <= ISOAL_TIME_MID_POINT_US) {
164 if ((time_before - time_after) <= ISOAL_TIME_SPAN_HALF_US) {
165 /* Time_before is after time_after and the result is invalid. */
166 } else {
167 /* time_after has wrapped */
168 *result = time_after + ISOAL_TIME_SPAN_FULL_US - time_before;
169 valid = true;
170 }
171 }
172
173 /* Time_before is after time_after and the result is invalid. */
174 } else {
175 /* Time_before <= time_after */
176 *result = time_after - time_before;
177 if (*result <= ISOAL_TIME_SPAN_HALF_US) {
178 /* result is valid if it is within half the maximum
179 * time span.
180 */
181 valid = true;
182 } else {
183 /* time_before has wrapped and the calculation is not
184 * valid as time_before is ahead of time_after.
185 */
186 }
187 }
188
189 return valid;
190 }
191
192 #if defined(CONFIG_BT_CTLR_SYNC_ISO) || defined(CONFIG_BT_CTLR_CONN_ISO)
193
194 #define SET_RX_SDU_TIMESTAMP(_sink, _timestamp, _value) \
195 _timestamp = _value; \
196 ISOAL_LOG_DBGV("[%p] %s updated (%lu)", _sink, #_timestamp, _value);
197
198 static void isoal_rx_framed_update_sdu_release(struct isoal_sink *sink);
199
200 /**
201 * @brief Find free sink from statically-sized pool and allocate it
202 * @details Implemented as linear search since pool is very small
203 *
204 * @param hdl[out] Handle to sink
205 * @return ISOAL_STATUS_OK if we could allocate; otherwise ISOAL_STATUS_ERR_SINK_ALLOC
206 */
isoal_sink_allocate(isoal_sink_handle_t * hdl)207 static isoal_status_t isoal_sink_allocate(isoal_sink_handle_t *hdl)
208 {
209 isoal_sink_handle_t i;
210
211 /* Very small linear search to find first free */
212 for (i = 0; i < CONFIG_BT_CTLR_ISOAL_SINKS; i++) {
213 if (isoal_global.sink_allocated[i] == ISOAL_ALLOC_STATE_FREE) {
214 isoal_global.sink_allocated[i] = ISOAL_ALLOC_STATE_TAKEN;
215 *hdl = i;
216 return ISOAL_STATUS_OK;
217 }
218 }
219
220 return ISOAL_STATUS_ERR_SINK_ALLOC; /* All entries were taken */
221 }
222
223 /**
224 * @brief Mark a sink as being free to allocate again
225 * @param hdl[in] Handle to sink
226 */
isoal_sink_deallocate(isoal_sink_handle_t hdl)227 static void isoal_sink_deallocate(isoal_sink_handle_t hdl)
228 {
229 if (hdl < ARRAY_SIZE(isoal_global.sink_allocated)) {
230 isoal_global.sink_allocated[hdl] = ISOAL_ALLOC_STATE_FREE;
231 } else {
232 LL_ASSERT(0);
233 }
234
235 if (hdl < ARRAY_SIZE(isoal_global.sink_state)) {
236 (void)memset(&isoal_global.sink_state[hdl], 0, sizeof(struct isoal_sink));
237 } else {
238 LL_ASSERT(0);
239 }
240 }
241
242 /**
243 * @brief Create a new sink
244 *
245 * @param handle[in] Connection handle
246 * @param role[in] Peripheral, Central or Broadcast
247 * @param framed[in] Framed case
248 * @param burst_number[in] Burst Number
249 * @param flush_timeout[in] Flush timeout
250 * @param sdu_interval[in] SDU interval
251 * @param iso_interval[in] ISO interval
252 * @param stream_sync_delay[in] CIS / BIS sync delay
253 * @param group_sync_delay[in] CIG / BIG sync delay
254 * @param sdu_alloc[in] Callback of SDU allocator
255 * @param sdu_emit[in] Callback of SDU emitter
256 * @param sdu_write[in] Callback of SDU byte writer
257 * @param hdl[out] Handle to new sink
258 *
259 * @return ISOAL_STATUS_OK if we could create a new sink; otherwise ISOAL_STATUS_ERR_SINK_ALLOC
260 */
isoal_sink_create(uint16_t handle,uint8_t role,uint8_t framed,uint8_t burst_number,uint8_t flush_timeout,uint32_t sdu_interval,uint16_t iso_interval,uint32_t stream_sync_delay,uint32_t group_sync_delay,isoal_sink_sdu_alloc_cb sdu_alloc,isoal_sink_sdu_emit_cb sdu_emit,isoal_sink_sdu_write_cb sdu_write,isoal_sink_handle_t * hdl)261 isoal_status_t isoal_sink_create(
262 uint16_t handle,
263 uint8_t role,
264 uint8_t framed,
265 uint8_t burst_number,
266 uint8_t flush_timeout,
267 uint32_t sdu_interval,
268 uint16_t iso_interval,
269 uint32_t stream_sync_delay,
270 uint32_t group_sync_delay,
271 isoal_sink_sdu_alloc_cb sdu_alloc,
272 isoal_sink_sdu_emit_cb sdu_emit,
273 isoal_sink_sdu_write_cb sdu_write,
274 isoal_sink_handle_t *hdl)
275 {
276 uint32_t iso_interval_us;
277 isoal_status_t err;
278
279 /* ISO interval in units of time requires the integer (iso_interval)
280 * to be multiplied by 1250us.
281 */
282 iso_interval_us = iso_interval * ISO_INT_UNIT_US;
283
284 /* Allocate a new sink */
285 err = isoal_sink_allocate(hdl);
286 if (err) {
287 return err;
288 }
289
290 struct isoal_sink_session *session = &isoal_global.sink_state[*hdl].session;
291
292 session->handle = handle;
293 session->framed = framed;
294 session->sdu_interval = sdu_interval;
295 session->iso_interval = iso_interval;
296 session->burst_number = burst_number;
297
298 /* Todo: Next section computing various constants, should potentially be a
299 * function in itself as a number of the dependencies could be changed while
300 * a connection is active.
301 */
302
303 session->pdus_per_sdu = (burst_number * sdu_interval) /
304 iso_interval_us;
305
306 /* Computation of transport latency (constant part)
307 *
308 * Unframed case:
309 *
310 * C->P: SDU_Synchronization_Reference =
311 * CIS reference anchor point + CIS_Sync_Delay + (FT_C_To_P - 1) * ISO_Interval
312 *
313 * P->C: SDU_Synchronization_Reference =
314 * CIS reference anchor point + CIS_Sync_Delay - CIG_Sync_Delay -
315 * ((ISO_Interval / SDU interval)-1) * SDU interval
316 *
317 * BIS: SDU_Synchronization_Reference =
318 * BIG reference anchor point + BIG_Sync_Delay
319 *
320 * Framed case:
321 *
322 * C->P: SDU_Synchronization_Reference =
323 * CIS Reference Anchor point +
324 * CIS_Sync_Delay + SDU_Interval_C_To_P + FT_C_To_P * ISO_Interval -
325 * Time_Offset
326 *
327 * P->C: synchronization reference SDU = CIS reference anchor point +
328 * CIS_Sync_Delay - CIG_Sync_Delay - Time_Offset
329 *
330 * BIS: SDU_Synchronization_Reference =
331 * BIG reference anchor point +
332 * BIG_Sync_Delay + SDU_interval + ISO_Interval - Time_Offset.
333 */
334 if (role == ISOAL_ROLE_PERIPHERAL) {
335 if (framed) {
336 session->sdu_sync_const = stream_sync_delay + sdu_interval +
337 (flush_timeout * iso_interval_us);
338 } else {
339 session->sdu_sync_const = stream_sync_delay +
340 ((flush_timeout - 1UL) * iso_interval_us);
341 }
342 } else if (role == ISOAL_ROLE_CENTRAL) {
343 if (framed) {
344 session->sdu_sync_const = stream_sync_delay - group_sync_delay;
345 } else {
346 session->sdu_sync_const = stream_sync_delay - group_sync_delay -
347 (((iso_interval_us / sdu_interval) - 1UL) *
348 iso_interval_us);
349 }
350 } else if (role == ISOAL_ROLE_BROADCAST_SINK) {
351 if (framed) {
352 session->sdu_sync_const = group_sync_delay + sdu_interval + iso_interval_us;
353 } else {
354 session->sdu_sync_const = group_sync_delay;
355 }
356 } else {
357 LL_ASSERT(0);
358 }
359
360 /* Remember the platform-specific callbacks */
361 session->sdu_alloc = sdu_alloc;
362 session->sdu_emit = sdu_emit;
363 session->sdu_write = sdu_write;
364
365 /* Initialize running seq number to zero */
366 session->sn = 0;
367
368 return err;
369 }
370
371 /**
372 * @brief Atomically enable latch-in of packets and SDU production
373 * @param hdl[in] Handle of existing instance
374 */
isoal_sink_enable(isoal_sink_handle_t hdl)375 void isoal_sink_enable(isoal_sink_handle_t hdl)
376 {
377 if (hdl < ARRAY_SIZE(isoal_global.sink_state)) {
378 /* Reset bookkeeping state */
379 memset(&isoal_global.sink_state[hdl].sdu_production, 0,
380 sizeof(isoal_global.sink_state[hdl].sdu_production));
381
382 /* Atomically enable */
383 isoal_global.sink_state[hdl].sdu_production.mode = ISOAL_PRODUCTION_MODE_ENABLED;
384 } else {
385 LL_ASSERT(0);
386 }
387 }
388
389 /**
390 * @brief Atomically disable latch-in of packets and SDU production
391 * @param hdl[in] Handle of existing instance
392 */
isoal_sink_disable(isoal_sink_handle_t hdl)393 void isoal_sink_disable(isoal_sink_handle_t hdl)
394 {
395 if (hdl < ARRAY_SIZE(isoal_global.sink_state)) {
396 /* Atomically disable */
397 isoal_global.sink_state[hdl].sdu_production.mode = ISOAL_PRODUCTION_MODE_DISABLED;
398 } else {
399 LL_ASSERT(0);
400 }
401 }
402
403 /**
404 * @brief Disable and deallocate existing sink
405 * @param hdl[in] Handle of existing instance
406 */
isoal_sink_destroy(isoal_sink_handle_t hdl)407 void isoal_sink_destroy(isoal_sink_handle_t hdl)
408 {
409 /* Atomic disable */
410 isoal_sink_disable(hdl);
411
412 /* Permit allocation anew */
413 isoal_sink_deallocate(hdl);
414 }
415
416 /* Obtain destination SDU */
isoal_rx_allocate_sdu(struct isoal_sink * sink,const struct isoal_pdu_rx * pdu_meta)417 static isoal_status_t isoal_rx_allocate_sdu(struct isoal_sink *sink,
418 const struct isoal_pdu_rx *pdu_meta)
419 {
420 struct isoal_sink_session *session;
421 struct isoal_sdu_production *sp;
422 struct isoal_sdu_produced *sdu;
423 isoal_status_t err;
424
425 err = ISOAL_STATUS_OK;
426 session = &sink->session;
427 sp = &sink->sdu_production;
428 sdu = &sp->sdu;
429
430 /* Allocate a SDU if the previous was filled (thus sent) */
431 const bool sdu_complete = (sp->sdu_available == 0);
432
433 if (sdu_complete) {
434 /* Allocate new clean SDU buffer */
435 err = session->sdu_alloc(
436 sink,
437 pdu_meta, /* [in] PDU origin may determine buffer */
438 &sdu->contents /* [out] Updated with pointer and size */
439 );
440
441 if (err == ISOAL_STATUS_OK) {
442 sp->sdu_allocated = 1U;
443 }
444
445 /* Nothing has been written into buffer yet */
446 sp->sdu_written = 0;
447 sp->sdu_available = sdu->contents.size;
448 LL_ASSERT(sdu->contents.size > 0);
449
450 /* Get seq number from session counter */
451 sdu->sn = session->sn;
452 }
453
454 return err;
455 }
456
457 /**
458 * @brief Depending of whether the configuration is enabled, this will either
459 * buffer and collate information for the SDU across all fragments
460 * before emitting the batch of fragments, or immediately release the
461 * fragment.
462 * @param sink Point to the sink context structure
463 * @param end_of_sdu Indicates if this is the end fragment of an SDU or forced
464 * release on an error
465 * @return Status of operation
466 */
isoal_rx_buffered_emit_sdu(struct isoal_sink * sink,bool end_of_sdu)467 static isoal_status_t isoal_rx_buffered_emit_sdu(struct isoal_sink *sink, bool end_of_sdu)
468 {
469 struct isoal_emitted_sdu_frag sdu_frag;
470 struct isoal_emitted_sdu sdu_status;
471 struct isoal_sink_session *session;
472 struct isoal_sdu_production *sp;
473 struct isoal_sdu_produced *sdu;
474 bool emit_sdu_current;
475 isoal_status_t err;
476
477 err = ISOAL_STATUS_OK;
478 session = &sink->session;
479 sp = &sink->sdu_production;
480 sdu = &sp->sdu;
481
482 /* Initialize current SDU fragment buffer */
483 sdu_frag.sdu_state = sp->sdu_state;
484 sdu_frag.sdu_frag_size = sp->sdu_written;
485 sdu_frag.sdu = *sdu;
486
487 sdu_status.total_sdu_size = sdu_frag.sdu_frag_size;
488 sdu_status.collated_status = sdu_frag.sdu.status;
489 emit_sdu_current = true;
490
491 #if defined(ISOAL_BUFFER_RX_SDUS_ENABLE)
492 uint16_t next_write_indx;
493 bool sdu_list_empty;
494 bool emit_sdu_list;
495 bool sdu_list_max;
496 bool sdu_list_err;
497
498 next_write_indx = sp->sdu_list.next_write_indx;
499 sdu_list_max = (next_write_indx >= CONFIG_BT_CTLR_ISO_RX_SDU_BUFFERS);
500 sdu_list_empty = (next_write_indx == 0);
501
502 /* There is an error in the sequence of SDUs if the current SDU fragment
503 * is not an end fragment and either the list at capacity or the current
504 * fragment is not a continuation (i.e. it is a start of a new SDU).
505 */
506 sdu_list_err = !end_of_sdu &&
507 (sdu_list_max ||
508 (!sdu_list_empty && sdu_frag.sdu_state != BT_ISO_CONT));
509
510 /* Release the current fragment if it is the end of the SDU or if it is
511 * not the starting fragment of a multi-fragment SDU.
512 */
513 emit_sdu_current = end_of_sdu || (sdu_list_empty && sdu_frag.sdu_state != BT_ISO_START);
514
515 /* Flush the buffered SDUs if this is an end fragment either on account
516 * of reaching the end of the SDU or on account of an error or if
517 * there is an error in the sequence of buffered fragments.
518 */
519 emit_sdu_list = emit_sdu_current || sdu_list_err;
520
521 /* Total size is cleared if the current fragment is not being emitted
522 * or if there is an error in the sequence of fragments.
523 */
524 if (!emit_sdu_current || sdu_list_err) {
525 sdu_status.total_sdu_size = 0;
526 sdu_status.collated_status = (sdu_list_err ? ISOAL_SDU_STATUS_LOST_DATA :
527 ISOAL_SDU_STATUS_VALID);
528 }
529
530 if (emit_sdu_list && next_write_indx > 0) {
531 if (!sdu_list_err) {
532 /* Collated information is not reliable if there is an
533 * error in the sequence of the fragments.
534 */
535 for (uint8_t i = 0; i < next_write_indx; i++) {
536 sdu_status.total_sdu_size +=
537 sp->sdu_list.list[i].sdu_frag_size;
538 if (sp->sdu_list.list[i].sdu.status == ISOAL_SDU_STATUS_LOST_DATA ||
539 sdu_status.collated_status == ISOAL_SDU_STATUS_LOST_DATA) {
540 sdu_status.collated_status = ISOAL_SDU_STATUS_LOST_DATA;
541 } else {
542 sdu_status.collated_status |=
543 sp->sdu_list.list[i].sdu.status;
544 }
545 }
546 }
547
548 for (uint8_t i = 0; i < next_write_indx; i++) {
549 err |= session->sdu_emit(sink, &sp->sdu_list.list[i],
550 &sdu_status);
551 }
552
553 next_write_indx = sp->sdu_list.next_write_indx = 0;
554 }
555 #endif /* ISOAL_BUFFER_RX_SDUS_ENABLE */
556
557 if (emit_sdu_current) {
558 if (sdu_frag.sdu_state == BT_ISO_SINGLE) {
559 sdu_status.total_sdu_size = sdu_frag.sdu_frag_size;
560 sdu_status.collated_status = sdu_frag.sdu.status;
561 }
562
563 ISOAL_LOG_DBG("[%p] SDU %u @TS=%u err=%X len=%u released\n",
564 sink, sdu_frag.sdu.sn, sdu_frag.sdu.timestamp,
565 sdu_status.collated_status, sdu_status.total_sdu_size);
566 err |= session->sdu_emit(sink, &sdu_frag, &sdu_status);
567
568 #if defined(ISOAL_BUFFER_RX_SDUS_ENABLE)
569 } else if (next_write_indx < CONFIG_BT_CTLR_ISO_RX_SDU_BUFFERS) {
570 sp->sdu_list.list[next_write_indx++] = sdu_frag;
571 sp->sdu_list.next_write_indx = next_write_indx;
572 #endif /* ISOAL_BUFFER_RX_SDUS_ENABLE */
573 } else {
574 /* Unreachable */
575 LL_ASSERT(0);
576 }
577
578 return err;
579 }
580
isoal_rx_try_emit_sdu(struct isoal_sink * sink,bool end_of_sdu)581 static isoal_status_t isoal_rx_try_emit_sdu(struct isoal_sink *sink, bool end_of_sdu)
582 {
583 struct isoal_sink_session *session;
584 struct isoal_sdu_production *sp;
585 struct isoal_sdu_produced *sdu;
586 isoal_status_t err;
587
588 err = ISOAL_STATUS_OK;
589 sp = &sink->sdu_production;
590 session = &sink->session;
591 sdu = &sp->sdu;
592
593 /* Emit a SDU */
594 const bool sdu_complete = (sp->sdu_available == 0) || end_of_sdu;
595
596 if (end_of_sdu) {
597 sp->sdu_available = 0;
598 }
599
600 if (sdu_complete) {
601 uint8_t next_state = BT_ISO_START;
602
603 switch (sp->sdu_state) {
604 case BT_ISO_START:
605 if (end_of_sdu) {
606 sp->sdu_state = BT_ISO_SINGLE;
607 next_state = BT_ISO_START;
608 } else {
609 sp->sdu_state = BT_ISO_START;
610 next_state = BT_ISO_CONT;
611 }
612 break;
613 case BT_ISO_CONT:
614 if (end_of_sdu) {
615 sp->sdu_state = BT_ISO_END;
616 next_state = BT_ISO_START;
617 } else {
618 sp->sdu_state = BT_ISO_CONT;
619 next_state = BT_ISO_CONT;
620 }
621 break;
622 }
623 sdu->status = sp->sdu_status;
624
625 err = isoal_rx_buffered_emit_sdu(sink, end_of_sdu);
626 sp->sdu_allocated = 0U;
627
628 if (end_of_sdu) {
629 isoal_rx_framed_update_sdu_release(sink);
630 sp->sdu_status = ISOAL_SDU_STATUS_VALID;
631 session->sn++;
632 }
633
634 /* update next state */
635 sink->sdu_production.sdu_state = next_state;
636 }
637
638 return err;
639 }
640
isoal_rx_append_to_sdu(struct isoal_sink * sink,const struct isoal_pdu_rx * pdu_meta,uint8_t offset,uint8_t length,bool is_end_fragment,bool is_padding)641 static isoal_status_t isoal_rx_append_to_sdu(struct isoal_sink *sink,
642 const struct isoal_pdu_rx *pdu_meta,
643 uint8_t offset,
644 uint8_t length,
645 bool is_end_fragment,
646 bool is_padding)
647 {
648 isoal_pdu_len_t packet_available;
649 const uint8_t *pdu_payload;
650 bool handle_error_case;
651 isoal_status_t err;
652
653 /* Might get an empty packed due to errors, we will need to terminate
654 * and send something up anyhow
655 */
656 packet_available = length;
657 handle_error_case = (is_end_fragment && (packet_available == 0));
658
659 pdu_payload = pdu_meta->pdu->payload + offset;
660 LL_ASSERT(pdu_payload);
661
662 /* While there is something left of the packet to consume */
663 err = ISOAL_STATUS_OK;
664 while ((packet_available > 0) || handle_error_case) {
665 isoal_status_t err_alloc;
666 struct isoal_sdu_production *sp;
667 struct isoal_sdu_produced *sdu;
668
669 err_alloc = ISOAL_STATUS_OK;
670 if (!is_padding) {
671 /* A new SDU should only be allocated if the current is
672 * not padding. Covers a situation where the end
673 * fragment was not received.
674 */
675 err_alloc = isoal_rx_allocate_sdu(sink, pdu_meta);
676 }
677
678 sp = &sink->sdu_production;
679 sdu = &sp->sdu;
680
681 err |= err_alloc;
682
683 /*
684 * For this SDU we can only consume of packet, bounded by:
685 * - What can fit in the destination SDU.
686 * - What remains of the packet.
687 */
688 const size_t consume_len = MIN(
689 packet_available,
690 sp->sdu_available
691 );
692
693 if (consume_len > 0) {
694 const struct isoal_sink_session *session = &sink->session;
695
696 err |= session->sdu_write(sdu->contents.dbuf,
697 sp->sdu_written,
698 pdu_payload,
699 consume_len);
700 pdu_payload += consume_len;
701 sp->sdu_written += consume_len;
702 sp->sdu_available -= consume_len;
703 packet_available -= consume_len;
704 }
705 bool end_of_sdu = (packet_available == 0) && is_end_fragment;
706
707 isoal_status_t err_emit = ISOAL_STATUS_OK;
708
709 if (sp->sdu_allocated) {
710 /* SDU should be emitted only if it was allocated */
711 err_emit = isoal_rx_try_emit_sdu(sink, end_of_sdu);
712 }
713
714 handle_error_case = false;
715 err |= err_emit;
716 }
717
718 return err;
719 }
720
721
722 /**
723 * @brief Consume an unframed PDU: Copy contents into SDU(s) and emit to a sink
724 * @details Destination sink may have an already partially built SDU
725 *
726 * @param sink[in,out] Destination sink with bookkeeping state
727 * @param pdu_meta[out] PDU with meta information (origin, timing, status)
728 *
729 * @return Status
730 */
isoal_rx_unframed_consume(struct isoal_sink * sink,const struct isoal_pdu_rx * pdu_meta)731 static isoal_status_t isoal_rx_unframed_consume(struct isoal_sink *sink,
732 const struct isoal_pdu_rx *pdu_meta)
733 {
734 struct isoal_sink_session *session;
735 struct isoal_sdu_production *sp;
736 struct node_rx_iso_meta *meta;
737 struct pdu_iso *pdu;
738 bool end_of_packet;
739 uint8_t next_state;
740 isoal_status_t err;
741 bool pdu_padding;
742 uint8_t length;
743 bool last_pdu;
744 bool pdu_err;
745 bool seq_err;
746 uint8_t llid;
747
748 sp = &sink->sdu_production;
749 session = &sink->session;
750 meta = pdu_meta->meta;
751 pdu = pdu_meta->pdu;
752
753 err = ISOAL_STATUS_OK;
754 next_state = ISOAL_START;
755
756 /* If status is not ISOAL_PDU_STATUS_VALID, length and LLID cannot be trusted */
757 llid = pdu->ll_id;
758 pdu_err = (pdu_meta->meta->status != ISOAL_PDU_STATUS_VALID);
759 length = pdu_err ? 0U : pdu->len;
760 /* A zero length PDU with LLID 0b01 (PDU_BIS_LLID_START_CONTINUE) would be a padding PDU.
761 * However if there are errors in the PDU, it could be an incorrectly receive non-padding
762 * PDU. Therefore only consider a PDU with errors as padding if received after the end
763 * fragment is seen when padding PDUs are expected.
764 */
765 pdu_padding = (length == 0) && (llid == PDU_BIS_LLID_START_CONTINUE) &&
766 (!pdu_err || sp->fsm == ISOAL_ERR_SPOOL);
767 seq_err = (meta->payload_number != (sp->prev_pdu_id+1));
768
769 /* If there are no buffers available, the PDUs received by the ISO-AL
770 * may not be in sequence even though this is expected for unframed rx.
771 * It would be necessary to exit the ISOAL_ERR_SPOOL state as the PDU
772 * count and as a result the last_pdu detection is no longer reliable.
773 */
774 if (sp->fsm == ISOAL_ERR_SPOOL) {
775 if ((!pdu_err && !seq_err &&
776 /* Previous sequence error should have move to the
777 * ISOAL_ERR_SPOOL state and emitted the SDU in production. No
778 * PDU error so LLID and length are reliable and no sequence
779 * error so this PDU is the next in order.
780 */
781 ((sp->prev_pdu_is_end || sp->prev_pdu_is_padding) &&
782 ((llid == PDU_BIS_LLID_START_CONTINUE && length > 0) ||
783 (llid == PDU_BIS_LLID_COMPLETE_END && length == 0))))
784 /* Detected a start of a new SDU as the last PDU was an end
785 * fragment or padding and the current is the start of a new SDU
786 * (either filled or zero length). Move to ISOAL_START
787 * immediately.
788 */
789
790 || (meta->payload_number % session->pdus_per_sdu == 0)) {
791 /* Based on the payload number, this should be the start
792 * of a new SDU.
793 */
794 sp->fsm = ISOAL_START;
795 }
796 }
797
798 if (sp->fsm == ISOAL_START) {
799 struct isoal_sdu_produced *sdu;
800 uint32_t anchorpoint;
801 uint16_t sdu_offset;
802 int32_t latency;
803
804 sp->sdu_status = ISOAL_SDU_STATUS_VALID;
805 sp->sdu_state = BT_ISO_START;
806 sp->pdu_cnt = 1;
807 sp->only_padding = pdu_padding;
808 seq_err = false;
809
810 /* The incoming time stamp for each PDU is expected to be the
811 * CIS / BIS reference anchor point. SDU reference point is
812 * reconstructed by adding the precalculated latency constant.
813 *
814 * BT Core V5.3 : Vol 6 Low Energy Controller : Part G IS0-AL:
815 * 3.2.2 SDU synchronization reference using unframed PDUs:
816 *
817 * The CIS reference anchor point is computed excluding any
818 * retransmissions or missed subevents and shall be set to the
819 * start of the isochronous event in which the first PDU
820 * containing the SDU could have been transferred.
821 *
822 * The BIG reference anchor point is the anchor point of the BIG
823 * event that the PDU is associated with.
824 */
825 anchorpoint = meta->timestamp;
826 latency = session->sdu_sync_const;
827 sdu = &sp->sdu;
828 sdu->timestamp = isoal_get_wrapped_time_us(anchorpoint, latency);
829
830 /* If there are multiple SDUs in an ISO interval
831 * (SDU interval < ISO Interval) every SDU after the first
832 * should add an SDU interval to the time stamp.
833 *
834 * BT Core V5.3 : Vol 6 Low Energy Controller : Part G IS0-AL:
835 * 3.2.2 SDU synchronization reference using unframed PDUs:
836 *
837 * All PDUs belonging to a burst as defined by the configuration
838 * of BN have the same reference anchor point. When multiple
839 * SDUs have the same reference anchor point, the first SDU uses
840 * the reference anchor point timing. Each subsequent SDU
841 * increases the SDU synchronization reference timing with one
842 * SDU interval.
843 */
844 sdu_offset = (meta->payload_number % session->burst_number) / session->pdus_per_sdu;
845 sdu->timestamp = isoal_get_wrapped_time_us(sdu->timestamp,
846 sdu_offset * session->sdu_interval);
847 } else {
848 sp->pdu_cnt++;
849 }
850
851 last_pdu = (sp->pdu_cnt == session->pdus_per_sdu);
852 end_of_packet = (llid == PDU_BIS_LLID_COMPLETE_END) || last_pdu || pdu_err;
853 sp->only_padding = sp->only_padding && pdu_padding;
854
855 switch (sp->fsm) {
856 case ISOAL_START:
857 case ISOAL_CONTINUE:
858 if (pdu_err || seq_err) {
859 /* PDU contains errors */
860 if (last_pdu) {
861 /* Last PDU all done */
862 next_state = ISOAL_START;
863 } else {
864 next_state = ISOAL_ERR_SPOOL;
865 }
866 } else if (llid == PDU_BIS_LLID_START_CONTINUE) {
867 /* PDU contains a continuation (neither start of end) fragment of SDU */
868 if (last_pdu) {
869 /* last pdu in sdu, but end fragment not seen, emit with error */
870 next_state = ISOAL_START;
871 } else {
872 next_state = ISOAL_CONTINUE;
873 }
874 } else if (llid == PDU_BIS_LLID_COMPLETE_END) {
875 /* PDU contains end fragment of a fragmented SDU */
876 if (last_pdu) {
877 /* Last PDU all done */
878 next_state = ISOAL_START;
879 } else {
880 /* Padding after end fragment to follow */
881 next_state = ISOAL_ERR_SPOOL;
882 }
883 } else {
884 /* Unsupported case */
885 err = ISOAL_STATUS_ERR_UNSPECIFIED;
886 LOG_ERR("Invalid unframed LLID (%d)", llid);
887 LL_ASSERT(0);
888 }
889 break;
890
891 case ISOAL_ERR_SPOOL:
892 /* State assumes that at end fragment or err has been seen,
893 * now just consume the rest
894 */
895 if (last_pdu) {
896 /* Last padding seen, restart */
897 next_state = ISOAL_START;
898 } else {
899 next_state = ISOAL_ERR_SPOOL;
900 }
901 break;
902
903 }
904
905 /* Update error state */
906 /* Prioritisation:
907 * (1) Sequence Error should set the ISOAL_SDU_STATUS_LOST_DATA status
908 * as data is missing and this will trigger the HCI to discard any
909 * data received.
910 *
911 * BT Core V5.3 : Vol 4 HCI I/F : Part G HCI Func. Spec.:
912 * 5.4.5 HCI ISO Data packets
913 * If Packet_Status_Flag equals 0b10 then PB_Flag shall equal 0b10.
914 * When Packet_Status_Flag is set to 0b10 in packets from the Controller to the
915 * Host, there is no data and ISO_SDU_Length shall be set to zero.
916 *
917 * (2) Any error status received from the LL via the PDU status should
918 * set the relevant error conditions
919 *
920 * (3) Forcing lost data when receiving only padding PDUs for any SDU
921 *
922 * https://bluetooth.atlassian.net/browse/ES-22876
923 * Request for Clarification - Recombination actions when only
924 * padding unframed PDUs are received:
925 * The clarification was to be rejected, but the discussion in the
926 * comments from March 3rd 2023 were interpreted as "We are
927 * expecting a PDU which ISOAL should convert into an SDU;
928 * instead we receive a padding PDU, which we cannot turn into a
929 * SDU, so the SDU wasn't received at all, and should be reported
930 * as such".
931 *
932 * (4) Missing end fragment handling.
933 */
934 if (seq_err) {
935 sp->sdu_status |= ISOAL_SDU_STATUS_LOST_DATA;
936 } else if (pdu_err && !pdu_padding) {
937 sp->sdu_status |= meta->status;
938 } else if (last_pdu && sp->only_padding) {
939 /* Force lost data if only padding PDUs */
940 sp->sdu_status |= ISOAL_SDU_STATUS_LOST_DATA;
941 } else if (last_pdu && (llid != PDU_BIS_LLID_COMPLETE_END) &&
942 (sp->fsm != ISOAL_ERR_SPOOL)) {
943 /* END fragment never seen */
944 sp->sdu_status |= ISOAL_SDU_STATUS_ERRORS;
945 }
946
947 /* Append valid PDU to SDU */
948 if (sp->fsm != ISOAL_ERR_SPOOL && (!pdu_padding || end_of_packet)) {
949 /* If only padding PDUs are received, an SDU should be released
950 * as missing (lost data) even if there are no actual errors.
951 * (Refer to error prioritisation above for details).
952 */
953 bool append_as_padding = pdu_padding && !sp->only_padding;
954 err |= isoal_rx_append_to_sdu(sink, pdu_meta, 0,
955 length, end_of_packet,
956 append_as_padding);
957 }
958
959 /* Update next state */
960 sp->fsm = next_state;
961 sp->prev_pdu_id = meta->payload_number;
962 sp->prev_pdu_is_end = !pdu_err && llid == PDU_BIS_LLID_COMPLETE_END;
963 sp->prev_pdu_is_padding = !pdu_err && pdu_padding;
964
965 sp->initialized = 1U;
966
967 return err;
968 }
969
970 /* Check a given segment for errors */
isoal_check_seg_header(struct pdu_iso_sdu_sh * seg_hdr,uint8_t pdu_size_remaining)971 static isoal_sdu_status_t isoal_check_seg_header(struct pdu_iso_sdu_sh *seg_hdr,
972 uint8_t pdu_size_remaining)
973 {
974 if (!seg_hdr) {
975 /* Segment header is null */
976 return ISOAL_SDU_STATUS_ERRORS;
977 }
978
979 if (pdu_size_remaining >= PDU_ISO_SEG_HDR_SIZE &&
980 pdu_size_remaining >= PDU_ISO_SEG_HDR_SIZE + seg_hdr->len) {
981
982 /* Valid if there is sufficient data for the segment header and
983 * there is sufficient data for the required length of the
984 * segment
985 */
986 return ISOAL_SDU_STATUS_VALID;
987 }
988
989 /* Data is missing from the PDU */
990 return ISOAL_SDU_STATUS_LOST_DATA;
991 }
992
993 /* Check available time reference and release any missing / lost SDUs
994 *
995 * Time tracking and release of lost SDUs for framed:
996 *
997 * Time tracking is implemented based on using the incoming time-stamps of the
998 * PDUs, which should correspond to the BIG / CIG reference anchorpoint of the
999 * current event, to track how time has advanced. The reference used is the
1000 * reconstructed SDU synchronisation reference point. For the CIS peripheral and
1001 * BIS receiver, this reference is ahead of the time-stamp (anchorpoint),
1002 * however for the CIS central this reference will be before (i.e. in the past).
1003 * Where the time offset is not available, an ISO interval is used in place of
1004 * the time offset to create an approximate reference.
1005 *
1006 * This information is in-turn used to decided if SDUs are missing or lost and
1007 * when they should be released. This approach is inherrently bursty with the
1008 * most probable worst case burst being 2 x (ISO interval / SDU Interval) SDUs,
1009 * which would occur when only padding is seen in one event followed by all the
1010 * SDUs from the next event in one PDU.
1011 */
isoal_rx_framed_release_lost_sdus(struct isoal_sink * sink,const struct isoal_pdu_rx * pdu_meta,bool timestamp_valid,uint32_t next_sdu_timestamp)1012 static isoal_status_t isoal_rx_framed_release_lost_sdus(struct isoal_sink *sink,
1013 const struct isoal_pdu_rx *pdu_meta,
1014 bool timestamp_valid,
1015 uint32_t next_sdu_timestamp)
1016 {
1017 struct isoal_sink_session *session;
1018 struct isoal_sdu_production *sp;
1019 struct isoal_sdu_produced *sdu;
1020 isoal_status_t err;
1021 uint32_t time_elapsed;
1022
1023 sp = &sink->sdu_production;
1024 session = &sink->session;
1025 sdu = &sp->sdu;
1026
1027 err = ISOAL_STATUS_OK;
1028
1029 if (isoal_get_time_diff(sdu->timestamp, next_sdu_timestamp, &time_elapsed)) {
1030 /* Time elapsed >= 0 */
1031 uint8_t lost_sdus;
1032
1033 if (timestamp_valid) {
1034 /* If there is a valid new time reference, then
1035 * calculate the gap between the next SDUs expected
1036 * time stamp and the actual reference, rounding at the
1037 * mid point.
1038 * 0 Next SDU is the SDU that provided the new time
1039 * reference, no lost SDUs
1040 * >0 Number of lost SDUs
1041 */
1042 lost_sdus = (time_elapsed + (session->sdu_interval / 2)) /
1043 session->sdu_interval;
1044 ISOAL_LOG_DBGV("[%p] Next SDU timestamp (%lu) accurate",
1045 sink, next_sdu_timestamp);
1046 } else {
1047 /* If there is no valid new time reference, then lost
1048 * SDUs should only be released for every full
1049 * SDU interval. This should include consideration that
1050 * the next expected SDU's time stamp is the base for
1051 * time_elapsed (i.e. +1).
1052 */
1053 ISOAL_LOG_DBGV("[%p] Next SDU timestamp (%lu) approximate",
1054 sink, next_sdu_timestamp);
1055 lost_sdus = time_elapsed ? (time_elapsed / session->sdu_interval) + 1 : 0;
1056 }
1057
1058 ISOAL_LOG_DBGV("[%p] Releasing %u lost SDUs", sink, lost_sdus);
1059
1060 while (lost_sdus > 0 && !err) {
1061 sp->sdu_status |= ISOAL_SDU_STATUS_LOST_DATA;
1062
1063 err = isoal_rx_append_to_sdu(sink, pdu_meta, 0, 0, true, false);
1064 lost_sdus--;
1065 }
1066 }
1067
1068 return err;
1069 }
1070
1071 /* Update time tracking after release of an SDU.
1072 * At present only required for framed PDUs.
1073 */
isoal_rx_framed_update_sdu_release(struct isoal_sink * sink)1074 static void isoal_rx_framed_update_sdu_release(struct isoal_sink *sink)
1075 {
1076 struct isoal_sink_session *session;
1077 struct isoal_sdu_production *sp;
1078 struct isoal_sdu_produced *sdu;
1079 uint32_t timestamp;
1080
1081 sp = &sink->sdu_production;
1082 session = &sink->session;
1083 sdu = &sp->sdu;
1084
1085 if (session->framed) {
1086 /* Update to the expected release time of the next SDU */
1087 timestamp = isoal_get_wrapped_time_us(sdu->timestamp, session->sdu_interval);
1088 SET_RX_SDU_TIMESTAMP(sink, sdu->timestamp, timestamp);
1089 }
1090 }
1091
1092 /**
1093 * @brief Consume a framed PDU: Copy contents into SDU(s) and emit to a sink
1094 * @details Destination sink may have an already partially built SDU
1095 *
1096 * @param sink[in,out] Destination sink with bookkeeping state
1097 * @param pdu_meta[out] PDU with meta information (origin, timing, status)
1098 *
1099 * @return Status
1100 */
isoal_rx_framed_consume(struct isoal_sink * sink,const struct isoal_pdu_rx * pdu_meta)1101 static isoal_status_t isoal_rx_framed_consume(struct isoal_sink *sink,
1102 const struct isoal_pdu_rx *pdu_meta)
1103 {
1104 struct isoal_sink_session *session;
1105 struct isoal_sdu_production *sp;
1106 struct isoal_sdu_produced *sdu;
1107 struct pdu_iso_sdu_sh *seg_hdr;
1108 struct node_rx_iso_meta *meta;
1109 uint32_t iso_interval_us;
1110 uint32_t anchorpoint;
1111 uint8_t *end_of_pdu;
1112 uint32_t timeoffset;
1113 isoal_status_t err;
1114 uint8_t next_state;
1115 uint32_t timestamp;
1116 bool pdu_padding;
1117 int32_t latency;
1118 bool pdu_err;
1119 bool seq_err;
1120 bool seg_err;
1121
1122 sp = &sink->sdu_production;
1123 session = &sink->session;
1124 meta = pdu_meta->meta;
1125 sdu = &sp->sdu;
1126
1127 iso_interval_us = session->iso_interval * ISO_INT_UNIT_US;
1128
1129 err = ISOAL_STATUS_OK;
1130 next_state = ISOAL_START;
1131 pdu_err = (pdu_meta->meta->status != ISOAL_PDU_STATUS_VALID);
1132 pdu_padding = (pdu_meta->pdu->len == 0);
1133
1134 if (sp->fsm == ISOAL_START) {
1135 seq_err = false;
1136 } else {
1137 seq_err = (meta->payload_number != (sp->prev_pdu_id + 1));
1138 }
1139
1140 end_of_pdu = ((uint8_t *) pdu_meta->pdu->payload) + pdu_meta->pdu->len - 1UL;
1141 seg_hdr = (pdu_err || seq_err || pdu_padding) ? NULL :
1142 (struct pdu_iso_sdu_sh *) pdu_meta->pdu->payload;
1143
1144 seg_err = false;
1145 if (seg_hdr && isoal_check_seg_header(seg_hdr, pdu_meta->pdu->len) ==
1146 ISOAL_SDU_STATUS_LOST_DATA) {
1147 seg_err = true;
1148 seg_hdr = NULL;
1149 }
1150
1151 /* Calculate an approximate timestamp */
1152 timestamp = isoal_get_wrapped_time_us(meta->timestamp,
1153 session->sdu_sync_const - iso_interval_us);
1154 if (!sp->initialized) {
1155 /* This should be the first PDU received in this session */
1156 /* Initialize a temporary timestamp for the next SDU */
1157 SET_RX_SDU_TIMESTAMP(sink, sdu->timestamp, timestamp);
1158 }
1159
1160 if (pdu_padding && !pdu_err && !seq_err) {
1161 /* Check and release missed SDUs on receiving padding PDUs */
1162 ISOAL_LOG_DBGV("[%p] Received padding", sink);
1163 err |= isoal_rx_framed_release_lost_sdus(sink, pdu_meta, false, timestamp);
1164 }
1165
1166 while (seg_hdr) {
1167 bool append = true;
1168 const uint8_t sc = seg_hdr->sc;
1169 const uint8_t cmplt = seg_hdr->cmplt;
1170
1171 if (sp->fsm == ISOAL_START) {
1172 sp->sdu_status = ISOAL_SDU_STATUS_VALID;
1173 sp->sdu_state = BT_ISO_START;
1174 }
1175
1176 ISOAL_LOG_DBGV("[%p] State %s", sink, FSM_TO_STR(sp->fsm));
1177 switch (sp->fsm) {
1178 case ISOAL_START:
1179 if (!sc) {
1180 /* Start segment, included time-offset */
1181 timeoffset = sys_le24_to_cpu(seg_hdr->timeoffset);
1182 anchorpoint = meta->timestamp;
1183 latency = session->sdu_sync_const;
1184 timestamp = isoal_get_wrapped_time_us(anchorpoint,
1185 latency - timeoffset);
1186 ISOAL_LOG_DBGV("[%p] Segment Start @TS=%ld", sink, timestamp);
1187
1188 err |= isoal_rx_framed_release_lost_sdus(sink, pdu_meta, true,
1189 timestamp);
1190 SET_RX_SDU_TIMESTAMP(sink, sdu->timestamp, timestamp);
1191
1192 if (cmplt) {
1193 /* The start of a new SDU that contains the full SDU data in
1194 * the current PDU.
1195 */
1196 ISOAL_LOG_DBGV("[%p] Segment Single", sink);
1197 next_state = ISOAL_START;
1198 } else {
1199 /* The start of a new SDU, where not all SDU data is
1200 * included in the current PDU, and additional PDUs are
1201 * required to complete the SDU.
1202 */
1203 next_state = ISOAL_CONTINUE;
1204 }
1205
1206 } else {
1207 /* Unsupported case */
1208 err = ISOAL_STATUS_ERR_UNSPECIFIED;
1209 }
1210 break;
1211
1212 case ISOAL_CONTINUE:
1213 if (sc && !cmplt) {
1214 /* The continuation of a previous SDU. The SDU payload is appended
1215 * to the previous data and additional PDUs are required to
1216 * complete the SDU.
1217 */
1218 ISOAL_LOG_DBGV("[%p] Segment Continue", sink);
1219 next_state = ISOAL_CONTINUE;
1220 } else if (sc && cmplt) {
1221 /* The continuation of a previous SDU.
1222 * Frame data is appended to previously received SDU data and
1223 * completes in the current PDU.
1224 */
1225 ISOAL_LOG_DBGV("[%p] Segment End", sink);
1226 next_state = ISOAL_START;
1227 } else {
1228 /* Unsupported case */
1229 err = ISOAL_STATUS_ERR_UNSPECIFIED;
1230 }
1231 break;
1232
1233 case ISOAL_ERR_SPOOL:
1234 /* In error state, search for valid next start of SDU */
1235
1236 if (!sc) {
1237 /* Start segment, included time-offset */
1238 timeoffset = sys_le24_to_cpu(seg_hdr->timeoffset);
1239 anchorpoint = meta->timestamp;
1240 latency = session->sdu_sync_const;
1241 timestamp = isoal_get_wrapped_time_us(anchorpoint,
1242 latency - timeoffset);
1243 ISOAL_LOG_DBGV("[%p] Segment Start @TS=%ld", sink, timestamp);
1244
1245 err |= isoal_rx_framed_release_lost_sdus(sink, pdu_meta, true,
1246 timestamp);
1247 SET_RX_SDU_TIMESTAMP(sink, sdu->timestamp, timestamp);
1248
1249 if (cmplt) {
1250 /* The start of a new SDU that contains the full SDU data
1251 * in the current PDU.
1252 */
1253 ISOAL_LOG_DBGV("[%p] Segment Single", sink);
1254 next_state = ISOAL_START;
1255 } else {
1256 /* The start of a new SDU, where not all SDU data is
1257 * included in the current PDU, and additional PDUs are
1258 * required to complete the SDU.
1259 */
1260 next_state = ISOAL_CONTINUE;
1261 }
1262
1263 } else {
1264 /* Start not found yet, stay in Error state */
1265 err |= isoal_rx_framed_release_lost_sdus(sink, pdu_meta, false,
1266 timestamp);
1267 append = false;
1268 next_state = ISOAL_ERR_SPOOL;
1269 }
1270
1271 if (next_state != ISOAL_ERR_SPOOL) {
1272 /* While in the Error state, received a valid start of the next SDU,
1273 * so SDU status and sequence number should be updated.
1274 */
1275 sp->sdu_status = ISOAL_SDU_STATUS_VALID;
1276 /* sp->sdu_state will be set by next_state decided above */
1277 }
1278 break;
1279 }
1280
1281 if (append) {
1282 /* Calculate offset of first payload byte from SDU based on assumption
1283 * of No time_offset in header
1284 */
1285 uint8_t offset = ((uint8_t *) seg_hdr) + PDU_ISO_SEG_HDR_SIZE -
1286 pdu_meta->pdu->payload;
1287 uint8_t length = seg_hdr->len;
1288
1289 if (!sc) {
1290 /* time_offset included in header, don't copy offset field to SDU */
1291 offset = offset + PDU_ISO_SEG_TIMEOFFSET_SIZE;
1292 length = length - PDU_ISO_SEG_TIMEOFFSET_SIZE;
1293 }
1294
1295 /* Todo: check if effective len=0 what happens then?
1296 * We should possibly be able to send empty packets with only time stamp
1297 */
1298 ISOAL_LOG_DBGV("[%p] Appending %lu bytes", sink, length);
1299 err |= isoal_rx_append_to_sdu(sink, pdu_meta, offset, length, cmplt, false);
1300 }
1301
1302 /* Update next state */
1303 ISOAL_LOG_DBGV("[%p] FSM Next State %s", sink, FSM_TO_STR(next_state));
1304 sp->fsm = next_state;
1305
1306 /* Find next segment header, set to null if past end of PDU */
1307 seg_hdr = (struct pdu_iso_sdu_sh *) (((uint8_t *) seg_hdr) +
1308 seg_hdr->len + PDU_ISO_SEG_HDR_SIZE);
1309
1310 if (((uint8_t *) seg_hdr) > end_of_pdu) {
1311 seg_hdr = NULL;
1312 } else if (isoal_check_seg_header(seg_hdr,
1313 (uint8_t)(end_of_pdu + 1UL - ((uint8_t *) seg_hdr))) ==
1314 ISOAL_SDU_STATUS_LOST_DATA) {
1315 seg_err = true;
1316 seg_hdr = NULL;
1317 }
1318 }
1319
1320 if (pdu_err || seq_err || seg_err) {
1321 bool error_sdu_pending = false;
1322
1323 /* When one or more ISO Data PDUs are not received, the receiving device may
1324 * discard all SDUs affected by the missing PDUs. Any partially received SDU
1325 * may also be discarded.
1326 */
1327 next_state = ISOAL_ERR_SPOOL;
1328
1329
1330 /* This maps directly to the HCI ISO Data packet Packet_Status_Flag by way of the
1331 * sdu_status in the SDU emitted.
1332 * BT Core V5.3 : Vol 4 HCI I/F : Part G HCI Func. Spec.:
1333 * 5.4.5 HCI ISO Data packets : Table 5.2 :
1334 * Packet_Status_Flag (in packets sent by the Controller)
1335 * 0b00 Valid data. The complete SDU was received correctly.
1336 * 0b01 Possibly invalid data. The contents of the ISO_SDU_Fragment may contain
1337 * errors or part of the SDU may be missing. This is reported as "data with
1338 * possible errors".
1339 * 0b10 Part(s) of the SDU were not received correctly. This is reported as
1340 * "lost data".
1341 */
1342 isoal_sdu_status_t next_sdu_status = ISOAL_SDU_STATUS_VALID;
1343 if (seq_err || seg_err) {
1344 next_sdu_status |= ISOAL_SDU_STATUS_LOST_DATA;
1345 } else if (pdu_err) {
1346 next_sdu_status |= meta->status;
1347 }
1348
1349 switch (sp->fsm) {
1350 case ISOAL_START:
1351 /* If errors occur while waiting for the start of an SDU
1352 * then an SDU should should only be released if there
1353 * is confirmation that a reception occurred
1354 * unsuccessfully. In the case of STATUS_LOST_DATA which
1355 * could result from a flush, an SDU should not be
1356 * released as the flush does not necessarily mean that
1357 * part of an SDU has been lost. In this case Lost SDU
1358 * release defaults to the lost SDU detection based on
1359 * the SDU interval. If we have a SDU to release
1360 * following any lost SDUs, lost SDU handling should be
1361 * similar to when a valid timestamp for the next SDU
1362 * exists.
1363 */
1364 error_sdu_pending = seg_err ||
1365 (pdu_err && meta->status == ISOAL_SDU_STATUS_ERRORS);
1366 err |= isoal_rx_framed_release_lost_sdus(sink, pdu_meta,
1367 error_sdu_pending, timestamp);
1368
1369 if (error_sdu_pending) {
1370 sp->sdu_status = next_sdu_status;
1371 err |= isoal_rx_append_to_sdu(sink, pdu_meta, 0U, 0U, true, false);
1372 }
1373 break;
1374
1375 case ISOAL_CONTINUE:
1376 /* If error occurs while an SDU is in production,
1377 * release the SDU with errors and then check for lost
1378 * SDUs. Since the SDU is already in production, the
1379 * time stamp already set should be valid.
1380 */
1381 sp->sdu_status = next_sdu_status;
1382 err |= isoal_rx_append_to_sdu(sink, pdu_meta, 0, 0, true, false);
1383 err |= isoal_rx_framed_release_lost_sdus(sink, pdu_meta,
1384 error_sdu_pending, timestamp);
1385 break;
1386
1387 case ISOAL_ERR_SPOOL:
1388 err |= isoal_rx_framed_release_lost_sdus(sink, pdu_meta,
1389 error_sdu_pending, timestamp);
1390 break;
1391 }
1392
1393 /* Update next state */
1394 ISOAL_LOG_DBGV("[%p] FSM Error Next State %s", sink, FSM_TO_STR(next_state));
1395 sp->fsm = next_state;
1396 }
1397
1398 sp->prev_pdu_id = meta->payload_number;
1399 sp->initialized = 1U;
1400
1401 return err;
1402 }
1403
1404 /**
1405 * @brief Deep copy a PDU, recombine into SDU(s)
1406 * @details Recombination will occur individually for every enabled sink
1407 *
1408 * @param sink_hdl[in] Handle of destination sink
1409 * @param pdu_meta[in] PDU along with meta information (origin, timing, status)
1410 * @return Status
1411 */
isoal_rx_pdu_recombine(isoal_sink_handle_t sink_hdl,const struct isoal_pdu_rx * pdu_meta)1412 isoal_status_t isoal_rx_pdu_recombine(isoal_sink_handle_t sink_hdl,
1413 const struct isoal_pdu_rx *pdu_meta)
1414 {
1415 struct isoal_sink *sink = &isoal_global.sink_state[sink_hdl];
1416 isoal_status_t err = ISOAL_STATUS_OK;
1417
1418 if (sink && sink->sdu_production.mode != ISOAL_PRODUCTION_MODE_DISABLED) {
1419 if (sink->session.framed) {
1420 err = isoal_rx_framed_consume(sink, pdu_meta);
1421 } else {
1422 err = isoal_rx_unframed_consume(sink, pdu_meta);
1423 }
1424 }
1425
1426 return err;
1427 }
1428 #endif /* CONFIG_BT_CTLR_SYNC_ISO || CONFIG_BT_CTLR_CONN_ISO */
1429
1430 #if defined(CONFIG_BT_CTLR_ADV_ISO) || defined(CONFIG_BT_CTLR_CONN_ISO)
1431 /**
1432 * @brief Find free source from statically-sized pool and allocate it
1433 * @details Implemented as linear search since pool is very small
1434 *
1435 * @param hdl[out] Handle to source
1436 * @return ISOAL_STATUS_OK if we could allocate; otherwise ISOAL_STATUS_ERR_SOURCE_ALLOC
1437 */
isoal_source_allocate(isoal_source_handle_t * hdl)1438 static isoal_status_t isoal_source_allocate(isoal_source_handle_t *hdl)
1439 {
1440 isoal_source_handle_t i;
1441
1442 /* Very small linear search to find first free */
1443 for (i = 0; i < CONFIG_BT_CTLR_ISOAL_SOURCES; i++) {
1444 if (isoal_global.source_allocated[i] == ISOAL_ALLOC_STATE_FREE) {
1445 isoal_global.source_allocated[i] = ISOAL_ALLOC_STATE_TAKEN;
1446 *hdl = i;
1447 return ISOAL_STATUS_OK;
1448 }
1449 }
1450
1451 return ISOAL_STATUS_ERR_SOURCE_ALLOC; /* All entries were taken */
1452 }
1453
1454 /**
1455 * @brief Mark a source as being free to allocate again
1456 * @param hdl[in] Handle to source
1457 */
isoal_source_deallocate(isoal_source_handle_t hdl)1458 static void isoal_source_deallocate(isoal_source_handle_t hdl)
1459 {
1460 struct isoal_pdu_production *pp;
1461 struct isoal_source *source;
1462
1463 if (hdl < ARRAY_SIZE(isoal_global.source_state)) {
1464 source = &isoal_global.source_state[hdl];
1465 } else {
1466 LL_ASSERT(0);
1467 return;
1468 }
1469
1470 pp = &source->pdu_production;
1471
1472 if (pp->pdu_available > 0) {
1473 /* There is a pending PDU that should be released */
1474 if (source && source->session.pdu_release) {
1475 source->session.pdu_release(pp->pdu.contents.handle,
1476 source->session.handle,
1477 ISOAL_STATUS_ERR_PDU_EMIT);
1478 }
1479 }
1480
1481 if (hdl < ARRAY_SIZE(isoal_global.source_allocated)) {
1482 isoal_global.source_allocated[hdl] = ISOAL_ALLOC_STATE_FREE;
1483 } else {
1484 LL_ASSERT(0);
1485 }
1486
1487 (void)memset(source, 0, sizeof(struct isoal_source));
1488 }
1489
1490 /**
1491 * @brief Check if a provided handle is valid
1492 * @param[in] hdl Input handle for validation
1493 * @return Handle valid / not valid
1494 */
isoal_check_source_hdl_valid(isoal_source_handle_t hdl)1495 static isoal_status_t isoal_check_source_hdl_valid(isoal_source_handle_t hdl)
1496 {
1497 if (hdl < ARRAY_SIZE(isoal_global.source_allocated) &&
1498 isoal_global.source_allocated[hdl] == ISOAL_ALLOC_STATE_TAKEN) {
1499 return ISOAL_STATUS_OK;
1500 }
1501
1502 LOG_ERR("Invalid source handle (0x%02x)", hdl);
1503
1504 return ISOAL_STATUS_ERR_UNSPECIFIED;
1505 }
1506
1507 /**
1508 * @brief Create a new source
1509 *
1510 * @param handle[in] Connection handle
1511 * @param role[in] Peripheral, Central or Broadcast
1512 * @param framed[in] Framed case
1513 * @param burst_number[in] Burst Number
1514 * @param flush_timeout[in] Flush timeout
1515 * @param max_octets[in] Maximum PDU size (Max_PDU_C_To_P / Max_PDU_P_To_C)
1516 * @param sdu_interval[in] SDU interval
1517 * @param iso_interval[in] ISO interval
1518 * @param stream_sync_delay[in] CIS / BIS sync delay
1519 * @param group_sync_delay[in] CIG / BIG sync delay
1520 * @param pdu_alloc[in] Callback of PDU allocator
1521 * @param pdu_write[in] Callback of PDU byte writer
1522 * @param pdu_emit[in] Callback of PDU emitter
1523 * @param pdu_release[in] Callback of PDU deallocator
1524 * @param hdl[out] Handle to new source
1525 *
1526 * @return ISOAL_STATUS_OK if we could create a new sink; otherwise ISOAL_STATUS_ERR_SOURCE_ALLOC
1527 */
isoal_source_create(uint16_t handle,uint8_t role,uint8_t framed,uint8_t burst_number,uint8_t flush_timeout,uint8_t max_octets,uint32_t sdu_interval,uint16_t iso_interval,uint32_t stream_sync_delay,uint32_t group_sync_delay,isoal_source_pdu_alloc_cb pdu_alloc,isoal_source_pdu_write_cb pdu_write,isoal_source_pdu_emit_cb pdu_emit,isoal_source_pdu_release_cb pdu_release,isoal_source_handle_t * hdl)1528 isoal_status_t isoal_source_create(
1529 uint16_t handle,
1530 uint8_t role,
1531 uint8_t framed,
1532 uint8_t burst_number,
1533 uint8_t flush_timeout,
1534 uint8_t max_octets,
1535 uint32_t sdu_interval,
1536 uint16_t iso_interval,
1537 uint32_t stream_sync_delay,
1538 uint32_t group_sync_delay,
1539 isoal_source_pdu_alloc_cb pdu_alloc,
1540 isoal_source_pdu_write_cb pdu_write,
1541 isoal_source_pdu_emit_cb pdu_emit,
1542 isoal_source_pdu_release_cb pdu_release,
1543 isoal_source_handle_t *hdl)
1544 {
1545 isoal_status_t err;
1546
1547 /* Allocate a new source */
1548 err = isoal_source_allocate(hdl);
1549 if (err) {
1550 return err;
1551 }
1552
1553 struct isoal_source_session *session = &isoal_global.source_state[*hdl].session;
1554
1555 session->handle = handle;
1556 session->framed = framed;
1557 session->burst_number = burst_number;
1558 session->iso_interval = iso_interval;
1559 session->sdu_interval = sdu_interval;
1560
1561 /* Todo: Next section computing various constants, should potentially be a
1562 * function in itself as a number of the dependencies could be changed while
1563 * a connection is active.
1564 */
1565
1566 /* Note: sdu_interval unit is uS, iso_interval is a multiple of 1.25mS */
1567 session->pdus_per_sdu = (burst_number * sdu_interval) /
1568 ((uint32_t)iso_interval * ISO_INT_UNIT_US);
1569 /* Set maximum PDU size */
1570 session->max_pdu_size = max_octets;
1571
1572 /* Remember the platform-specific callbacks */
1573 session->pdu_alloc = pdu_alloc;
1574 session->pdu_write = pdu_write;
1575 session->pdu_emit = pdu_emit;
1576 session->pdu_release = pdu_release;
1577
1578 /* TODO: Constant need to be updated */
1579
1580 /* Initialize running seq number to zero */
1581 session->sn = 0;
1582
1583 return err;
1584 }
1585
1586 /**
1587 * @brief Atomically enable latch-in of packets and PDU production
1588 * @param hdl[in] Handle of existing instance
1589 */
isoal_source_enable(isoal_source_handle_t hdl)1590 void isoal_source_enable(isoal_source_handle_t hdl)
1591 {
1592 if (hdl < ARRAY_SIZE(isoal_global.source_state)) {
1593 /* Reset bookkeeping state */
1594 memset(&isoal_global.source_state[hdl].pdu_production, 0,
1595 sizeof(isoal_global.source_state[hdl].pdu_production));
1596
1597 /* Atomically enable */
1598 isoal_global.source_state[hdl].pdu_production.mode = ISOAL_PRODUCTION_MODE_ENABLED;
1599 } else {
1600 LL_ASSERT(0);
1601 }
1602 }
1603
1604 /**
1605 * @brief Atomically disable latch-in of packets and PDU production
1606 * @param hdl[in] Handle of existing instance
1607 */
isoal_source_disable(isoal_source_handle_t hdl)1608 void isoal_source_disable(isoal_source_handle_t hdl)
1609 {
1610 if (hdl < ARRAY_SIZE(isoal_global.source_state)) {
1611 /* Atomically disable */
1612 isoal_global.source_state[hdl].pdu_production.mode = ISOAL_PRODUCTION_MODE_DISABLED;
1613 } else {
1614 LL_ASSERT(0);
1615 }
1616 }
1617
1618 /**
1619 * @brief Disable and deallocate existing source
1620 * @param hdl[in] Handle of existing instance
1621 */
isoal_source_destroy(isoal_source_handle_t hdl)1622 void isoal_source_destroy(isoal_source_handle_t hdl)
1623 {
1624 /* Atomic disable */
1625 isoal_source_disable(hdl);
1626
1627 /* Permit allocation anew */
1628 isoal_source_deallocate(hdl);
1629 }
1630
isoal_is_time_stamp_valid(const struct isoal_source * source_ctx,const uint32_t cntr_time,const uint32_t time_stamp)1631 static bool isoal_is_time_stamp_valid(const struct isoal_source *source_ctx,
1632 const uint32_t cntr_time,
1633 const uint32_t time_stamp)
1634 {
1635 const struct isoal_source_session *session;
1636 uint32_t time_diff;
1637
1638 session = &source_ctx->session;
1639
1640 /* This is an arbitrarily defined range. The purpose is to
1641 * decide if the time stamp provided by the host is sensible
1642 * within the controller's clock domain. An SDU interval plus ISO
1643 * interval is expected to provide a good balance between situations
1644 * where either could be significantly larger than the other.
1645 *
1646 * BT Core V5.4 : Vol 6 Low Energy Controller : Part G IS0-AL:
1647 * 3.3 Time Stamp for SDU :
1648 * When an HCI ISO Data packet sent by the Host does not contain
1649 * a Time Stamp or the Time_Stamp value is not based on the
1650 * Controller's clock, the Controller should determine the CIS
1651 * or BIS event to be used to transmit the SDU contained in that
1652 * packet based on the time of arrival of that packet.
1653 */
1654 const uint32_t sdu_interval_us = session->sdu_interval;
1655 const uint32_t iso_interval_us = session->iso_interval * ISO_INT_UNIT_US;
1656 /* ISO Interval 0x0000_0004 ~ 0x0000_0C80 x 1250 +
1657 * SDU Interval 0x0000_00FF ~ 0x000F_FFFF <= 004D_08FF
1658 */
1659 const int32_t time_stamp_valid_half_range = sdu_interval_us + iso_interval_us;
1660 const uint32_t time_stamp_valid_min = isoal_get_wrapped_time_us(cntr_time,
1661 (-time_stamp_valid_half_range));
1662 const uint32_t time_stamp_valid_range = 2 * time_stamp_valid_half_range;
1663 const bool time_stamp_is_valid = isoal_get_time_diff(time_stamp_valid_min,
1664 time_stamp,
1665 &time_diff) &&
1666 time_diff <= time_stamp_valid_range;
1667
1668 return time_stamp_is_valid;
1669 }
1670
1671 /**
1672 * Queue the PDU in production in the relevant LL transmit queue. If the
1673 * attempt to release the PDU fails, the buffer linked to the PDU will be released
1674 * and it will not be possible to retry the emit operation on the same PDU.
1675 * @param[in] source_ctx ISO-AL source reference for this CIS / BIS
1676 * @param[in] produced_pdu PDU in production
1677 * @param[in] pdu_ll_id LLID to be set indicating the type of fragment
1678 * @param[in] sdu_fragments Number of SDU HCI fragments consumed
1679 * @param[in] payload_number CIS / BIS payload number
1680 * @param[in] payload_size Length of the data written to the PDU
1681 * @return Error status of the operation
1682 */
isoal_tx_pdu_emit(const struct isoal_source * source_ctx,const struct isoal_pdu_produced * produced_pdu,const uint8_t pdu_ll_id,const uint8_t sdu_fragments,const uint64_t payload_number,const isoal_pdu_len_t payload_size)1683 static isoal_status_t isoal_tx_pdu_emit(const struct isoal_source *source_ctx,
1684 const struct isoal_pdu_produced *produced_pdu,
1685 const uint8_t pdu_ll_id,
1686 const uint8_t sdu_fragments,
1687 const uint64_t payload_number,
1688 const isoal_pdu_len_t payload_size)
1689 {
1690 struct node_tx_iso *node_tx;
1691 isoal_status_t status;
1692 uint16_t handle;
1693
1694 /* Retrieve CIS / BIS handle */
1695 handle = bt_iso_handle(source_ctx->session.handle);
1696
1697 /* Retrieve Node handle */
1698 node_tx = produced_pdu->contents.handle;
1699 /* Under race condition with isoal_source_deallocate() */
1700 if (!node_tx) {
1701 return ISOAL_STATUS_ERR_PDU_EMIT;
1702 }
1703
1704 /* Set payload number */
1705 node_tx->payload_count = payload_number & 0x7fffffffff;
1706 node_tx->sdu_fragments = sdu_fragments;
1707 /* Set PDU LLID */
1708 produced_pdu->contents.pdu->ll_id = pdu_ll_id;
1709 /* Set PDU length */
1710 produced_pdu->contents.pdu->len = (uint8_t)payload_size;
1711
1712 /* Attempt to enqueue the node towards the LL */
1713 status = source_ctx->session.pdu_emit(node_tx, handle);
1714
1715 ISOAL_LOG_DBG("[%p] PDU %llu err=%X len=%u frags=%u released",
1716 source_ctx, payload_number, status,
1717 produced_pdu->contents.pdu->len, sdu_fragments);
1718
1719 if (status != ISOAL_STATUS_OK) {
1720 /* If it fails, the node will be released and no further attempt
1721 * will be possible
1722 */
1723 LOG_ERR("Failed to enqueue node (%p)", node_tx);
1724 source_ctx->session.pdu_release(node_tx, handle, status);
1725 }
1726
1727 return status;
1728 }
1729
1730 /**
1731 * Allocates a new PDU only if the previous PDU was emitted
1732 * @param[in] source ISO-AL source reference
1733 * @param[in] tx_sdu SDU fragment to be transmitted (can be NULL)
1734 * @return Error status of operation
1735 */
isoal_tx_allocate_pdu(struct isoal_source * source,const struct isoal_sdu_tx * tx_sdu)1736 static isoal_status_t isoal_tx_allocate_pdu(struct isoal_source *source,
1737 const struct isoal_sdu_tx *tx_sdu)
1738 {
1739 ARG_UNUSED(tx_sdu);
1740
1741 struct isoal_source_session *session;
1742 struct isoal_pdu_production *pp;
1743 struct isoal_pdu_produced *pdu;
1744 isoal_status_t err;
1745
1746 err = ISOAL_STATUS_OK;
1747 session = &source->session;
1748 pp = &source->pdu_production;
1749 pdu = &pp->pdu;
1750
1751 /* Allocate a PDU if the previous was filled (thus sent) */
1752 const bool pdu_complete = (pp->pdu_available == 0);
1753
1754 if (pdu_complete) {
1755 /* Allocate new PDU buffer */
1756 err = session->pdu_alloc(
1757 &pdu->contents /* [out] Updated with pointer and size */
1758 );
1759
1760 if (err) {
1761 pdu->contents.handle = NULL;
1762 pdu->contents.pdu = NULL;
1763 pdu->contents.size = 0;
1764 }
1765
1766 /* Get maximum buffer available */
1767 const size_t available_len = MIN(
1768 session->max_pdu_size,
1769 pdu->contents.size
1770 );
1771
1772 /* Nothing has been written into buffer yet */
1773 pp->pdu_written = 0;
1774 pp->pdu_available = available_len;
1775 pp->pdu_allocated = 1U;
1776 LL_ASSERT(available_len > 0);
1777
1778 pp->pdu_cnt++;
1779 }
1780
1781 return err;
1782 }
1783
1784 /**
1785 * Attempt to emit the PDU in production if it is complete.
1786 * @param[in] source ISO-AL source reference
1787 * @param[in] force_emit Request PDU emit
1788 * @param[in] pdu_ll_id LLID / PDU fragment type as Start, Cont, End, Single (Unframed) or Framed
1789 * @return Error status of operation
1790 */
isoal_tx_try_emit_pdu(struct isoal_source * source,bool force_emit,uint8_t pdu_ll_id)1791 static isoal_status_t isoal_tx_try_emit_pdu(struct isoal_source *source,
1792 bool force_emit,
1793 uint8_t pdu_ll_id)
1794 {
1795 struct isoal_pdu_production *pp;
1796 struct isoal_pdu_produced *pdu;
1797 isoal_status_t err;
1798
1799 err = ISOAL_STATUS_OK;
1800 pp = &source->pdu_production;
1801 pdu = &pp->pdu;
1802
1803 /* Emit a PDU */
1804 const bool pdu_complete = (pp->pdu_available == 0) || force_emit;
1805
1806 if (force_emit) {
1807 pp->pdu_available = 0;
1808 }
1809
1810 if (pdu_complete) {
1811 /* Emit PDU and increment the payload number */
1812 err = isoal_tx_pdu_emit(source, pdu, pdu_ll_id,
1813 pp->sdu_fragments,
1814 pp->payload_number,
1815 pp->pdu_written);
1816 pp->payload_number++;
1817 pp->sdu_fragments = 0;
1818 pp->pdu_allocated = 0U;
1819 }
1820
1821 return err;
1822 }
1823
1824 /**
1825 * @brief Get the next unframed payload number for transmission based on the
1826 * input meta data in the TX SDU and the current production information.
1827 * @param source_hdl[in] Destination source handle
1828 * @param tx_sdu[in] SDU with meta data information
1829 * @param payload_number[out] Next payload number
1830 * @return Number of skipped SDUs
1831 */
isoal_tx_unframed_get_next_payload_number(isoal_source_handle_t source_hdl,const struct isoal_sdu_tx * tx_sdu,uint64_t * payload_number)1832 uint16_t isoal_tx_unframed_get_next_payload_number(isoal_source_handle_t source_hdl,
1833 const struct isoal_sdu_tx *tx_sdu,
1834 uint64_t *payload_number)
1835 {
1836 struct isoal_source_session *session;
1837 struct isoal_pdu_production *pp;
1838 struct isoal_source *source;
1839 uint16_t sdus_skipped;
1840
1841 source = &isoal_global.source_state[source_hdl];
1842 session = &source->session;
1843 pp = &source->pdu_production;
1844
1845 /* Current payload number should have been updated to match the next
1846 * SDU.
1847 */
1848 *payload_number = pp->payload_number;
1849 sdus_skipped = 0;
1850
1851 if (tx_sdu->sdu_state == BT_ISO_START ||
1852 tx_sdu->sdu_state == BT_ISO_SINGLE) {
1853 /* Initialize to info provided in SDU */
1854 bool time_diff_valid;
1855 uint32_t time_diff;
1856
1857 /* Start of a new SDU */
1858 time_diff_valid = false;
1859 time_diff = 0;
1860
1861 /* Adjust payload number */
1862 if (IS_ENABLED(CONFIG_BT_CTLR_ISOAL_SN_STRICT) && pp->initialized) {
1863 /* Not the first SDU in this session, so reference
1864 * information should be valid. At this point, the
1865 * current payload number should be at the first PDU of
1866 * the incoming SDU, if the SDU is in sequence.
1867 * Adjustment is only required for the number of SDUs
1868 * skipped beyond the next expected SDU.
1869 */
1870 time_diff_valid = isoal_get_time_diff(session->last_input_time_stamp,
1871 tx_sdu->time_stamp,
1872 &time_diff);
1873
1874 /* Priority is given to the sequence number, however as
1875 * there is a possibility that the app may not manage
1876 * the sequence number correctly by incrementing every
1877 * SDU interval, the time stamp is checked if the
1878 * sequence number doesn't change.
1879 */
1880 if (tx_sdu->packet_sn > session->last_input_sn + 1) {
1881 /* Packet sequence number is not consecutive.
1882 * Find the number of skipped SDUs based on the
1883 * difference in the packet sequence number.
1884 */
1885 sdus_skipped = (tx_sdu->packet_sn - session->last_input_sn) - 1;
1886 *payload_number = pp->payload_number +
1887 (sdus_skipped * session->pdus_per_sdu);
1888
1889 } else if (tx_sdu->packet_sn == session->last_input_sn &&
1890 time_diff_valid && time_diff > session->sdu_interval) {
1891 /* SDU time stamps are not consecutive if more
1892 * than two SDU intervals apart. Find the number
1893 * of skipped SDUs based on the difference in
1894 * the time stamps.
1895 */
1896 /* Round at mid-point */
1897 sdus_skipped = ((time_diff + (session->sdu_interval / 2)) /
1898 session->sdu_interval) - 1;
1899 *payload_number = pp->payload_number +
1900 (sdus_skipped * session->pdus_per_sdu);
1901 } else {
1902 /* SDU is next in sequence */
1903 }
1904 } else {
1905 /* First SDU, so align with target event */
1906 /* Update the payload number if the target event is
1907 * later than the payload number indicates.
1908 */
1909 *payload_number = MAX(pp->payload_number,
1910 (tx_sdu->target_event * session->burst_number));
1911 }
1912 }
1913
1914 return sdus_skipped;
1915 }
1916
1917 /**
1918 * @brief Fragment received SDU and produce unframed PDUs
1919 * @details Destination source may have an already partially built PDU
1920 *
1921 * @param[in] source_hdl Destination source handle
1922 * @param[in] tx_sdu SDU with packet boundary information
1923 *
1924 * @return Status
1925 *
1926 * @note
1927 * PSN in SDUs for unframed TX:
1928 *
1929 * @par
1930 * Before the modification to use the PSN to decide the position of an SDU in a
1931 * stream of SDU, the target event was what was used in deciding the event for
1932 * each SDU. This meant that there would possibly have been skews on the
1933 * receiver for each SDU and there were problems with LL/CIS/PER/BV-39-C which
1934 * expects clustering within an event.
1935 *
1936 * @par
1937 * After the change, the PSN is used to decide the position of an SDU in the
1938 * stream anchored at the first PSN received. However for the first SDU
1939 * (assume that PSN=0), it will be the target event that decides which event
1940 * will be used for the fragmented payloads. Although the same interface from
1941 * the original is retained, the target event and group reference point only
1942 * impacts the event chosen for the first SDU and all subsequent SDUs will be
1943 * decided relative to the first.
1944 *
1945 * @par
1946 * The target event and related group reference point is still used to provide
1947 * the ISO-AL with a notion of time, for example when storing information
1948 * required for the TX Sync command. For example if for PSN 4, target event is
1949 * 8 but event 7 is chosen as the correct position for the SDU with PSN 4, the
1950 * group reference point stored is obtained by subtracting an ISO interval from
1951 * the group reference provided with target event 8 to get the BIG/CIG reference
1952 * for event 7. It is also expected that this value is the latest reference and
1953 * is drift compensated.
1954 *
1955 * @par
1956 * The PSN alone is not sufficient for this because host and controller have no
1957 * common reference time for when CIG / BIG event 0 starts. Therefore it is
1958 * expected that it is possible to receive PSN 0 in event 2 for example. If the
1959 * target event provided is event 3, then PSN 0 will be fragmented into payloads
1960 * for event 3 and that will serve as the anchor for the stream and subsequent
1961 * SDUs. If for example target event provided was event 2 instead, then it could
1962 * very well be that PSN 0 might not be transmitted as is was received midway
1963 * through event 2 and the payloads expired. If this happens then subsequent
1964 * SDUs might also all be late for their transmission slots as they are
1965 * positioned relative to PSN 0.
1966 */
isoal_tx_unframed_produce(isoal_source_handle_t source_hdl,const struct isoal_sdu_tx * tx_sdu)1967 static isoal_status_t isoal_tx_unframed_produce(isoal_source_handle_t source_hdl,
1968 const struct isoal_sdu_tx *tx_sdu)
1969 {
1970 struct isoal_source_session *session;
1971 isoal_sdu_len_t packet_available;
1972 struct isoal_pdu_production *pp;
1973 struct isoal_source *source;
1974 const uint8_t *sdu_payload;
1975 bool zero_length_sdu;
1976 isoal_status_t err;
1977 bool padding_pdu;
1978 uint8_t ll_id;
1979
1980 source = &isoal_global.source_state[source_hdl];
1981 session = &source->session;
1982 pp = &source->pdu_production;
1983 padding_pdu = false;
1984 err = ISOAL_STATUS_OK;
1985
1986 packet_available = tx_sdu->size;
1987 sdu_payload = tx_sdu->dbuf;
1988 LL_ASSERT(sdu_payload);
1989
1990 zero_length_sdu = (packet_available == 0 &&
1991 tx_sdu->sdu_state == BT_ISO_SINGLE);
1992
1993 if (tx_sdu->sdu_state == BT_ISO_START ||
1994 tx_sdu->sdu_state == BT_ISO_SINGLE) {
1995 uint32_t actual_grp_ref_point;
1996 uint64_t next_payload_number;
1997 uint16_t sdus_skipped;
1998 uint64_t actual_event;
1999 bool time_diff_valid;
2000 uint32_t time_diff;
2001
2002 /* Start of a new SDU */
2003 actual_grp_ref_point = tx_sdu->grp_ref_point;
2004 sdus_skipped = 0;
2005 time_diff_valid = false;
2006 time_diff = 0;
2007 /* Adjust payload number */
2008 time_diff_valid = isoal_get_time_diff(session->last_input_time_stamp,
2009 tx_sdu->time_stamp,
2010 &time_diff);
2011
2012 sdus_skipped = isoal_tx_unframed_get_next_payload_number(source_hdl, tx_sdu,
2013 &next_payload_number);
2014 pp->payload_number = next_payload_number;
2015
2016 /* Update sequence number for received SDU
2017 *
2018 * BT Core V5.3 : Vol 6 Low Energy Controller : Part G IS0-AL:
2019 * 2 ISOAL Features :
2020 * SDUs received by the ISOAL from the upper layer shall be
2021 * given a sequence number which is initialized to 0 when the
2022 * CIS or BIS is created.
2023 *
2024 * NOTE: The upper layer may synchronize its sequence number
2025 * with the sequence number in the ISOAL once the Datapath is
2026 * configured and the link is established.
2027 */
2028 session->sn += sdus_skipped + 1;
2029
2030 /* Get actual event for this payload number */
2031 actual_event = pp->payload_number / session->burst_number;
2032
2033 /* Get group reference point for this PDU based on the actual
2034 * event being set. This might introduce some errors as the
2035 * group reference point for future events could drift. However
2036 * as the time offset calculation requires an absolute value,
2037 * this seems to be the best candidate.
2038 */
2039 if (actual_event != tx_sdu->target_event) {
2040 actual_grp_ref_point =
2041 isoal_get_wrapped_time_us(tx_sdu->grp_ref_point,
2042 (actual_event - tx_sdu->target_event) *
2043 session->iso_interval * ISO_INT_UNIT_US);
2044 }
2045
2046 /* Store timing info for TX Sync command */
2047 session->tx_time_stamp = actual_grp_ref_point;
2048 /* BT Core V5.3 : Vol 4 HCI : Part E HCI Functional Spec:
2049 * 7.8.96 LE Read ISO TX Sync Command:
2050 * When the Connection_Handle identifies a CIS or BIS that is
2051 * transmitting unframed PDUs the value of Time_Offset returned
2052 * shall be zero
2053 * Relies on initialization value being 0.
2054 */
2055
2056 /* Reset PDU fragmentation count for this SDU */
2057 pp->pdu_cnt = 0;
2058
2059 /* The start of an unframed SDU will always be in a new PDU.
2060 * There cannot be any other fragments packed.
2061 */
2062 pp->sdu_fragments = 0;
2063
2064 /* Update input packet number and time stamp */
2065 session->last_input_sn = tx_sdu->packet_sn;
2066
2067 if (!time_diff_valid || time_diff < session->sdu_interval) {
2068 /* If the time-stamp is invalid or the difference is
2069 * less than an SDU interval, then set the reference
2070 * time stamp to what should have been received. This is
2071 * done to avoid incorrectly detecting a gap in time
2072 * stamp inputs should there be a burst of SDUs
2073 * clustered together.
2074 */
2075 session->last_input_time_stamp = isoal_get_wrapped_time_us(
2076 session->last_input_time_stamp,
2077 session->sdu_interval);
2078 } else {
2079 session->last_input_time_stamp = tx_sdu->time_stamp;
2080 }
2081 }
2082
2083 /* PDUs should be created until the SDU fragment has been fragmented or
2084 * if this is the last fragment of the SDU, until the required padding
2085 * PDU(s) are sent.
2086 */
2087 while ((err == ISOAL_STATUS_OK) &&
2088 ((packet_available > 0) || padding_pdu || zero_length_sdu)) {
2089 const isoal_status_t err_alloc = isoal_tx_allocate_pdu(source, tx_sdu);
2090 struct isoal_pdu_produced *pdu = &pp->pdu;
2091 err |= err_alloc;
2092
2093 /*
2094 * For this PDU we can only consume of packet, bounded by:
2095 * - What can fit in the destination PDU.
2096 * - What remains of the packet.
2097 */
2098 const size_t consume_len = MIN(
2099 packet_available,
2100 pp->pdu_available
2101 );
2102
2103 /* End of the SDU fragment has been reached when the last of the
2104 * SDU is packed into a PDU.
2105 */
2106 bool end_of_sdu_frag = !padding_pdu &&
2107 ((consume_len > 0 && consume_len == packet_available) ||
2108 zero_length_sdu);
2109
2110 if (consume_len > 0) {
2111 err |= session->pdu_write(&pdu->contents,
2112 pp->pdu_written,
2113 sdu_payload,
2114 consume_len);
2115 sdu_payload += consume_len;
2116 pp->pdu_written += consume_len;
2117 pp->pdu_available -= consume_len;
2118 packet_available -= consume_len;
2119 }
2120
2121 if (end_of_sdu_frag) {
2122 /* Each PDU will carry the number of completed SDU
2123 * fragments contained in that PDU.
2124 */
2125 pp->sdu_fragments++;
2126 }
2127
2128 /* End of the SDU is reached at the end of the last SDU fragment
2129 * or if this is a single fragment SDU
2130 */
2131 bool end_of_sdu = (packet_available == 0) &&
2132 ((tx_sdu->sdu_state == BT_ISO_SINGLE) ||
2133 (tx_sdu->sdu_state == BT_ISO_END));
2134
2135 /* Decide PDU type
2136 *
2137 * BT Core V5.3 : Vol 6 Low Energy Controller : Part G IS0-AL:
2138 * 2.1 Unframed PDU :
2139 * LLID 0b00 PDU_BIS_LLID_COMPLETE_END:
2140 * (1) When the payload of the ISO Data PDU contains the end
2141 * fragment of an SDU.
2142 * (2) When the payload of the ISO Data PDU contains a complete
2143 * SDU.
2144 * (3) When an SDU contains zero length data, the corresponding
2145 * PDU shall be of zero length and the LLID field shall be
2146 * set to 0b00.
2147 *
2148 * LLID 0b01 PDU_BIS_LLID_COMPLETE_END:
2149 * (1) When the payload of the ISO Data PDU contains a start or
2150 * a continuation fragment of an SDU.
2151 * (2) When the ISO Data PDU is used as padding.
2152 */
2153 ll_id = PDU_BIS_LLID_COMPLETE_END;
2154 if (!end_of_sdu || padding_pdu) {
2155 ll_id = PDU_BIS_LLID_START_CONTINUE;
2156 }
2157
2158 const isoal_status_t err_emit = isoal_tx_try_emit_pdu(source, end_of_sdu, ll_id);
2159
2160 err |= err_emit;
2161
2162 /* Send padding PDU(s) if required
2163 *
2164 * BT Core V5.3 : Vol 6 Low Energy Controller : Part G IS0-AL:
2165 * 2.1 Unframed PDU :
2166 * Each SDU shall generate BN ÷ (ISO_Interval ÷ SDU_Interval)
2167 * fragments. If an SDU generates less than this number of
2168 * fragments, empty payloads shall be used to make up the
2169 * number.
2170 */
2171 padding_pdu = (end_of_sdu && (pp->pdu_cnt < session->pdus_per_sdu));
2172 zero_length_sdu = false;
2173 }
2174
2175 pp->initialized = 1U;
2176
2177 return err;
2178 }
2179
2180 /**
2181 * @brief Inserts a segmentation header at the current write point in the PDU
2182 * under production.
2183 * @param source source handle
2184 * @param sc start / continuation bit value to be written
2185 * @param cmplt complete bit value to be written
2186 * @param time_offset value of time offset to be written
2187 * @return status
2188 */
isoal_insert_seg_header_timeoffset(struct isoal_source * source,const bool sc,const bool cmplt,const uint32_t time_offset)2189 static isoal_status_t isoal_insert_seg_header_timeoffset(struct isoal_source *source,
2190 const bool sc,
2191 const bool cmplt,
2192 const uint32_t time_offset)
2193 {
2194 struct isoal_source_session *session;
2195 struct isoal_pdu_production *pp;
2196 struct isoal_pdu_produced *pdu;
2197 struct pdu_iso_sdu_sh seg_hdr;
2198 isoal_status_t err;
2199 uint8_t write_size;
2200
2201 session = &source->session;
2202 pp = &source->pdu_production;
2203 pdu = &pp->pdu;
2204 write_size = PDU_ISO_SEG_HDR_SIZE + (sc ? 0 : PDU_ISO_SEG_TIMEOFFSET_SIZE);
2205
2206 memset(&seg_hdr, 0, sizeof(seg_hdr));
2207
2208 /* Check if there is enough space left in the PDU. This should not fail
2209 * as the calling should also check before requesting insertion of a
2210 * new header.
2211 */
2212 if (pp->pdu_available < write_size) {
2213
2214 return ISOAL_STATUS_ERR_UNSPECIFIED;
2215 }
2216
2217 seg_hdr.sc = sc;
2218 seg_hdr.cmplt = cmplt;
2219 seg_hdr.len = sc ? 0 : PDU_ISO_SEG_TIMEOFFSET_SIZE;
2220
2221 if (!sc) {
2222 seg_hdr.timeoffset = time_offset;
2223 }
2224
2225 /* Store header */
2226 pp->seg_hdr_sc = seg_hdr.sc;
2227 pp->seg_hdr_length = seg_hdr.len;
2228
2229 /* Save location of last segmentation header so that it can be updated
2230 * as data is written.
2231 */
2232 pp->last_seg_hdr_loc = pp->pdu_written;
2233 /* Write to PDU */
2234 err = session->pdu_write(&pdu->contents,
2235 pp->pdu_written,
2236 (uint8_t *) &seg_hdr,
2237 write_size);
2238 pp->pdu_written += write_size;
2239 pp->pdu_available -= write_size;
2240
2241 ISOAL_LOG_DBGV("[%p] Seg header write size=%u sc=%u cmplt=%u TO=%u len=%u",
2242 source, write_size, sc, cmplt, time_offset, seg_hdr.len);
2243
2244 return err;
2245 }
2246
2247 /**
2248 * @brief Updates the cmplt flag and length in the last segmentation header written
2249 * @param source source handle
2250 * @param cmplt ew value for complete flag
2251 * param add_length length to add
2252 * @return status
2253 */
isoal_update_seg_header_cmplt_length(struct isoal_source * source,const bool cmplt,const uint8_t add_length)2254 static isoal_status_t isoal_update_seg_header_cmplt_length(struct isoal_source *source,
2255 const bool cmplt,
2256 const uint8_t add_length)
2257 {
2258 struct isoal_source_session *session;
2259 struct isoal_pdu_production *pp;
2260 struct isoal_pdu_produced *pdu;
2261 struct pdu_iso_sdu_sh seg_hdr;
2262
2263 session = &source->session;
2264 pp = &source->pdu_production;
2265 pdu = &pp->pdu;
2266 memset(&seg_hdr, 0, sizeof(seg_hdr));
2267
2268 seg_hdr.sc = pp->seg_hdr_sc;
2269
2270 /* Update the complete flag and length */
2271 seg_hdr.cmplt = cmplt;
2272 pp->seg_hdr_length += add_length;
2273 seg_hdr.len = pp->seg_hdr_length;
2274
2275
2276 /* Re-write the segmentation header at the same location */
2277 return session->pdu_write(&pdu->contents,
2278 pp->last_seg_hdr_loc,
2279 (uint8_t *) &seg_hdr,
2280 PDU_ISO_SEG_HDR_SIZE);
2281
2282 ISOAL_LOG_DBGV("[%p] Seg header write size=%u sc=%u cmplt=%u len=%u",
2283 source, PDU_ISO_SEG_HDR_SIZE, seg_hdr.sc, cmplt, seg_hdr.len);
2284 }
2285
2286 /**
2287 * Find the earliest feasible event for transmission capacity is not wasted and
2288 * return information based on that event.
2289 *
2290 * @param[in] *source_ctx Destination source context
2291 * @param[in] tx_sdu SDU with meta data information
2292 * @param[out] payload_number Updated payload number for the selected event
2293 * @param[out] grp_ref_point Group reference point for the selected event
2294 * @param[out] time_offset Segmentation Time offset to selected event
2295 * @return The number SDUs skipped from the last
2296 */
isoal_tx_framed_find_correct_tx_event(const struct isoal_source * source_ctx,const struct isoal_sdu_tx * tx_sdu,uint64_t * payload_number,uint32_t * grp_ref_point,uint32_t * time_offset)2297 static uint16_t isoal_tx_framed_find_correct_tx_event(const struct isoal_source *source_ctx,
2298 const struct isoal_sdu_tx *tx_sdu,
2299 uint64_t *payload_number,
2300 uint32_t *grp_ref_point,
2301 uint32_t *time_offset)
2302 {
2303 const struct isoal_source_session *session;
2304 const struct isoal_pdu_production *pp;
2305 uint32_t actual_grp_ref_point;
2306 uint64_t next_payload_number;
2307 uint16_t sdus_skipped;
2308 uint64_t actual_event;
2309 bool time_diff_valid;
2310 uint32_t time_diff;
2311 uint32_t time_stamp_selected;
2312
2313 session = &source_ctx->session;
2314 pp = &source_ctx->pdu_production;
2315
2316 sdus_skipped = 0U;
2317 time_diff = 0U;
2318
2319 /* Continue with the current payload unless there is need to change */
2320 next_payload_number = pp->payload_number;
2321 actual_event = pp->payload_number / session->burst_number;
2322
2323 ISOAL_LOG_DBGV("[%p] Start PL=%llu Evt=%lu.", source_ctx, next_payload_number,
2324 actual_event);
2325
2326 /* Get the drift updated group reference point for this event based on
2327 * the actual event being set. This might introduce some errors as the
2328 * group reference point for future events could drift. However as the
2329 * time offset calculation requires an absolute value, this seems to be
2330 * the best candidate.
2331 */
2332 if (actual_event != tx_sdu->target_event) {
2333 actual_grp_ref_point = isoal_get_wrapped_time_us(tx_sdu->grp_ref_point,
2334 ((actual_event - tx_sdu->target_event) * session->iso_interval *
2335 ISO_INT_UNIT_US));
2336 } else {
2337 actual_grp_ref_point = tx_sdu->grp_ref_point;
2338 }
2339
2340 ISOAL_LOG_DBGV("[%p] Current PL=%llu Evt=%llu Ref=%lu",
2341 source_ctx, next_payload_number, actual_event, actual_grp_ref_point);
2342
2343 if (tx_sdu->sdu_state == BT_ISO_START ||
2344 tx_sdu->sdu_state == BT_ISO_SINGLE) {
2345 /* Start of a new SDU */
2346
2347 const bool time_stamp_is_valid = isoal_is_time_stamp_valid(source_ctx,
2348 tx_sdu->cntr_time_stamp,
2349 tx_sdu->time_stamp);
2350
2351 /* Adjust payload number */
2352 if (pp->initialized) {
2353 /* Not the first SDU in this session, so reference
2354 * information should be valid. .
2355 */
2356
2357 time_diff_valid = isoal_get_time_diff(session->last_input_time_stamp,
2358 tx_sdu->time_stamp,
2359 &time_diff);
2360
2361 /* Priority is given to the sequence number */
2362 if (tx_sdu->packet_sn > session->last_input_sn + 1) {
2363 ISOAL_LOG_DBGV("[%p] Using packet_sn for skipped SDUs", source_ctx);
2364 sdus_skipped = (tx_sdu->packet_sn - session->last_input_sn) - 1;
2365
2366 } else if (tx_sdu->packet_sn == session->last_input_sn &&
2367 time_diff_valid && time_diff > session->sdu_interval) {
2368 ISOAL_LOG_DBGV("[%p] Using time_stamp for skipped SDUs",
2369 source_ctx);
2370 /* Round at mid-point */
2371 sdus_skipped = ((time_diff + (session->sdu_interval / 2)) /
2372 session->sdu_interval) - 1;
2373 } else {
2374 /* SDU is next in sequence */
2375 }
2376
2377 if (time_stamp_is_valid) {
2378 /* Use provided time stamp for time offset
2379 * calculation
2380 */
2381 time_stamp_selected = tx_sdu->time_stamp;
2382 ISOAL_LOG_DBGV("[%p] Selecting Time Stamp (%lu) from SDU",
2383 source_ctx, time_stamp_selected);
2384 } else if (time_diff_valid) {
2385 /* Project a time stamp based on the last time
2386 * stamp and the difference in input time stamps
2387 */
2388 time_stamp_selected = isoal_get_wrapped_time_us(
2389 session->tx_time_stamp,
2390 time_diff - session->tx_time_offset);
2391 ISOAL_LOG_DBGV("[%p] Projecting Time Stamp (%lu) from SDU delta",
2392 source_ctx, time_stamp_selected);
2393 } else {
2394 /* Project a time stamp based on the last time
2395 * stamp and the number of skipped SDUs
2396 */
2397 time_stamp_selected = isoal_get_wrapped_time_us(
2398 session->tx_time_stamp,
2399 ((sdus_skipped + 1) * session->sdu_interval)
2400 - session->tx_time_offset);
2401 ISOAL_LOG_DBGV("[%p] Projecting Time Stamp (%lu) from skipped SDUs",
2402 source_ctx, time_stamp_selected);
2403 }
2404
2405 } else {
2406 /* First SDU, align with target event */
2407 if (actual_event < tx_sdu->target_event) {
2408 actual_event = tx_sdu->target_event;
2409 actual_grp_ref_point = tx_sdu->grp_ref_point;
2410 }
2411
2412 ISOAL_LOG_DBGV("[%p] Use target_event", source_ctx);
2413
2414 if (time_stamp_is_valid) {
2415 /* Time stamp is within valid range -
2416 * use provided time stamp
2417 */
2418 time_stamp_selected = tx_sdu->time_stamp;
2419 ISOAL_LOG_DBGV("[%p] Selecting Time Stamp (%lu) from SDU",
2420 source_ctx, time_stamp_selected);
2421 } else {
2422 /* Time stamp is out of range -
2423 * use controller's capture time
2424 */
2425 time_stamp_selected = tx_sdu->cntr_time_stamp;
2426 ISOAL_LOG_DBGV("[%p] Selecting Time Stamp (%lu) from controller",
2427 source_ctx, time_stamp_selected);
2428 }
2429 }
2430
2431 /* Selecting the event for transmission is done solely based on
2432 * the time stamp and the ability to calculate a valid time
2433 * offset.
2434 */
2435
2436 /* Check if time stamp on packet is later than the group
2437 * reference point and find next feasible event for transmission.
2438 *
2439 * BT Core V5.3 : Vol 6 Low Energy Controller : Part G IS0-AL:
2440 * 3.1 Time_Offset in framed PDUs :
2441 * The Time_Offset shall be a positive value.
2442 */
2443 while (!isoal_get_time_diff(time_stamp_selected, actual_grp_ref_point, &time_diff)
2444 || time_diff == 0) {
2445 /* Advance target to next event */
2446 actual_event++;
2447 actual_grp_ref_point = isoal_get_wrapped_time_us(actual_grp_ref_point,
2448 session->iso_interval * ISO_INT_UNIT_US);
2449 }
2450
2451 ISOAL_LOG_DBGV("[%p] Chosen PL=%llu Evt=%llu Ref=%lu",
2452 source_ctx, (actual_event * session->burst_number), actual_event,
2453 actual_grp_ref_point);
2454
2455 /* If the event selected is the last event segmented for, then
2456 * it is possible that some payloads have already been
2457 * released for this event. Segmentation should continue from
2458 * that payload.
2459 */
2460 next_payload_number = MAX(pp->payload_number,
2461 (actual_event * session->burst_number));
2462
2463 ISOAL_LOG_DBGV("[%p] Final Evt=%llu (PL=%llu) Ref.=%lu Next PL=%llu",
2464 source, actual_event, (actual_event * session->burst_number),
2465 actual_grp_ref_point, next_payload_number);
2466
2467 /* Calculate the time offset */
2468 time_diff_valid = isoal_get_time_diff(time_stamp_selected,
2469 actual_grp_ref_point, &time_diff);
2470
2471 LL_ASSERT(time_diff_valid);
2472 LL_ASSERT(time_diff > 0);
2473 /* Time difference must be less than the maximum possible
2474 * time-offset of 24-bits.
2475 */
2476 LL_ASSERT(time_diff <= 0x00FFFFFF);
2477 }
2478
2479 *payload_number = next_payload_number;
2480 *grp_ref_point = actual_grp_ref_point;
2481 *time_offset = time_diff;
2482
2483 return sdus_skipped;
2484 }
2485
2486 /**
2487 * @brief Fragment received SDU and produce framed PDUs
2488 * @details Destination source may have an already partially built PDU
2489 *
2490 * @param source[in,out] Destination source with bookkeeping state
2491 * @param tx_sdu[in] SDU with packet boundary information
2492 *
2493 * @return Status
2494 */
isoal_tx_framed_produce(isoal_source_handle_t source_hdl,const struct isoal_sdu_tx * tx_sdu)2495 static isoal_status_t isoal_tx_framed_produce(isoal_source_handle_t source_hdl,
2496 const struct isoal_sdu_tx *tx_sdu)
2497 {
2498 struct isoal_source_session *session;
2499 struct isoal_pdu_production *pp;
2500 isoal_sdu_len_t packet_available;
2501 struct isoal_source *source;
2502 const uint8_t *sdu_payload;
2503 uint32_t time_offset;
2504 bool zero_length_sdu;
2505 isoal_status_t err;
2506 bool padding_pdu;
2507
2508 source = &isoal_global.source_state[source_hdl];
2509 session = &source->session;
2510 pp = &source->pdu_production;
2511 padding_pdu = false;
2512 err = ISOAL_STATUS_OK;
2513 time_offset = 0;
2514
2515 packet_available = tx_sdu->size;
2516 sdu_payload = tx_sdu->dbuf;
2517 LL_ASSERT(sdu_payload);
2518
2519 zero_length_sdu = (packet_available == 0 &&
2520 tx_sdu->sdu_state == BT_ISO_SINGLE);
2521
2522 ISOAL_LOG_DBGV("[%p] SDU %u len=%u TS=%lu Ref=%lu Evt=%llu Frag=%u",
2523 source, tx_sdu->packet_sn, tx_sdu->iso_sdu_length, tx_sdu->time_stamp,
2524 tx_sdu->grp_ref_point, tx_sdu->target_event, tx_sdu->sdu_state);
2525
2526 if (tx_sdu->sdu_state == BT_ISO_START ||
2527 tx_sdu->sdu_state == BT_ISO_SINGLE) {
2528 uint32_t actual_grp_ref_point;
2529 uint64_t next_payload_number;
2530 uint16_t sdus_skipped;
2531 bool time_diff_valid;
2532 uint32_t time_diff;
2533
2534 /* Start of a new SDU */
2535 time_diff_valid = isoal_get_time_diff(session->last_input_time_stamp,
2536 tx_sdu->time_stamp,
2537 &time_diff);
2538
2539 /* Find the best transmission event */
2540 sdus_skipped = isoal_tx_framed_find_correct_tx_event(source, tx_sdu,
2541 &next_payload_number,
2542 &actual_grp_ref_point,
2543 &time_offset);
2544
2545 ISOAL_LOG_DBGV("[%p] %u SDUs skipped.", source, sdus_skipped);
2546 ISOAL_LOG_DBGV("[%p] Starting SDU %u PL=(%llu->%llu) Grp Ref=%lu TO=%lu",
2547 source, tx_sdu->packet_sn, pp->payload_number, next_payload_number,
2548 actual_grp_ref_point, time_offset);
2549
2550
2551 if (next_payload_number > pp->payload_number) {
2552 /* Moving to a new payload */
2553 if (pp->pdu_allocated) {
2554 /* Current PDU in production should be released before
2555 * moving to new event.
2556 */
2557 ISOAL_LOG_DBGV("[%p] Pending PDU released.\n");
2558 err |= isoal_tx_try_emit_pdu(source, true, PDU_BIS_LLID_FRAMED);
2559 }
2560
2561 while (err == ISOAL_STATUS_OK && next_payload_number > pp->payload_number &&
2562 (pp->payload_number % session->burst_number)) {
2563 /* Release padding PDUs for this event */
2564 err |= isoal_tx_allocate_pdu(source, tx_sdu);
2565 err |= isoal_tx_try_emit_pdu(source, true, PDU_BIS_LLID_FRAMED);
2566 }
2567 }
2568
2569 /* Reset PDU production state */
2570 pp->pdu_state = BT_ISO_START;
2571
2572 /* Update to new payload number */
2573 pp->payload_number = next_payload_number;
2574
2575 /* Update sequence number for received SDU
2576 *
2577 * BT Core V5.3 : Vol 6 Low Energy Controller : Part G IS0-AL:
2578 * 2 ISOAL Features :
2579 * SDUs received by the ISOAL from the upper layer shall be
2580 * given a sequence number which is initialized to 0 when the
2581 * CIS or BIS is created.
2582 *
2583 * NOTE: The upper layer may synchronize its sequence number
2584 * with the sequence number in the ISOAL once the Datapath is
2585 * configured and the link is established.
2586 */
2587
2588 session->sn += sdus_skipped + 1;
2589
2590 /* Store timing info for TX Sync command */
2591 session->tx_time_stamp = actual_grp_ref_point;
2592 session->tx_time_offset = time_offset;
2593
2594 /* Reset PDU fragmentation count for this SDU */
2595 pp->pdu_cnt = 0;
2596
2597 /* Update input packet number and time stamp */
2598 session->last_input_sn = tx_sdu->packet_sn;
2599
2600 if (pp->initialized && tx_sdu->time_stamp == tx_sdu->cntr_time_stamp &&
2601 (!time_diff_valid || time_diff < session->sdu_interval)) {
2602 /* If the time-stamp is invalid or the difference is
2603 * less than an SDU interval, then set the reference
2604 * time stamp to what should have been received. This is
2605 * done to avoid incorrectly detecting a gap in time
2606 * stamp inputs should there be a burst of SDUs
2607 * clustered together.
2608 */
2609 session->last_input_time_stamp = isoal_get_wrapped_time_us(
2610 session->last_input_time_stamp,
2611 session->sdu_interval);
2612 } else {
2613 session->last_input_time_stamp = tx_sdu->time_stamp;
2614 }
2615 }
2616
2617 /* PDUs should be created until the SDU fragment has been fragmented or if
2618 * this is the last fragment of the SDU, until the required padding PDU(s)
2619 * are sent.
2620 */
2621 while ((err == ISOAL_STATUS_OK) &&
2622 ((packet_available > 0) || padding_pdu || zero_length_sdu)) {
2623 const isoal_status_t err_alloc = isoal_tx_allocate_pdu(source, tx_sdu);
2624 struct isoal_pdu_produced *pdu = &pp->pdu;
2625
2626 err |= err_alloc;
2627
2628 ISOAL_LOG_DBGV("[%p] State %s", source, STATE_TO_STR(pp->pdu_state));
2629 if (pp->pdu_state == BT_ISO_START) {
2630 /* Start of a new SDU. Segmentation header and time-offset
2631 * should be inserted.
2632 */
2633 err |= isoal_insert_seg_header_timeoffset(source,
2634 false, false,
2635 sys_cpu_to_le24(time_offset));
2636 pp->pdu_state = BT_ISO_CONT;
2637 } else if (!padding_pdu && pp->pdu_state == BT_ISO_CONT && pp->pdu_written == 0) {
2638 /* Continuing an SDU in a new PDU. Segmentation header
2639 * alone should be inserted.
2640 */
2641 err |= isoal_insert_seg_header_timeoffset(source,
2642 true, false,
2643 sys_cpu_to_le24(0));
2644 }
2645
2646 /*
2647 * For this PDU we can only consume of packet, bounded by:
2648 * - What can fit in the destination PDU.
2649 * - What remains of the packet.
2650 */
2651 const size_t consume_len = MIN(
2652 packet_available,
2653 pp->pdu_available
2654 );
2655
2656 /* End of the SDU fragment has been reached when the last of the
2657 * SDU is packed into a PDU.
2658 */
2659 bool end_of_sdu_frag = !padding_pdu &&
2660 ((consume_len > 0 && consume_len == packet_available) ||
2661 zero_length_sdu);
2662
2663 if (consume_len > 0) {
2664 err |= session->pdu_write(&pdu->contents,
2665 pp->pdu_written,
2666 sdu_payload,
2667 consume_len);
2668 sdu_payload += consume_len;
2669 pp->pdu_written += consume_len;
2670 pp->pdu_available -= consume_len;
2671 packet_available -= consume_len;
2672 }
2673
2674 if (end_of_sdu_frag) {
2675 /* Each PDU will carry the number of completed SDU
2676 * fragments contained in that PDU.
2677 */
2678 pp->sdu_fragments++;
2679 }
2680
2681 /* End of the SDU is reached at the end of the last SDU fragment
2682 * or if this is a single fragment SDU
2683 */
2684 bool end_of_sdu = (packet_available == 0) &&
2685 ((tx_sdu->sdu_state == BT_ISO_SINGLE) ||
2686 (tx_sdu->sdu_state == BT_ISO_END));
2687 /* Update complete flag in last segmentation header */
2688 err |= isoal_update_seg_header_cmplt_length(source, end_of_sdu, consume_len);
2689
2690 /* If there isn't sufficient usable space then release the
2691 * PDU when the end of the SDU is reached, instead of waiting
2692 * for the next SDU.
2693 */
2694 bool release_pdu = end_of_sdu && (pp->pdu_available <= ISOAL_TX_SEGMENT_MIN_SIZE);
2695 const isoal_status_t err_emit = isoal_tx_try_emit_pdu(source, release_pdu,
2696 PDU_BIS_LLID_FRAMED);
2697
2698 err |= err_emit;
2699
2700 /* BT Core V5.3 : Vol 6 Low Energy Controller : Part G IS0-AL:
2701 * 2 ISOAL Features :
2702 * Padding is required when the data does not add up to the
2703 * configured number of PDUs that are specified in the BN
2704 * parameter per CIS or BIS event.
2705 *
2706 * When padding PDUs as opposed to null PDUs are required for
2707 * framed production is not clear. Padding PDUs will be released
2708 * on the next event prepare trigger.
2709 */
2710 padding_pdu = false;
2711 zero_length_sdu = false;
2712 }
2713
2714 pp->initialized = 1U;
2715
2716 return err;
2717 }
2718
2719 /**
2720 * @brief Handle preparation of the given source before commencing TX on the
2721 * specified event (only for framed sources)
2722 * @param source_hdl Handle of source to prepare
2723 * @param event_count Event number source should be prepared for
2724 * @return Status of operation
2725 */
isoal_tx_framed_event_prepare_handle(isoal_source_handle_t source_hdl,uint64_t event_count)2726 static isoal_status_t isoal_tx_framed_event_prepare_handle(isoal_source_handle_t source_hdl,
2727 uint64_t event_count)
2728 {
2729 struct isoal_source_session *session;
2730 struct isoal_pdu_production *pp;
2731 uint64_t first_event_payload;
2732 struct isoal_source *source;
2733 uint64_t last_event_payload;
2734 isoal_status_t err_alloc;
2735 bool release_padding;
2736 isoal_status_t err;
2737
2738 err = ISOAL_STATUS_OK;
2739 err_alloc = ISOAL_STATUS_OK;
2740 release_padding = false;
2741
2742 source = &isoal_global.source_state[source_hdl];
2743 session = &source->session;
2744 pp = &source->pdu_production;
2745 first_event_payload = (session->burst_number * event_count);
2746 last_event_payload = (session->burst_number * (event_count + 1ULL)) - 1ULL;
2747
2748 if (pp->pdu_allocated && pp->payload_number <= last_event_payload) {
2749 /* Pending PDU that should be released for framed TX */
2750 ISOAL_LOG_DBGV("[%p] Prepare PDU released.", source);
2751 err = isoal_tx_try_emit_pdu(source, true, PDU_BIS_LLID_FRAMED);
2752 }
2753
2754 if (pp->mode != ISOAL_PRODUCTION_MODE_DISABLED) {
2755 /* BT Core V5.3 : Vol 6 Low Energy Controller :
2756 * Part G IS0-AL:
2757 *
2758 * 2 ISOAL Features :
2759 * Padding is required when the data does not add up to the
2760 * configured number of PDUs that are specified in the BN
2761 * parameter per CIS or BIS event.
2762 *
2763 * There is some lack of clarity in the specifications as to why
2764 * padding PDUs should be used as opposed to null PDUs. However
2765 * if a payload is not available, the LL must default to waiting
2766 * for the flush timeout before it can proceed to the next
2767 * payload.
2768 *
2769 * This means a loss of retransmission capacity for future
2770 * payloads that could exist. Sending padding PDUs will prevent
2771 * this loss while not resulting in additional SDUs on the
2772 * receiver. However it does incur the allocation and handling
2773 * overhead on the transmitter.
2774 *
2775 * As an interpretation of the specification, padding PDUs will
2776 * only be released if an SDU has been received in the current
2777 * event.
2778 */
2779 if (pp->payload_number > first_event_payload) {
2780 release_padding = true;
2781 }
2782 }
2783
2784 if (release_padding) {
2785 while (!err && !err_alloc && (pp->payload_number < last_event_payload + 1ULL)) {
2786 ISOAL_LOG_DBGV("[%p] Prepare padding PDU release.", source);
2787 err_alloc = isoal_tx_allocate_pdu(source, NULL);
2788
2789 err = isoal_tx_try_emit_pdu(source, true, PDU_BIS_LLID_FRAMED);
2790 }
2791 }
2792
2793 /* Not possible to recover if allocation or emit fails here*/
2794 LL_ASSERT(!(err || err_alloc));
2795
2796 if (pp->payload_number < last_event_payload + 1ULL) {
2797 pp->payload_number = last_event_payload + 1ULL;
2798 ISOAL_LOG_DBGV("[%p] Prepare PL updated to %lu.", source, pp->payload_number);
2799 }
2800
2801 return err;
2802 }
2803
2804 /**
2805 * @brief Deep copy a SDU, fragment into PDU(s)
2806 * @details Fragmentation will occur individually for every enabled source
2807 *
2808 * @param source_hdl[in] Handle of destination source
2809 * @param tx_sdu[in] SDU along with packet boundary state
2810 * @return Status
2811 */
isoal_tx_sdu_fragment(isoal_source_handle_t source_hdl,struct isoal_sdu_tx * tx_sdu)2812 isoal_status_t isoal_tx_sdu_fragment(isoal_source_handle_t source_hdl,
2813 struct isoal_sdu_tx *tx_sdu)
2814 {
2815 struct isoal_source_session *session;
2816 struct isoal_source *source;
2817 isoal_status_t err;
2818
2819 source = &isoal_global.source_state[source_hdl];
2820 session = &source->session;
2821 err = ISOAL_STATUS_ERR_PDU_ALLOC;
2822
2823 /* Set source context active to mutually exclude ISO Event prepare
2824 * kick.
2825 */
2826 source->context_active = 1U;
2827
2828 if (source->pdu_production.mode != ISOAL_PRODUCTION_MODE_DISABLED) {
2829 /* BT Core V5.3 : Vol 6 Low Energy Controller : Part G IS0-AL:
2830 * 2 ISOAL Features :
2831 * (1) Unframed PDUs shall only be used when the ISO_Interval
2832 * is equal to or an integer multiple of the SDU_Interval
2833 * and a constant time offset alignment is maintained
2834 * between the SDU generation and the timing in the
2835 * isochronous transport.
2836 * (2) When the Host requests the use of framed PDUs, the
2837 * Controller shall use framed PDUs.
2838 */
2839 if (source->session.framed) {
2840 err = isoal_tx_framed_produce(source_hdl, tx_sdu);
2841 } else {
2842 err = isoal_tx_unframed_produce(source_hdl, tx_sdu);
2843 }
2844 }
2845
2846 source->context_active = 0U;
2847
2848 if (source->timeout_trigger) {
2849 source->timeout_trigger = 0U;
2850 if (session->framed) {
2851 ISOAL_LOG_DBGV("[%p] Prepare cb flag trigger", source);
2852 isoal_tx_framed_event_prepare_handle(source_hdl,
2853 source->timeout_event_count);
2854 }
2855 }
2856
2857 return err;
2858 }
2859
isoal_tx_pdu_release(isoal_source_handle_t source_hdl,struct node_tx_iso * node_tx)2860 void isoal_tx_pdu_release(isoal_source_handle_t source_hdl,
2861 struct node_tx_iso *node_tx)
2862 {
2863 struct isoal_source *source = &isoal_global.source_state[source_hdl];
2864
2865 if (source && source->session.pdu_release) {
2866 source->session.pdu_release(node_tx, source->session.handle,
2867 ISOAL_STATUS_OK);
2868 }
2869 }
2870
2871 /**
2872 * @brief Get information required for HCI_LE_Read_ISO_TX_Sync
2873 * @param source_hdl Source handle linked to handle provided in HCI message
2874 * @param seq Packet Sequence number of last SDU
2875 * @param timestamp CIG / BIG reference point of last SDU
2876 * @param offset Time-offset (Framed) / 0 (Unframed) of last SDU
2877 * @return Operation status
2878 */
isoal_tx_get_sync_info(isoal_source_handle_t source_hdl,uint16_t * seq,uint32_t * timestamp,uint32_t * offset)2879 isoal_status_t isoal_tx_get_sync_info(isoal_source_handle_t source_hdl,
2880 uint16_t *seq,
2881 uint32_t *timestamp,
2882 uint32_t *offset)
2883 {
2884 if (isoal_check_source_hdl_valid(source_hdl) == ISOAL_STATUS_OK) {
2885 struct isoal_source_session *session;
2886
2887 session = &isoal_global.source_state[source_hdl].session;
2888
2889 /* BT Core V5.3 : Vol 4 HCI : Part E HCI Functional Spec:
2890 * 7.8.96 LE Read ISO TX Sync Command:
2891 * If the Host issues this command before an SDU had been transmitted by
2892 * the Controller, then Controller shall return the error code Command
2893 * Disallowed.
2894 */
2895 if (session->sn > 0) {
2896 *seq = session->sn;
2897 *timestamp = session->tx_time_stamp;
2898 *offset = session->tx_time_offset;
2899 return ISOAL_STATUS_OK;
2900 }
2901 }
2902
2903 return ISOAL_STATUS_ERR_UNSPECIFIED;
2904 }
2905
2906 /**
2907 * @brief Incoming prepare request before commencing TX for the specified
2908 * event
2909 * @param source_hdl Handle of source to prepare
2910 * @param event_count Event number source should be prepared for
2911 * @return Status of operation
2912 */
isoal_tx_event_prepare(isoal_source_handle_t source_hdl,uint64_t event_count)2913 void isoal_tx_event_prepare(isoal_source_handle_t source_hdl,
2914 uint64_t event_count)
2915 {
2916 struct isoal_source_session *session;
2917 struct isoal_source *source;
2918
2919 source = &isoal_global.source_state[source_hdl];
2920 session = &source->session;
2921
2922 /* Store prepare timeout information and check if fragmentation context
2923 * is active.
2924 */
2925 source->timeout_event_count = event_count;
2926 source->timeout_trigger = 1U;
2927 if (source->context_active) {
2928 return;
2929 }
2930 source->timeout_trigger = 0U;
2931
2932 if (session->framed) {
2933 ISOAL_LOG_DBGV("[%p] Prepare call back", source);
2934 isoal_tx_framed_event_prepare_handle(source_hdl, event_count);
2935 }
2936 }
2937
2938 #endif /* CONFIG_BT_CTLR_ADV_ISO || CONFIG_BT_CTLR_CONN_ISO */
2939