Skip to content

Commit cb4edfd

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 e8f9c5e commit cb4edfd

7 files changed

Lines changed: 146 additions & 37 deletions

File tree

src/audio/pipeline/pipeline-graph.c

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <sof/audio/module_adapter/module/generic.h>
1111
#include <sof/audio/pipeline.h>
1212
#include <sof/ipc/msg.h>
13+
#include <sof/schedule/ll_schedule_domain.h>
1314
#include <sof/lib/vregion.h>
1415
#include <rtos/interrupt.h>
1516
#include <rtos/symbol.h>
@@ -29,6 +30,7 @@
2930
#include <stdbool.h>
3031
#include <stddef.h>
3132
#include <stdint.h>
33+
#include <sof/schedule/ll_schedule_domain.h>
3234

3335
LOG_MODULE_REGISTER(pipe, CONFIG_SOF_LOG_LEVEL);
3436

@@ -181,19 +183,24 @@ static void buffer_set_comp(struct comp_buffer *buffer, struct comp_dev *comp,
181183
}
182184

183185
#ifdef CONFIG_SOF_USERSPACE_LL
186+
/*
187+
* User-space LL: callers (IPC handlers) already hold the per-core LL
188+
* lock via user_ll_lock_sched()/ll_block() while modifying pipeline
189+
* connections, which provides mutual exclusion with the LL thread. No
190+
* additional lock is taken here; instead assert that the lock is held.
191+
*/
184192
#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)
193+
#define PPL_LOCK(x) user_ll_assert_locked(x)
194+
#define PPL_UNLOCK(x)
193195
#else
196+
/*
197+
* Kernel-space LL. When modifying pipeline connections, block IRQs
198+
* and prevent LL from running. No locking needed when iterating
199+
* the pipeline in the LL thread.
200+
*/
194201
#define PPL_LOCK_DECLARE uint32_t flags
195-
#define PPL_LOCK() irq_local_disable(flags)
196-
#define PPL_UNLOCK() irq_local_enable(flags)
202+
#define PPL_LOCK(x) irq_local_disable(flags)
203+
#define PPL_UNLOCK(x) irq_local_enable(flags)
197204
#endif
198205

199206
int pipeline_connect(struct comp_dev *comp, struct comp_buffer *buffer,
@@ -208,19 +215,19 @@ int pipeline_connect(struct comp_dev *comp, struct comp_buffer *buffer,
208215
else
209216
comp_info(comp, "connect buffer %d as source", buf_get_id(buffer));
210217

211-
PPL_LOCK();
218+
PPL_LOCK(buffer->core);
212219

213220
comp_list = comp_buffer_list(comp, dir);
214221
ret = buffer_attach(buffer, comp_list, dir);
215222
if (ret < 0) {
216223
comp_err(comp, "buffer %d already connected dir %d",
217224
buf_get_id(buffer), dir);
218-
PPL_UNLOCK();
225+
PPL_UNLOCK(buffer->core);
219226
return ret;
220227
}
221228
buffer_set_comp(buffer, comp, dir);
222229

223-
PPL_UNLOCK();
230+
PPL_UNLOCK(buffer->core);
224231

225232
return 0;
226233
}
@@ -235,13 +242,13 @@ void pipeline_disconnect(struct comp_dev *comp, struct comp_buffer *buffer, int
235242
else
236243
comp_dbg(comp, "disconnect buffer %d as source", buf_get_id(buffer));
237244

238-
PPL_LOCK();
245+
PPL_LOCK(buffer->core);
239246

240247
comp_list = comp_buffer_list(comp, dir);
241248
buffer_detach(buffer, comp_list, dir);
242249
buffer_set_comp(buffer, NULL, dir);
243250

244-
PPL_UNLOCK();
251+
PPL_UNLOCK(buffer->core);
245252
}
246253

247254
/* pipelines must be inactive */

src/audio/pipeline/pipeline-stream.c

Lines changed: 19 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,20 @@ 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: pipeline_copy() runs as an LL task, with the per-core
152+
* LL lock already held by the LL thread for the whole tick. Taking the
153+
* lock again here would only add syscall overhead on this hot path, so
154+
* we just assert that the lock is in fact held.
155+
*/
156+
#define PPL_LOCK(x) user_ll_assert_locked(x)
157+
#define PPL_UNLOCK(x)
158+
#else
159+
#define PPL_LOCK(x)
160+
#define PPL_UNLOCK(x)
161+
#endif
162+
148163
/* Copy data across all pipeline components.
149164
* For capture pipelines it always starts from source component
150165
* and continues downstream and for playback pipelines it first
@@ -162,6 +177,8 @@ int pipeline_copy(struct pipeline *p)
162177
uint32_t dir;
163178
int ret;
164179

180+
PPL_LOCK(p->core);
181+
165182
if (p->source_comp->direction == SOF_IPC_STREAM_PLAYBACK) {
166183
dir = PPL_DIR_UPSTREAM;
167184
start = p->sink_comp;
@@ -178,6 +195,8 @@ int pipeline_copy(struct pipeline *p)
178195
pipe_err(p, "ret = %d, start->comp.id = %u, dir = %u",
179196
ret, dev_comp_id(start), dir);
180197

198+
PPL_UNLOCK(p->core);
199+
181200
return ret;
182201
}
183202

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/include/sof/schedule/ll_schedule_domain.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,12 @@ bool zephyr_ll_user_heap_verify(struct k_heap *heap);
122122
void zephyr_ll_user_resources_init(void);
123123
void user_ll_lock_sched(int core);
124124
void user_ll_unlock_sched(int core);
125+
#ifdef CONFIG_ASSERT
126+
/* Assert that the calling context already holds the LL lock for 'core'. */
127+
void user_ll_assert_locked(int core);
128+
#else
129+
static inline void user_ll_assert_locked(int core) { (void)core; }
130+
#endif
125131
#endif /* CONFIG_SOF_USERSPACE_LL */
126132

127133
static inline struct ll_schedule_domain *domain_init

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 lock
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)