Skip to content
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

LLEXT: run non-performance critical code in DRAM #9522

Merged
merged 9 commits into from
Nov 27, 2024
47 changes: 40 additions & 7 deletions scripts/llext_link_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,12 @@ def main():

command = [args.command]

executable = []
writable = []
readonly = []

text_found = False

elf = ELFFile(open(args.file, 'rb'))

# Create an object file with sections grouped by their properties,
Expand All @@ -102,13 +105,13 @@ def main():
# In general additional executable sections are possible, e.g.
# .init. In the future support for arbitrary such sections can be
# added, similar to writable and read-only data below.
if s_name != '.text':
print(f"Warning! Non-standard executable section {s_name}")

text_addr = max_alignment(text_addr, 0x1000, s_alignment)
text_size = s_size

command.append(f'-Wl,-Ttext=0x{text_addr:x}')
if s_name == '.text':
text_found = True
text_addr = max_alignment(text_addr, 0x1000, s_alignment)
text_size = s_size
command.append(f'-Wl,-Ttext=0x{text_addr:x}')
else:
executable.append(section)

continue

Expand All @@ -122,6 +125,36 @@ def main():
# .rodata or other read-only sections
readonly.append(section)

if not text_found:
raise RuntimeError('No .text section found in the object file')

# The original LLEXT support in SOF linked all LLEXT modules with pre-
# calculated addresses. Such modules can only be used at those exact
# addresses, so we map memory buffers for such modules to those
# addresses and copy them there.
# Now we also need to be able to re-link parts of modules at run-time to
# run at arbitrary memory locations. One of the use-cases is running
# parts of the module directly in DRAM - sacrificing performance but
# saving scarce SRAM. We achieve this by placing non-performance
# critical functions in a .text.dram ELF section. When compiling and
# linking such functions, an additional .literal.dram section is
# automatically created. Note, that for some reason the compiler also
# marks that section as executable.
# This script links those sections at address 0. We could hard-code
# section names, but so far we choose to only link .text the "original"
# way and all other executable sections we link at 0.
exe_addr = 0

for section in executable:
s_alignment = section.header['sh_addralign']
s_name = section.name

exe_addr = align_up(exe_addr, s_alignment)

command.append(f'-Wl,--section-start={s_name}=0x{exe_addr:x}')

exe_addr += section.header['sh_size']

start_addr = align_up(text_addr + text_size, 0x1000)

for section in readonly:
Expand Down
6 changes: 3 additions & 3 deletions src/audio/drc/drc.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <ipc/control.h>
#include <ipc/stream.h>
#include <ipc/topology.h>
#include <module/module/llext.h>
#include <rtos/alloc.h>
#include <rtos/init.h>
#include <rtos/panic.h>
Expand Down Expand Up @@ -138,7 +139,7 @@ static int drc_setup(struct drc_comp_data *cd, uint16_t channels, uint32_t rate)
* End of DRC setup code. Next the standard component methods.
*/

static int drc_init(struct processing_module *mod)
__cold static int drc_init(struct processing_module *mod)
{
struct module_data *md = &mod->priv;
struct comp_dev *dev = mod->dev;
Expand Down Expand Up @@ -195,7 +196,7 @@ static int drc_init(struct processing_module *mod)
return ret;
}

static int drc_free(struct processing_module *mod)
__cold static int drc_free(struct processing_module *mod)
{
struct drc_comp_data *cd = module_get_private_data(mod);

Expand Down Expand Up @@ -407,7 +408,6 @@ SOF_MODULE_INIT(drc, sys_comp_module_drc_interface_init);
/* modular: llext dynamic link */

#include <module/module/api_ver.h>
#include <module/module/llext.h>
#include <rimage/sof/user/manifest.h>

#define UUID_DRC 0xda, 0xe4, 0x6e, 0xb3, 0x6f, 0x00, 0xf9, 0x47, \
Expand Down
6 changes: 6 additions & 0 deletions src/include/module/module/llext.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,10 @@ static const struct sof_module_api_build_info buildinfo __section(".mod_buildinf
.api_version_number.full = SOF_MODULE_API_CURRENT_VERSION, \
}

#if CONFIG_LLEXT_TYPE_ELF_RELOCATABLE && defined(LL_EXTENSION_BUILD)
#define __cold __section(".text.dram")
#else
#define __cold
#endif

#endif
29 changes: 14 additions & 15 deletions src/library_manager/llext_manager.c
Original file line number Diff line number Diff line change
Expand Up @@ -236,19 +236,24 @@ static int llext_manager_unload_module(uint32_t module_id, const struct sof_man_
return err;
}

static int llext_manager_link(struct llext_buf_loader *ebl, struct sof_man_fw_desc *desc,
struct sof_man_module *mod, uint32_t module_id, struct module_data *md,
static bool llext_manager_section_detached(const elf_shdr_t *shdr)
{
return shdr->sh_addr < SOF_MODULE_DRAM_LINK_END;
}

static int llext_manager_link(struct llext_buf_loader *ebl, uintptr_t dram_base,
const char *name, uint32_t module_id, struct module_data *md,
const void **buildinfo,
const struct sof_man_module_manifest **mod_manifest)
{
uintptr_t dram_base = (uintptr_t)desc - SOF_MAN_ELF_TEXT_OFFSET;
struct lib_manager_mod_ctx *ctx = lib_manager_get_mod_ctx(module_id);
/* Identify if this is the first time loading this module */
struct llext_load_param ldr_parm = {
.relocate_local = !ctx->segment[LIB_MANAGER_TEXT].size,
.pre_located = true,
.section_detached = llext_manager_section_detached,
};
int ret = llext_load(&ebl->loader, mod->name, &md->llext, &ldr_parm);
int ret = llext_load(&ebl->loader, name, &md->llext, &ldr_parm);

if (ret)
return ret;
Expand All @@ -263,7 +268,7 @@ static int llext_manager_link(struct llext_buf_loader *ebl, struct sof_man_fw_de
ctx->segment[LIB_MANAGER_TEXT].size,
ctx->segment[LIB_MANAGER_TEXT].file_offset);

/* This contains all other sections, except .text, it might contain .bss too */
/* All read-only data sections */
ctx->segment[LIB_MANAGER_RODATA].addr =
ebl->loader.sects[LLEXT_MEM_RODATA].sh_addr;
ctx->segment[LIB_MANAGER_RODATA].file_offset =
Expand All @@ -275,6 +280,7 @@ static int llext_manager_link(struct llext_buf_loader *ebl, struct sof_man_fw_de
ctx->segment[LIB_MANAGER_RODATA].size,
ctx->segment[LIB_MANAGER_RODATA].file_offset);

/* All writable data sections */
ctx->segment[LIB_MANAGER_DATA].addr =
ebl->loader.sects[LLEXT_MEM_DATA].sh_addr;
ctx->segment[LIB_MANAGER_DATA].file_offset =
Expand Down Expand Up @@ -336,7 +342,7 @@ uintptr_t llext_manager_allocate_module(struct processing_module *proc,
mod_array = (struct sof_man_module *)((char *)desc + SOF_MAN_MODULE_OFFSET(0));

/* LLEXT linking is only needed once for all the modules in the library */
ret = llext_manager_link(&ebl, desc, mod_array, module_id, md,
ret = llext_manager_link(&ebl, dram_base, mod_array[0].name, module_id, md,
(const void **)&buildinfo, &mod_manifest);
if (ret < 0) {
tr_err(&lib_manager_tr, "linking failed: %d", ret);
Expand All @@ -357,15 +363,8 @@ uintptr_t llext_manager_allocate_module(struct processing_module *proc,
if (ret < 0)
return 0;

/* Manifest is in read-only data */
uintptr_t dram_rodata = (uintptr_t)ctx->base_addr +
ctx->segment[LIB_MANAGER_RODATA].file_offset;
uintptr_t va_rodata_base = ctx->segment[LIB_MANAGER_RODATA].addr;
size_t offset = (uintptr_t)mod_manifest - dram_rodata;

/* ctx->mod_manifest points to an array of module manifests */
ctx->mod_manifest = sys_cache_uncached_ptr_get((__sparse_force void __sparse_cache *)
(va_rodata_base + offset));
/* ctx->mod_manifest points to a const array of module manifests */
ctx->mod_manifest = mod_manifest;
}

return ctx->mod_manifest[entry_index].module.entry_point;
Expand Down
1 change: 1 addition & 0 deletions src/math/numbers.c
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ int norm_int32(int32_t val)

return 31 - c;
}
EXPORT_SYMBOL(norm_int32);

#endif /* CONFIG_NORM */

Expand Down
2 changes: 2 additions & 0 deletions tools/rimage/src/include/rimage/module.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ struct module_section {

/* The contents of the section lie in the rom memory space */
bool rom;
/* The section is detached from the main part of the module */
bool detached;

/* ADSP devices have their RAM regions mapped twice. The first mapping is set in the CPU
* to bypass the L1 cache, and so access through pointers in that region is coherent between
Expand Down
9 changes: 9 additions & 0 deletions tools/rimage/src/include/rimage/sof/user/manifest.h
Original file line number Diff line number Diff line change
Expand Up @@ -250,4 +250,13 @@ struct sof_man_module_manifest {
(sizeof(struct sof_man_fw_header) + \
(index) * sizeof(struct sof_man_module))

/*
* LLEXT module link area for detached sections. When an LLEXT module contains
* detached sections, they will be linked with addresses in this range. The
* upper limit has no special meaning, simply assuming that 128MiB should be
* enough and that SRAM will not use these addresses.
*/
#define SOF_MODULE_DRAM_LINK_START 0
#define SOF_MODULE_DRAM_LINK_END 0x08000000

#endif /* __RIMAGE_USER_MANIFEST_H__ */
54 changes: 43 additions & 11 deletions tools/rimage/src/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <rimage/file_utils.h>
#include <rimage/rimage.h>
#include <rimage/misc_utils.h>
#include <rimage/sof/user/manifest.h>

int module_read_section(const struct module *module, const struct module_section *section,
void *buffer, const size_t size)
Expand Down Expand Up @@ -121,7 +122,7 @@ void module_print_zones(const struct module *module)

/**
* Print a list of valid program headers
*
*
* @param module pointer to a module structure
*/
static void module_print_programs(const struct module *module)
Expand All @@ -143,7 +144,7 @@ static void module_print_programs(const struct module *module)

/**
* Goes through program headers array to find the physical address based on the virtual address.
*
*
* @param elf elf file structure
* @param vaddr virtual address
* @return physical address when success, virtual address on error
Expand Down Expand Up @@ -173,7 +174,7 @@ unsigned long uncache_to_cache(const struct memory_alias *alias, unsigned long a

/**
* Checks if the section is placed in the rom memory address space
*
*
* @param config Memory configuration structure
* @param section section to be checked
* @return true if section is placed in rom memory address space
Expand All @@ -199,9 +200,36 @@ static bool section_is_rom(const struct memory_config *config,
return false;
}

/**
* Checks if the section is detached from the main module
*
* Some sections can be detached from the main module, e.g. for running in DRAM
*
* @param config Memory configuration structure
* @param section section to be checked
* @return true if section is detached
*/
static bool section_is_detached(const struct memory_config *config,
const struct elf_section_header *section)
{
uint32_t sect_start, sect_end;
const uint32_t start = SOF_MODULE_DRAM_LINK_START, end = SOF_MODULE_DRAM_LINK_END;

sect_start = section->data.vaddr;
sect_end = sect_start + section->data.size;

if (sect_end <= start || sect_start >= end)
return false;
if (sect_start >= start && sect_end <= end)
return true;

fprintf(stderr, "Warning! Section %s partially overlaps with dram memory.\n", section->name);
return false;
}

/**
* Initialize module_sections_info structure
*
*
* @param info Pointer to a module_sections_info structure
*/
static void sections_info_init(struct module_sections_info *info)
Expand All @@ -213,7 +241,7 @@ static void sections_info_init(struct module_sections_info *info)

/**
* Adds section to module_sections_info structure
*
*
* @param info Pointer to a module_sections_info structure
* @param section module_section structure
*/
Expand Down Expand Up @@ -244,7 +272,7 @@ static void sections_info_add(struct module_sections_info *info, struct module_s

/**
* Calculates file size after adding all sections
*
*
* @param info Pointer to a module_sections_info structure
*/
static void sections_info_finalize(struct module_sections_info *info)
Expand All @@ -257,7 +285,7 @@ static void sections_info_finalize(struct module_sections_info *info)

/**
* Checks the section header (type and flags) to determine the section type.
*
*
* @param section section header
* @return enum module_section_type
*/
Expand Down Expand Up @@ -315,11 +343,12 @@ void module_parse_sections(struct module *module, const struct memory_config *me
out_section->size = sect->data.size;
out_section->type = get_section_type(sect);
out_section->rom = section_is_rom(mem_cfg, sect);
out_section->detached = section_is_detached(mem_cfg, sect);
out_section->address = sect->data.vaddr;
out_section->load_address = find_physical_address(&module->elf, sect->data.vaddr);

/* Don't convert ROM addresses, ROM sections aren't included in the output image */
if (!out_section->rom) {
if (!out_section->rom && !out_section->detached) {
/* Walk the sections in the ELF file, changing the VMA/LMA of each uncached section
* to the equivalent address in the cached area of memory. */
out_section->address = uncache_to_cache(&mem_cfg->alias,
Expand Down Expand Up @@ -357,7 +386,10 @@ void module_parse_sections(struct module *module, const struct memory_config *me
break;
}

if (out_section->rom) {
if (out_section->detached) {
/* Detached sections are copied as is and don't contribute to metadata */
fprintf(stdout, " detached");
} else if (out_section->rom) {
/* ROM sections aren't included in the output image */
fprintf(stdout, " ROM");
} else {
Expand Down Expand Up @@ -430,7 +462,7 @@ void module_close(struct module *module)

/**
* Checks if the contents of the section overlaps
*
*
* @param a first section to check
* @param b second section to check
* @return true if space of a sections overlap
Expand All @@ -451,7 +483,7 @@ static bool section_check_overlap(const struct module_section *a, const struct m

/**
* Checks if the contents of the modules overlaps
*
*
* @param mod first module to check
* @param mod2 second module to check
* @return error code
Expand Down
Loading