-
Notifications
You must be signed in to change notification settings - Fork 349
userspace: proxy: Add support for llext modules #10643
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?
Changes from all commits
06a520d
1b4bf74
62fea34
03fb3c8
ee44813
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,6 +26,7 @@ | |
| #include <stdint.h> | ||
|
|
||
| #include <sof/lib_manager.h> | ||
| #include <sof/llext_manager.h> | ||
| #include <sof/audio/component.h> | ||
| #include <sof/schedule/dp_schedule.h> | ||
| #include <rtos/userspace_helper.h> | ||
|
|
@@ -163,6 +164,7 @@ static int user_work_item_init(struct userspace_context *user_ctx, struct k_heap | |
| work_item->event = &worker.event; | ||
| #endif | ||
| work_item->params.context = user_ctx; | ||
| work_item->params.mod = NULL; | ||
| user_ctx->work_item = work_item; | ||
|
|
||
| return 0; | ||
|
|
@@ -198,7 +200,7 @@ static int userspace_proxy_invoke(struct userspace_context *user_ctx, uint32_t c | |
| struct k_mem_partition ipc_part = { | ||
| .start = ipc_req_buf, | ||
| .size = MAILBOX_HOSTBOX_SIZE, | ||
| .attr = user_get_partition_attr(ipc_req_buf) | K_MEM_PARTITION_P_RO_U_RO, | ||
| .attr = user_get_partition_cache_attr(ipc_req_buf) | K_MEM_PARTITION_P_RO_U_RO, | ||
| }; | ||
| int ret = 0, ret2; | ||
|
|
||
|
|
@@ -274,8 +276,25 @@ static int userspace_proxy_memory_init(struct userspace_context *user_ctx, | |
| tr_dbg(&userspace_proxy_tr, "Heap partition %#lx + %zx, attr = %u", | ||
| heap_part.start, heap_part.size, heap_part.attr); | ||
|
|
||
| #if !defined(CONFIG_XTENSA_MMU_DOUBLE_MAP) && defined(CONFIG_SOF_ZEPHYR_HEAP_CACHED) | ||
| #define HEAP_PART_CACHED | ||
| /* When a new memory domain is created, only the "factory" entries from the L2 page | ||
| * tables are copied. Memory that was dynamically mapped during firmware execution | ||
| * will not be accessible from the new domain. The k_heap structure (drv->user_heap) | ||
| * resides in such dynamically mapped memory, so we must explicitly add a partition | ||
| * for it to ensure that syscalls can access this structure from the userspace domain. | ||
| */ | ||
| struct k_mem_partition heap_struct_part = { | ||
| .attr = K_MEM_PARTITION_P_RW_U_NA | | ||
| user_get_partition_cache_attr(POINTER_TO_UINT(drv->user_heap)) | ||
| }; | ||
|
|
||
| k_mem_region_align(&heap_struct_part.start, &heap_struct_part.size, | ||
| POINTER_TO_UINT(drv->user_heap), | ||
| sizeof(*drv->user_heap), CONFIG_MM_DRV_PAGE_SIZE); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does this mean that
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Its grants access for kernel mode only. |
||
|
|
||
| tr_dbg(&userspace_proxy_tr, "Heap struct partition %#lx + %zx, attr = %u", | ||
| heap_struct_part.start, heap_struct_part.size, heap_struct_part.attr); | ||
|
|
||
| #if defined(CONFIG_SOF_ZEPHYR_HEAP_CACHED) | ||
| /* Add cached module private heap to memory partitions */ | ||
| struct k_mem_partition heap_cached_part = { | ||
| .attr = K_MEM_PARTITION_P_RW_U_RW | XTENSA_MMU_CACHED_WB | ||
|
|
@@ -294,10 +313,11 @@ static int userspace_proxy_memory_init(struct userspace_context *user_ctx, | |
| * These include ops structures marked with APP_TASK_DATA. | ||
| */ | ||
| &common_partition, | ||
| #ifdef HEAP_PART_CACHED | ||
| #ifdef CONFIG_SOF_ZEPHYR_HEAP_CACHED | ||
| &heap_cached_part, | ||
| #endif | ||
| &heap_part | ||
| &heap_part, | ||
| &heap_struct_part | ||
| }; | ||
|
|
||
| tr_dbg(&userspace_proxy_tr, "Common partition %#lx + %zx, attr = %u", | ||
|
|
@@ -326,7 +346,7 @@ static int userspace_proxy_add_sections(struct userspace_context *user_ctx, uint | |
|
|
||
| mem_partition.start = mod->segment[idx].v_base_addr; | ||
| mem_partition.size = mod->segment[idx].flags.r.length * CONFIG_MM_DRV_PAGE_SIZE; | ||
| mem_partition.attr |= user_get_partition_attr(mem_partition.start); | ||
| mem_partition.attr |= user_get_partition_cache_attr(mem_partition.start); | ||
|
|
||
| ret = k_mem_domain_add_partition(user_ctx->comp_dom, &mem_partition); | ||
|
|
||
|
|
@@ -339,7 +359,7 @@ static int userspace_proxy_add_sections(struct userspace_context *user_ctx, uint | |
|
|
||
| lib_manager_get_instance_bss_address(instance_id, mod, &va_base, &mem_partition.size); | ||
| mem_partition.start = POINTER_TO_UINT(va_base); | ||
| mem_partition.attr = user_get_partition_attr(mem_partition.start) | | ||
| mem_partition.attr = user_get_partition_cache_attr(mem_partition.start) | | ||
| K_MEM_PARTITION_P_RW_U_RW; | ||
| ret = k_mem_domain_add_partition(user_ctx->comp_dom, &mem_partition); | ||
|
|
||
|
|
@@ -382,7 +402,7 @@ static int userspace_proxy_start_agent(struct userspace_context *user_ctx, | |
| } | ||
|
|
||
| int userspace_proxy_create(struct userspace_context **user_ctx, const struct comp_driver *drv, | ||
| const struct sof_man_module *manifest, system_agent_start_fn start_fn, | ||
| const struct sof_man_module *manifest, system_agent_start_fn agent_fn, | ||
| const struct system_agent_params *agent_params, | ||
| const void **agent_interface, const struct module_interface **ops) | ||
| { | ||
|
|
@@ -410,15 +430,20 @@ int userspace_proxy_create(struct userspace_context **user_ctx, const struct com | |
| if (ret) | ||
| goto error_dom; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unrelated to this PR, but you might want to revisit your memory domain life cycle. Currently memory domain destruction isn't really supported by the Zephyr version, that SOF is linking to. Recently 3032b58f52d776296a6e7e9c1a9c44b87f3cf019 has been merged into Zephyr, which makes it possible, but even with that domains have to be freed explicitly |
||
|
|
||
| ret = userspace_proxy_add_sections(context, agent_params->instance_id, manifest); | ||
| if (agent_fn) | ||
| ret = userspace_proxy_add_sections(context, agent_params->instance_id, manifest); | ||
| else | ||
| /* llext modules do not use the system agent. */ | ||
| ret = llext_manager_add_domain(agent_params->module_id, domain); | ||
|
softwarecki marked this conversation as resolved.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does llext_manager_add_domain really add a domain to something (thread i guess?)? If not, I encourage you to change the function name to *prep_domain or *init_domain. The previous use case for llext_manager_add_domain in scheduler_dp_task_init is straightforward whereas this use case is much harder to understand. A better name would greatly improve readability.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So it doesn't add a domain itself, i.e. a domain remains unassigned. This caused my confusion and I started to wonder why the domain isn't added for the non-llext case. I suspected a bug, but then I figured out that this function only adds partitions, but the domain itself is assigned to the thread in another place.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Exactly, the memory domain is a separate entity. Memory partitions and threads are added to a domain. Multiple threads can be assigned to the same memory domain using the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So you do understand my confusion. This function shall be named llext_manager_import_partitions or maybe llext_manager_add_seg_partitions... because this what it does: it incorporates a module's segments (as partitions) into a domain. |
||
|
|
||
| if (ret) | ||
| goto error_dom; | ||
|
|
||
| ret = user_work_item_init(context, drv->user_heap); | ||
| if (ret) | ||
| goto error_dom; | ||
|
|
||
| ret = userspace_proxy_start_agent(context, start_fn, agent_params, agent_interface); | ||
| ret = userspace_proxy_start_agent(context, agent_fn, agent_params, agent_interface); | ||
| if (ret) { | ||
| tr_err(&userspace_proxy_tr, "System agent failed with error %d.", ret); | ||
| goto error_work_item; | ||
|
softwarecki marked this conversation as resolved.
|
||
|
|
@@ -680,7 +705,7 @@ static int userspace_proxy_get_configuration(struct processing_module *mod, uint | |
| struct k_mem_partition ipc_resp_part = { | ||
| .start = ipc_resp_buf, | ||
| .size = SOF_IPC_MSG_MAX_SIZE, | ||
| .attr = user_get_partition_attr(ipc_resp_buf) | K_MEM_PARTITION_P_RW_U_RW, | ||
| .attr = user_get_partition_cache_attr(ipc_resp_buf) | K_MEM_PARTITION_P_RW_U_RW, | ||
| }; | ||
| int ret; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,7 +6,7 @@ | |
| #ifndef MODULE_LLEXT_H | ||
| #define MODULE_LLEXT_H | ||
|
|
||
| #define SOF_LLEXT_MODULE_MANIFEST(manifest_name, entry, affinity, mod_uuid, instances) \ | ||
| #define SOF_LLEXT_MODULE_MANIFEST(manifest_name, entry, affinity, mod_uuid, instances, ...) \ | ||
| { \ | ||
| .module = { \ | ||
| .name = manifest_name, \ | ||
|
|
@@ -16,6 +16,8 @@ | |
| .type = { \ | ||
| .load_type = SOF_MAN_MOD_TYPE_LLEXT, \ | ||
| .domain_ll = 1, \ | ||
| .user_mode = COND_CODE_0(NUM_VA_ARGS_LESS_1(_, ##__VA_ARGS__), (0), \ | ||
| (GET_ARG_N(1, __VA_ARGS__))), \ | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does this mean that you only ever use the first of the variadic parameters? Then maybe just add one |
||
| }, \ | ||
| .affinity_mask = (affinity), \ | ||
| } \ | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -150,7 +150,11 @@ const void *fast_get(struct k_heap *heap, const void *dram_ptr, size_t size) | |||||||||
| alloc_align = PLATFORM_DCACHE_ALIGN; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| if (size > FAST_GET_MAX_COPY_SIZE || !IS_ENABLED(CONFIG_USERSPACE)) | ||||||||||
| /* The module driver heap is shared by all instances of a given module. | ||||||||||
| * Instances can share the allocated buffer. | ||||||||||
| */ | ||||||||||
| if (size > FAST_GET_MAX_COPY_SIZE || !IS_ENABLED(CONFIG_USERSPACE) || | ||||||||||
| IS_ENABLED(CONFIG_SOF_USERSPACE_USE_DRIVER_HEAP)) | ||||||||||
| alloc_ptr = dram_ptr; | ||||||||||
| else | ||||||||||
| /* When userspace is enabled only share large buffers */ | ||||||||||
|
|
@@ -188,8 +192,12 @@ const void *fast_get(struct k_heap *heap, const void *dram_ptr, size_t size) | |||||||||
| /* | ||||||||||
| * We only get there for large buffers, since small buffers with | ||||||||||
| * enabled userspace don't create fast-get entries | ||||||||||
| * | ||||||||||
| * We also reach this point when using the module driver heap. | ||||||||||
| * Since the heap is already shared across module instances, | ||||||||||
| * we skip memory domain manipulation. | ||||||||||
| */ | ||||||||||
| if (mdom->num_partitions > 1) { | ||||||||||
| if (k_current_get()->base.user_options & K_USER && size > FAST_GET_MAX_COPY_SIZE) { | ||||||||||
|
||||||||||
| if (k_current_get()->base.user_options & K_USER && size > FAST_GET_MAX_COPY_SIZE) { | |
| if ((k_current_get()->base.user_options & K_USER) && | |
| (size > FAST_GET_MAX_COPY_SIZE || | |
| IS_ENABLED(CONFIG_SOF_USERSPACE_USE_DRIVER_HEAP))) { |
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.
Can you address this, same problem, how do the comment and code relate?
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.
not sure I understand the comment correctly. "syscalls can access" - syscalls execute in kernel mode, so they can access everything?
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.
Yes, syscalls executes in kernel mode but it still use userspace module memory domain
Uh oh!
There was an error while loading. Please reload this page.
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.
@softwarecki do you mean that when a syscall is executed, it inherits page tables from the userspace context?
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.
@lyakh Exactly, yes. That is why, when a new memory domain is created, the L2 page tables are copied. This provides access for kernel to selected memory regions.
@dcpleung Can you confirm?
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.
A new memory domain is created by copying the kernel pagetables. So during syscall, the memory can be accessed if it is accessible via kernel threads. If you have already mapped the heap area at boot as readable by kernel threads, syscall can use it.
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.
However, if the memory was mapped dynamically at runtime, it will not be present in the new domain and must be explicitly mapped there as well.
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, if you allocate memory, you will need to explicitly add it via memory partition to a memory domain before domain associated user threads can access them. Kernel threads and syscalls depend on whether that memory block has been mapped at boot or via
k_mem_map(). If not, you definitely need to add it to the domain or else it cannot be accessed in kernel mode.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.
I dont think we want to long term alloc user pages (for user usage) and then additionally map them to kernel for convenience syscall usage. Yes ok today when doing the initial integration (and pls call it out), but longer term we really should only copy what is needed between user -> kernel for syscalls (like the DMA API does now).
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 isn't user accessible memory. Only the kernel has access to it. However, we need to add a mapping because it was dynamically mapped in runtime, so this mapping won't be automatically copied to the new memory domain.