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