-
Notifications
You must be signed in to change notification settings - Fork 350
user-space ll: grant access to DAIs and DMAs #10533
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
Looks like this when running the "userspace_ll" ztest on a Intel PTL system: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Adds user-space support for LL scheduler threads by granting them access to all platform DAIs and DMAs, using existing SOF device enumeration helpers.
Changes:
- Add helpers to grant a user thread access to all DAI/DMA Zephyr devices
- Expose DMA/DAI device list APIs for platform-agnostic enumeration
- Integrate the new access grants into Zephyr LL domain thread registration
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| zephyr/lib/userspace_helper.c | Adds userspace helpers to grant access to all DAIs/DMAs and introduces logging |
| zephyr/lib/dma.c | Adds API to enumerate DMA Zephyr devices |
| zephyr/include/sof/lib/dma.h | Declares the new DMA device list API |
| zephyr/include/rtos/userspace_helper.h | Declares new userspace grant helper APIs |
| src/schedule/zephyr_domain.c | Grants DAI/DMA access to LL user threads during registration |
| src/lib/dai.c | Adds API to enumerate DAI Zephyr devices |
| src/include/sof/lib/dai-zephyr.h | Declares the new DAI device list API |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| #ifdef CONFIG_SOF_USERSPACE_LL | ||
|
|
||
| int user_access_to_mailbox(struct k_mem_domain *domain, k_tid_t thread_id) |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
user_access_to_mailbox() is now compiled only when CONFIG_SOF_USERSPACE_LL is enabled, but it appears to be a general userspace helper (and is still declared under #if CONFIG_USERSPACE in the header). This creates a concrete risk of link failures or missing functionality in CONFIG_USERSPACE=y builds that don’t enable CONFIG_SOF_USERSPACE_LL. Consider moving the #ifdef CONFIG_SOF_USERSPACE_LL guard so it only wraps the new DAI/DMA grant helpers (not user_access_to_mailbox()), or alternatively provide stubs / adjust header declarations so the build remains consistent across configurations.
| k_thread_access_grant(thread, dt->sem, domain->lock, zephyr_domain->timer); | ||
| user_grant_dai_access_all(thread); | ||
| user_grant_dma_access_all(thread); |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These calls are unconditional, but the implementations in userspace_helper.c are built only under CONFIG_USERSPACE && CONFIG_SOF_USERSPACE_LL (and currently the header declarations are not similarly guarded). This can lead to unresolved symbols in configurations where this file is compiled but CONFIG_SOF_USERSPACE_LL (or even CONFIG_USERSPACE) is off. Guard these calls with the same Kconfig option(s), and/or provide static inline no-op stubs in the header for non-enabled configurations.
| k_thread_access_grant(thread, dt->sem, domain->lock, zephyr_domain->timer); | |
| user_grant_dai_access_all(thread); | |
| user_grant_dma_access_all(thread); | |
| k_thread_access_grant(thread, dt->sem, domain->lock, zephyr_domain->timer); | |
| #if CONFIG_USERSPACE && CONFIG_SOF_USERSPACE_LL | |
| user_grant_dai_access_all(thread); | |
| user_grant_dma_access_all(thread); | |
| #endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a valid concern, but this code is already guarded by CONFIG_SOF_USERSPACE_LL and that depends on CONFIG_USERSPACE, so this is sufficient.
| * | ||
| * @param thread user-space thread for which DAI access is granted | ||
| */ | ||
| void user_grant_dai_access_all(struct k_thread *thread); |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These APIs are declared for all CONFIG_USERSPACE builds, but the implementations are under CONFIG_SOF_USERSPACE_LL in the .c file. To keep the public API consistent and avoid configuration-dependent link issues, either (1) wrap these declarations with #ifdef CONFIG_SOF_USERSPACE_LL to match the implementation, or (2) provide static inline no-op stubs when CONFIG_SOF_USERSPACE_LL is disabled.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is on purpose. One will get linker errors if trying to use these in the wrong config and this should be sufficient.
| * | ||
| * @param thread user-space thread for which DMA access is granted | ||
| */ | ||
| void user_grant_dma_access_all(struct k_thread *thread); |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These APIs are declared for all CONFIG_USERSPACE builds, but the implementations are under CONFIG_SOF_USERSPACE_LL in the .c file. To keep the public API consistent and avoid configuration-dependent link issues, either (1) wrap these declarations with #ifdef CONFIG_SOF_USERSPACE_LL to match the implementation, or (2) provide static inline no-op stubs when CONFIG_SOF_USERSPACE_LL is disabled.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is on purpose. One will get linker errors if trying to use these in the wrong config and this should be sufficient.
zephyr/include/sof/lib/dma.h
Outdated
| * \param count Pointer to store the number of devices in the list. | ||
| * \return Pointer to the array of device pointers. | ||
| */ | ||
| const struct device **dma_get_device_list(size_t *count); |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Returning const struct device ** allows callers to modify the returned array contents (even if the devices themselves are const). Since this API is exposing a shared internal list, it should return an immutable list type: const struct device * const *. Same applies to dai_get_device_list() for consistency/safety.
| const struct device **dma_get_device_list(size_t *count); | |
| const struct device * const *dma_get_device_list(size_t *count); |
zephyr/lib/dma.c
Outdated
| const struct device **dma_get_device_list(size_t *count) | ||
| { | ||
| static const struct device *device_list[ARRAY_SIZE(dma)]; | ||
| int i; |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dma_get_device_list() writes to a function-static array on every call. If multiple threads can call this concurrently, this introduces a data race on device_list[]. To avoid racy writes, populate the list once during a single-threaded init phase (e.g., in dmac_init()), or implement a one-time initialization guard that does not perform concurrent writes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will modify for V2
zephyr/lib/dma.c
Outdated
| int i; | ||
|
|
||
| if (count) | ||
| *count = ARRAY_SIZE(dma); | ||
|
|
||
| for (i = 0; i < ARRAY_SIZE(dma); i++) | ||
| device_list[i] = dma[i].z_dev; | ||
|
|
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dma_get_device_list() writes to a function-static array on every call. If multiple threads can call this concurrently, this introduces a data race on device_list[]. To avoid racy writes, populate the list once during a single-threaded init phase (e.g., in dmac_init()), or implement a one-time initialization guard that does not perform concurrent writes.
| int i; | |
| if (count) | |
| *count = ARRAY_SIZE(dma); | |
| for (i = 0; i < ARRAY_SIZE(dma); i++) | |
| device_list[i] = dma[i].z_dev; | |
| static struct k_spinlock device_list_lock; | |
| static int device_list_initialized; | |
| int i; | |
| k_spinlock_key_t key; | |
| if (count) | |
| *count = ARRAY_SIZE(dma); | |
| key = k_spin_lock(&device_list_lock); | |
| if (!device_list_initialized) { | |
| for (i = 0; i < ARRAY_SIZE(dma); i++) | |
| device_list[i] = dma[i].z_dev; | |
| device_list_initialized = 1; | |
| } | |
| k_spin_unlock(&device_list_lock, key); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will modify for V2. Above is way too much code for the need.
zephyr/lib/userspace_helper.c
Outdated
| for (i = 0; i < count; i++) { | ||
| k_thread_access_grant(thread, devices[i]); | ||
| LOG_DBG("Granted DMA device access: %d %s to thread %p", | ||
| i, devices[i]->name, thread); | ||
| } | ||
| } | ||
|
|
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Logging once per DMA device can become noisy and adds overhead (even at LOG_DBG, it still does formatting work depending on Zephyr logging configuration). Consider logging a single summary line (as done for DAI) or gating per-device debug logs behind a more verbose config.
| for (i = 0; i < count; i++) { | |
| k_thread_access_grant(thread, devices[i]); | |
| LOG_DBG("Granted DMA device access: %d %s to thread %p", | |
| i, devices[i]->name, thread); | |
| } | |
| } | |
| for (i = 0; i < count; i++) | |
| k_thread_access_grant(thread, devices[i]); | |
| LOG_DBG("Granted DMA access to thread %p for %zu devices", thread, count); | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is tuned for the common case. Most SOF targets have a handful of DMAs, so the logging is not too much (and detail is useful). With DAIs, we have many targets where amount of logging is too much, so I went with a summary instead.
lgirdwood
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nothing major from me, just one question.
src/lib/dai.c
Outdated
|
|
||
| const struct device **dai_get_device_list(size_t *count) | ||
| { | ||
| if (count) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems count is mandatory ? assert ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, let me turn this to an assert
|
The static array allocated inside Both of these functions could instead fill a buffer provided by the caller or invoke a callback for each element. I expect this function will be used more in the future, so regenerating the list on every call seems unnecessary. Keeping a duplicated list permanently also wastes memory. The current approach ends up combining both drawbacks. I think that the best option would be to drop the internal list entirely and use |
zephyr/lib/dma.c
Outdated
|
|
||
| const struct device **dma_get_device_list(size_t *count) | ||
| { | ||
| static const struct device *device_list[ARRAY_SIZE(dma)]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, maybe at least a comment why this doesn't have to be protected, or maybe it does? Can we use a caller-supplied array instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, let me see how to make this cleaner for V2 given copilot, you @lyakh and @softwarecki all disliked the implementation. Maybe I can add a cold_rodata array for this information, so we don't waste any SRAM for this info and minimize cost in code size.
kv2019i
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for reviews, will address in V2.
src/lib/dai.c
Outdated
|
|
||
| const struct device **dai_get_device_list(size_t *count) | ||
| { | ||
| if (count) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, let me turn this to an assert
| k_thread_access_grant(thread, dt->sem, domain->lock, zephyr_domain->timer); | ||
| user_grant_dai_access_all(thread); | ||
| user_grant_dma_access_all(thread); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a valid concern, but this code is already guarded by CONFIG_SOF_USERSPACE_LL and that depends on CONFIG_USERSPACE, so this is sufficient.
| * | ||
| * @param thread user-space thread for which DAI access is granted | ||
| */ | ||
| void user_grant_dai_access_all(struct k_thread *thread); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is on purpose. One will get linker errors if trying to use these in the wrong config and this should be sufficient.
| * | ||
| * @param thread user-space thread for which DMA access is granted | ||
| */ | ||
| void user_grant_dma_access_all(struct k_thread *thread); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is on purpose. One will get linker errors if trying to use these in the wrong config and this should be sufficient.
zephyr/lib/dma.c
Outdated
|
|
||
| const struct device **dma_get_device_list(size_t *count) | ||
| { | ||
| static const struct device *device_list[ARRAY_SIZE(dma)]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, let me see how to make this cleaner for V2 given copilot, you @lyakh and @softwarecki all disliked the implementation. Maybe I can add a cold_rodata array for this information, so we don't waste any SRAM for this info and minimize cost in code size.
zephyr/lib/dma.c
Outdated
| const struct device **dma_get_device_list(size_t *count) | ||
| { | ||
| static const struct device *device_list[ARRAY_SIZE(dma)]; | ||
| int i; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will modify for V2
zephyr/lib/dma.c
Outdated
| int i; | ||
|
|
||
| if (count) | ||
| *count = ARRAY_SIZE(dma); | ||
|
|
||
| for (i = 0; i < ARRAY_SIZE(dma); i++) | ||
| device_list[i] = dma[i].z_dev; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will modify for V2. Above is way too much code for the need.
zephyr/lib/userspace_helper.c
Outdated
| for (i = 0; i < count; i++) { | ||
| k_thread_access_grant(thread, devices[i]); | ||
| LOG_DBG("Granted DMA device access: %d %s to thread %p", | ||
| i, devices[i]->name, thread); | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is tuned for the common case. Most SOF targets have a handful of DMAs, so the logging is not too much (and detail is useful). With DAIs, we have many targets where amount of logging is too much, so I went with a summary instead.
Add helper functions to provide a list of all Zephyr devices and to grant a user thread access to them. Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Add helper function to grant a user thread access to all system DMA devices. Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
If the low-latency domain tasks are run in user-space, grant the LL threads access to all DAI and DMA devices. In future, there may be need to filter access permissions more, and/or have variation between different build targets, but for now the user LL thread will have access to the same devices as it does in kernel space. Note that access to memory is separately controlled and in addition to having access to a DMA device, user-space thread also needs access to the memory before it can do DMA transactions. Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Now that we have CONFIG_SOF_USERSPACE_LL, use it to conditionally drop some of the thread access helpers. Access to mailbox, DAIs and DMAs is currently only needed if LL thread is run in user-space. Leave these out if SOF is built for LL thread scheduling in kernel space. Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
b18e8ca to
a153d34
Compare
|
V2 pushed:
|
softwarecki
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks much better now. The code is cleaner, easier to read, and its overall size is reduced. Good work!
A small set of patches that allow granting access to all DAIs and DMAs to a user-space thread, and this is case, the LL scheduler threads when run in user-space. This PR uses the existing helper code in SOF that allow to enumerate the devices in a platform-agnostic manner, so this PR works for all SOF build targerts.