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