Skip to content

Commit 1998a42

Browse files
committed
audio: pipeline: change locking strategy for user LL builds
Modify the locking approach for CONFIG_SOF_USERSPACE_LL builds. Kernel LL implementation heavily relies on ability to disable interrupts when IPC handler is modifying the graph. This ensures a new LL tick and execution of a new graph cycle does not start before the graph modifications done by IPC handler are complete. In user-space, this approach is not available as user-space thread cannot disable interrupts. In commit 1e59ce2 ("pipeline: protect component connections with a mutex"), a sys_mutex based locking was implemented to protect the component list and modifications to it. This approach does not scale in the end as this would require taking the mutex for each component of each pipeline, and take the locks on every LL cycle tick. This results in significant system call overhead. Additionally Zephyr sys_mutex does not work correctly if the lock object is put into dynamically allocated user memory. In this commit, locking the LL graph is moved to a higher level. A single lock is used to protect the whole LL graph, and the lock is taken at start of LL tick. The same lock is taken by the IPC handlers when modifications to the graph are taken. The mutex interface supports priority inversion, so this usage is safe if LL timer tick happens while IPC processing is still in progress. The patch only changes behaviour for userspace LL SOF builds. If LL scheduling is kept in kernel, locking is done as before. Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
1 parent e3daabe commit 1998a42

5 files changed

Lines changed: 95 additions & 37 deletions

File tree

src/audio/pipeline/pipeline-graph.c

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include <stdbool.h>
3030
#include <stddef.h>
3131
#include <stdint.h>
32+
#include <sof/schedule/ll_schedule_domain.h>
3233

3334
LOG_MODULE_REGISTER(pipe, CONFIG_SOF_LOG_LEVEL);
3435

@@ -181,19 +182,22 @@ static void buffer_set_comp(struct comp_buffer *buffer, struct comp_dev *comp,
181182
}
182183

183184
#ifdef CONFIG_SOF_USERSPACE_LL
185+
/*
186+
* User-space LL: IPC-level user_ll_lock_sched() provides mutual
187+
* exclusion with the LL thread. No per-pipeline lock needed.
188+
*/
184189
#define PPL_LOCK_DECLARE
185-
#define PPL_LOCK() do { \
186-
int ret = sys_mutex_lock(&comp->list_mutex, K_FOREVER); \
187-
assert(ret == 0); \
188-
} while (0)
189-
#define PPL_UNLOCK() do { \
190-
int ret = sys_mutex_unlock(&comp->list_mutex); \
191-
assert(ret == 0); \
192-
} while (0)
190+
#define PPL_LOCK(x) user_ll_lock_sched(x)
191+
#define PPL_UNLOCK(x) user_ll_unlock_sched(x)
193192
#else
193+
/*
194+
* Kernel-space LL. When modifying pipeline connections, block IRQs
195+
* and prevent LL from running. No locking needed when iterating
196+
* the pipeline in the LL thread.
197+
*/
194198
#define PPL_LOCK_DECLARE uint32_t flags
195-
#define PPL_LOCK() irq_local_disable(flags)
196-
#define PPL_UNLOCK() irq_local_enable(flags)
199+
#define PPL_LOCK(x) irq_local_disable(flags)
200+
#define PPL_UNLOCK(x) irq_local_enable(flags)
197201
#endif
198202

199203
int pipeline_connect(struct comp_dev *comp, struct comp_buffer *buffer,
@@ -208,19 +212,19 @@ int pipeline_connect(struct comp_dev *comp, struct comp_buffer *buffer,
208212
else
209213
comp_info(comp, "connect buffer %d as source", buf_get_id(buffer));
210214

211-
PPL_LOCK();
215+
PPL_LOCK(buffer->core);
212216

213217
comp_list = comp_buffer_list(comp, dir);
214218
ret = buffer_attach(buffer, comp_list, dir);
215219
if (ret < 0) {
216220
comp_err(comp, "buffer %d already connected dir %d",
217221
buf_get_id(buffer), dir);
218-
PPL_UNLOCK();
222+
PPL_UNLOCK(buffer->core);
219223
return ret;
220224
}
221225
buffer_set_comp(buffer, comp, dir);
222226

223-
PPL_UNLOCK();
227+
PPL_UNLOCK(buffer->core);
224228

225229
return 0;
226230
}
@@ -235,13 +239,13 @@ void pipeline_disconnect(struct comp_dev *comp, struct comp_buffer *buffer, int
235239
else
236240
comp_dbg(comp, "disconnect buffer %d as source", buf_get_id(buffer));
237241

238-
PPL_LOCK();
242+
PPL_LOCK(buffer->core);
239243

240244
comp_list = comp_buffer_list(comp, dir);
241245
buffer_detach(buffer, comp_list, dir);
242246
buffer_set_comp(buffer, NULL, dir);
243247

244-
PPL_UNLOCK();
248+
PPL_UNLOCK(buffer->core);
245249
}
246250

247251
/* pipelines must be inactive */

src/audio/pipeline/pipeline-stream.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <rtos/kernel.h>
2222
#include <sof/audio/module_adapter/module/generic.h>
2323
#include <sof/lib/cpu-clk-manager.h>
24+
#include <sof/schedule/ll_schedule_domain.h>
2425

2526
#ifdef CONFIG_IPC_MAJOR_4
2627
#include <ipc4/notification.h>
@@ -145,6 +146,19 @@ static int pipeline_comp_copy(struct comp_dev *current,
145146
return err;
146147
}
147148

149+
#ifdef CONFIG_SOF_USERSPACE_LL
150+
/*
151+
* User-space LL: The LL thread runs at cooperative priority (-16)
152+
* and cannot be preempted by IPC (priority 1). No per-pipeline
153+
* lock needed in pipeline_copy() hot path.
154+
*/
155+
#define PPL_LOCK(x) user_ll_lock_sched(x)
156+
#define PPL_UNLOCK(x) user_ll_unlock_sched(x)
157+
#else
158+
#define PPL_LOCK(x)
159+
#define PPL_UNLOCK(x)
160+
#endif
161+
148162
/* Copy data across all pipeline components.
149163
* For capture pipelines it always starts from source component
150164
* and continues downstream and for playback pipelines it first
@@ -162,6 +176,8 @@ int pipeline_copy(struct pipeline *p)
162176
uint32_t dir;
163177
int ret;
164178

179+
PPL_LOCK(p->core);
180+
165181
if (p->source_comp->direction == SOF_IPC_STREAM_PLAYBACK) {
166182
dir = PPL_DIR_UPSTREAM;
167183
start = p->sink_comp;
@@ -178,6 +194,8 @@ int pipeline_copy(struct pipeline *p)
178194
pipe_err(p, "ret = %d, start->comp.id = %u, dir = %u",
179195
ret, dev_comp_id(start), dir);
180196

197+
PPL_UNLOCK(p->core);
198+
181199
return ret;
182200
}
183201

src/include/sof/audio/component.h

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -680,10 +680,6 @@ struct comp_dev {
680680
struct list_item bsource_list; /**< list of source buffers */
681681
struct list_item bsink_list; /**< list of sink buffers */
682682

683-
#ifdef CONFIG_SOF_USERSPACE_LL
684-
struct sys_mutex list_mutex; /**< protect lists of source/sinks */
685-
#endif
686-
687683
/* performance data*/
688684
struct comp_perf_data perf_data;
689685
/* Input Buffer Size for pin 0, add array for other pins if needed */
@@ -868,9 +864,6 @@ static inline void comp_init(const struct comp_driver *drv,
868864
dev->state = COMP_STATE_INIT;
869865
list_init(&dev->bsink_list);
870866
list_init(&dev->bsource_list);
871-
#ifdef CONFIG_SOF_USERSPACE_LL
872-
sys_mutex_init(&dev->list_mutex);
873-
#endif
874867
#ifndef __ZEPHYR__
875868
memcpy_s(&dev->tctx, sizeof(dev->tctx),
876869
trace_comp_drv_get_tr_ctx(dev->drv), sizeof(struct tr_ctx));

src/ipc/ipc-helper.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <sof/ipc/msg.h>
1818
#include <sof/ipc/driver.h>
1919
#include <sof/ipc/schedule.h>
20+
#include <sof/schedule/ll_schedule_domain.h>
2021
#include <rtos/alloc.h>
2122
#include <rtos/cache.h>
2223
#include <sof/lib/cpu.h>
@@ -336,7 +337,15 @@ __cold int ipc_comp_free(struct ipc *ipc, uint32_t comp_id)
336337
return -EINVAL;
337338
}
338339

340+
/* Lock buffer lists to prevent racing with the LL scheduler.
341+
* In user-space builds, use the LL scheduler's sys_mutex
342+
* (re-entrant, so safe if caller already holds it).
343+
*/
344+
#ifdef CONFIG_SOF_USERSPACE_LL
345+
user_ll_lock_sched(icd->core);
346+
#else
339347
irq_local_disable(flags);
348+
#endif
340349
comp_dev_for_each_producer_safe(icd->cd, buffer, safe) {
341350
comp_buffer_set_sink_component(buffer, NULL);
342351
/* This breaks the list, but we anyway delete all buffers */
@@ -349,7 +358,11 @@ __cold int ipc_comp_free(struct ipc *ipc, uint32_t comp_id)
349358
comp_buffer_reset_source_list(buffer);
350359
}
351360

361+
#ifdef CONFIG_SOF_USERSPACE_LL
362+
user_ll_unlock_sched(icd->core);
363+
#else
352364
irq_local_enable(flags);
365+
#endif
353366

354367
/*
355368
* A completed pipeline stores raw comp_dev pointers in its

src/ipc/ipc4/helper.c

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -448,10 +448,21 @@ __cold static int ipc_pipeline_module_free(uint32_t pipeline_id)
448448
struct ipc *ipc = ipc_get();
449449
struct ipc_comp_dev *icd;
450450
int ret;
451+
#ifdef CONFIG_SOF_USERSPACE_LL
452+
int ppl_core;
453+
#endif
451454

452455
assert_can_be_cold();
453456

454457
icd = ipc_get_comp_by_ppl_id(ipc, COMP_TYPE_COMPONENT, pipeline_id, IPC_COMP_ALL);
458+
if (!icd)
459+
return IPC4_SUCCESS;
460+
461+
#ifdef CONFIG_SOF_USERSPACE_LL
462+
ppl_core = icd->core;
463+
user_ll_lock_sched(ppl_core);
464+
#endif
465+
455466
while (icd) {
456467
struct comp_buffer *buffer;
457468
struct comp_buffer *safe;
@@ -481,12 +492,20 @@ __cold static int ipc_pipeline_module_free(uint32_t pipeline_id)
481492
else
482493
ret = ipc_comp_free(ipc, icd->id);
483494

484-
if (ret)
495+
if (ret) {
496+
#ifdef CONFIG_SOF_USERSPACE_LL
497+
user_ll_unlock_sched(ppl_core);
498+
#endif
485499
return IPC4_INVALID_RESOURCE_STATE;
500+
}
486501

487502
icd = ipc_get_comp_by_ppl_id(ipc, COMP_TYPE_COMPONENT, pipeline_id, IPC_COMP_ALL);
488503
}
489504

505+
#ifdef CONFIG_SOF_USERSPACE_LL
506+
user_ll_unlock_sched(ppl_core);
507+
#endif
508+
490509
return IPC4_SUCCESS;
491510
}
492511

@@ -560,21 +579,25 @@ __cold static struct comp_buffer *ipc4_create_buffer(struct comp_dev *src, bool
560579
* disable any interrupts.
561580
*/
562581

563-
#define ll_block(cross_core_bind, flags) \
582+
#if CONFIG_SOF_USERSPACE_LL
583+
#error "CONFIG_SOF_USERSPACE_LL not compatible with cross-core streams"
584+
#else
585+
#define ll_block(src_core, dst_core, flags) \
564586
do { \
565-
if (cross_core_bind) \
587+
if (src_core != dst_core) \
566588
domain_block(sof_get()->platform_timer_domain); \
567589
else \
568590
irq_local_disable(flags); \
569591
} while (0)
570592

571-
#define ll_unblock(cross_core_bind, flags) \
593+
#define ll_unblock(src_core, dst_core, flags) \
572594
do { \
573-
if (cross_core_bind) \
595+
if (src_core != dst_core) \
574596
domain_unblock(sof_get()->platform_timer_domain); \
575597
else \
576598
irq_local_enable(flags); \
577599
} while (0)
600+
#endif
578601

579602
/* Calling both ll_block() and ll_wait_finished_on_core() makes sure LL will not start its
580603
* next cycle and its current cycle on specified core has finished.
@@ -606,8 +629,15 @@ static int ll_wait_finished_on_core(struct comp_dev *dev)
606629

607630
#else
608631

609-
#define ll_block(cross_core_bind, flags) irq_local_disable(flags)
610-
#define ll_unblock(cross_core_bind, flags) irq_local_enable(flags)
632+
#if CONFIG_SOF_USERSPACE_LL
633+
/* note: cross-core streams are disabled in the build in this branch,
634+
* so we can safely use 'src_core' only */
635+
#define ll_block(src_core, dst_core, flags) do { user_ll_lock_sched(src_core); (void)flags; } while(0)
636+
#define ll_unblock(src_core, dst_core, flags) do { user_ll_unlock_sched(src_core) ; (void)flags; } while(0)
637+
#else
638+
#define ll_block(src_core, dst_core, flags) irq_local_disable(flags)
639+
#define ll_unblock(src_core, dst_core, flags) irq_local_enable(flags)
640+
#endif
611641

612642
#endif
613643

@@ -794,7 +824,7 @@ __cold int ipc_comp_connect(struct ipc *ipc, ipc_pipe_comp_connect *_connect)
794824
* blocked on corresponding core(s) to prevent IPC or IDC task getting preempted which
795825
* could result in buffers being only half connected when a pipeline task gets executed.
796826
*/
797-
ll_block(cross_core_bind, flags);
827+
ll_block(source->ipc_config.core, sink->ipc_config.core, flags);
798828

799829
if (cross_core_bind) {
800830
#if CONFIG_CROSS_CORE_STREAM
@@ -851,7 +881,7 @@ __cold int ipc_comp_connect(struct ipc *ipc, ipc_pipe_comp_connect *_connect)
851881
source->direction_set = true;
852882
}
853883

854-
ll_unblock(cross_core_bind, flags);
884+
ll_unblock(source->ipc_config.core, sink->ipc_config.core, flags);
855885

856886
return IPC4_SUCCESS;
857887

@@ -865,7 +895,7 @@ __cold int ipc_comp_connect(struct ipc *ipc, ipc_pipe_comp_connect *_connect)
865895
e_sink_connect:
866896
pipeline_disconnect(source, buffer, PPL_CONN_DIR_COMP_TO_BUFFER);
867897
free:
868-
ll_unblock(cross_core_bind, flags);
898+
ll_unblock(source->ipc_config.core, sink->ipc_config.core, flags);
869899
buffer_free(buffer);
870900
return IPC4_INVALID_RESOURCE_STATE;
871901
}
@@ -938,24 +968,24 @@ __cold int ipc_comp_disconnect(struct ipc *ipc, ipc_pipe_comp_connect *_connect)
938968
* IPC or IDC task getting preempted which could result in buffers being only half connected
939969
* when a pipeline task gets executed.
940970
*/
941-
ll_block(cross_core_unbind, flags);
971+
ll_block(src->ipc_config.core, sink->ipc_config.core, flags);
942972

943973
if (cross_core_unbind) {
944974
#if CONFIG_CROSS_CORE_STREAM
945975
/* Make sure LL has finished on both cores */
946976
if (!cpu_is_me(src->ipc_config.core))
947977
if (ll_wait_finished_on_core(src) < 0) {
948-
ll_unblock(cross_core_unbind, flags);
978+
ll_unblock(src->ipc_config.core, sink->ipc_config.core, flags);
949979
return IPC4_FAILURE;
950980
}
951981
if (!cpu_is_me(sink->ipc_config.core))
952982
if (ll_wait_finished_on_core(sink) < 0) {
953-
ll_unblock(cross_core_unbind, flags);
983+
ll_unblock(src->ipc_config.core, sink->ipc_config.core, flags);
954984
return IPC4_FAILURE;
955985
}
956986
#else
957987
tr_err(&ipc_tr, "Cross-core binding is disabled");
958-
ll_unblock(cross_core_unbind, flags);
988+
ll_unblock(src->ipc_config.core, sink->ipc_config.core, flags);
959989
return IPC4_FAILURE;
960990
#endif
961991
}
@@ -972,7 +1002,7 @@ __cold int ipc_comp_disconnect(struct ipc *ipc, ipc_pipe_comp_connect *_connect)
9721002
unbind_data.source = audio_buffer_get_source(&buffer->audio_buffer);
9731003
ret1 = comp_unbind(sink, &unbind_data);
9741004

975-
ll_unblock(cross_core_unbind, flags);
1005+
ll_unblock(src->ipc_config.core, sink->ipc_config.core, flags);
9761006

9771007
buffer_free(buffer);
9781008

0 commit comments

Comments
 (0)