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