From 967168a536386c2e7c827484498efb569355bcdf Mon Sep 17 00:00:00 2001 From: Ederson de Souza Date: Tue, 27 Feb 2024 15:05:47 -0800 Subject: [PATCH] subsys/llext: Generate syscalls stubs for EDK exclusively for userspace A new Kconfig option which generates syscall stubs assuming that extensions will always run on userspace, thus simplifying linking them, as there's no need for z_impl_ stubs (used for direct syscalls), CONFIG_LLEXT_EDK_USERSPACE_ONLY. While defining __ZEPHYR_USER__ could have the same effect for optmised builds, people building extensions on debug environments - thus non-optimised - would suffer, as they'd need to somehow make the stubs available (by either exporting the symbol or implementing dummy stubs). Signed-off-by: Ederson de Souza --- CMakeLists.txt | 15 +++++++++++ cmake/llext-edk.cmake | 9 +++++++ scripts/build/gen_syscalls.py | 47 +++++++++++++++++++++-------------- subsys/llext/Kconfig | 8 ++++++ 4 files changed, 60 insertions(+), 19 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 22b60009bac..e1d88a2fca4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -623,6 +623,7 @@ get_property(LIBC_LINK_LIBRARIES TARGET zephyr_interface PROPERTY LIBC_LINK_LIBR zephyr_link_libraries(${LIBC_LINK_LIBRARIES}) set(syscall_list_h ${CMAKE_CURRENT_BINARY_DIR}/include/generated/syscall_list.h) +set(edk_syscall_list_h ${CMAKE_CURRENT_BINARY_DIR}/edk/include/generated/syscall_list.h) set(syscalls_json ${CMAKE_CURRENT_BINARY_DIR}/misc/generated/syscalls.json) set(struct_tags_json ${CMAKE_CURRENT_BINARY_DIR}/misc/generated/struct_tags.json) @@ -2065,6 +2066,19 @@ endif() set(llext_edk_file ${PROJECT_BINARY_DIR}/${CONFIG_LLEXT_EDK_NAME}.tar.xz) add_custom_command( OUTPUT ${llext_edk_file} + # Regenerate syscalls in case CONFIG_LLEXT_EDK_USERSPACE_ONLY + COMMAND ${CMAKE_COMMAND} + -E make_directory edk/include/generated + COMMAND + ${PYTHON_EXECUTABLE} + ${ZEPHYR_BASE}/scripts/build/gen_syscalls.py + --json-file ${syscalls_json} # Read this file + --base-output edk/include/generated/syscalls # Write to this dir + --syscall-dispatch edk/include/generated/syscall_dispatch.c # Write this file + --syscall-list ${edk_syscall_list_h} + $<$:--userspace-only> + ${SYSCALL_LONG_REGISTERS_ARG} + ${SYSCALL_SPLIT_TIMEOUT_ARG} COMMAND ${CMAKE_COMMAND} -DPROJECT_BINARY_DIR=${PROJECT_BINARY_DIR} -DAPPLICATION_SOURCE_DIR=${APPLICATION_SOURCE_DIR} @@ -2075,6 +2089,7 @@ add_custom_command( -Dllext_edk_name=${CONFIG_LLEXT_EDK_NAME} -DWEST_TOPDIR=${WEST_TOPDIR} -DZEPHYR_BASE=${ZEPHYR_BASE} + -DCONFIG_LLEXT_EDK_USERSPACE_ONLY=${CONFIG_LLEXT_EDK_USERSPACE_ONLY} -P ${ZEPHYR_BASE}/cmake/llext-edk.cmake DEPENDS ${logical_target_for_zephyr_elf} COMMAND_EXPAND_LISTS diff --git a/cmake/llext-edk.cmake b/cmake/llext-edk.cmake index 7c8cf4201f5..dce42e32090 100644 --- a/cmake/llext-edk.cmake +++ b/cmake/llext-edk.cmake @@ -24,6 +24,10 @@ # - WEST_TOPDIR: Path to the west top directory. # - APPLICATION_SOURCE_DIR: Path to the application source directory. # - PROJECT_BINARY_DIR: Path to the project binary build directory. +# - CONFIG_LLEXT_EDK_USERSPACE_ONLY: Whether to copy syscall headers from the +# edk directory. This is necessary when building an extension that only +# supports userspace, as the syscall headers are regenerated in the edk +# directory. cmake_minimum_required(VERSION 3.20.0) @@ -94,6 +98,11 @@ foreach(dir ${include_dirs}) list(APPEND all_flags_cmake "-I\${CMAKE_CURRENT_LIST_DIR}/${dest_rel}") endforeach() +if(CONFIG_LLEXT_EDK_USERSPACE_ONLY) + # Copy syscall headers from edk directory, as they were regenerated there. + file(COPY ${PROJECT_BINARY_DIR}/edk/include/generated/ DESTINATION ${LLEXT_EDK_INC}/zephyr/include/generated) +endif() + list(JOIN all_flags_make " " all_flags_str) file(WRITE ${llext_edk}/Makefile.cflags "LLEXT_CFLAGS = ${all_flags_str}") diff --git a/scripts/build/gen_syscalls.py b/scripts/build/gen_syscalls.py index 8755f3c649a..b6e332d45ca 100755 --- a/scripts/build/gen_syscalls.py +++ b/scripts/build/gen_syscalls.py @@ -205,21 +205,27 @@ def union_decl(type, split): middle = "struct { uintptr_t lo, hi; } split" if split else "uintptr_t x" return "union { %s; %s val; }" % (middle, type) -def wrapper_defs(func_name, func_type, args, fn): +def wrapper_defs(func_name, func_type, args, fn, userspace_only): ret64 = need_split(func_type) mrsh_args = [] # List of rvalue expressions for the marshalled invocation decl_arglist = ", ".join([" ".join(argrec) for argrec in args]) or "void" syscall_id = "K_SYSCALL_" + func_name.upper() - wrap = "extern %s z_impl_%s(%s);\n" % (func_type, func_name, decl_arglist) - wrap += "\n" + wrap = '' + if not userspace_only: + wrap += "extern %s z_impl_%s(%s);\n" % (func_type, func_name, decl_arglist) + wrap += "\n" + wrap += "__pinned_func\n" wrap += "static inline %s %s(%s)\n" % (func_type, func_name, decl_arglist) wrap += "{\n" - wrap += "#ifdef CONFIG_USERSPACE\n" + if not userspace_only: + wrap += "#ifdef CONFIG_USERSPACE\n" + wrap += ("\t" + "uint64_t ret64;\n") if ret64 else "" - wrap += "\t" + "if (z_syscall_trap()) {\n" + if not userspace_only: + wrap += "\t" + "if (z_syscall_trap()) {\n" valist_args = [] for argnum, (argtype, argname) in enumerate(args): @@ -267,18 +273,19 @@ def wrapper_defs(func_name, func_type, args, fn): for argname in valist_args: wrap += "\t\t" + "va_end(%s);\n" % argname wrap += retcode - wrap += "\t" + "}\n" - wrap += "#endif\n" + if not userspace_only: + wrap += "\t" + "}\n" + wrap += "#endif\n" - # Otherwise fall through to direct invocation of the impl func. - # Note the compiler barrier: that is required to prevent code from - # the impl call from being hoisted above the check for user - # context. - impl_arglist = ", ".join([argrec[1] for argrec in args]) - impl_call = "z_impl_%s(%s)" % (func_name, impl_arglist) - wrap += "\t" + "compiler_barrier();\n" - wrap += "\t" + "%s%s;\n" % ("return " if func_type != "void" else "", - impl_call) + # Otherwise fall through to direct invocation of the impl func. + # Note the compiler barrier: that is required to prevent code from + # the impl call from being hoisted above the check for user + # context. + impl_arglist = ", ".join([argrec[1] for argrec in args]) + impl_call = "z_impl_%s(%s)" % (func_name, impl_arglist) + wrap += "\t" + "compiler_barrier();\n" + wrap += "\t" + "%s%s;\n" % ("return " if func_type != "void" else "", + impl_call) wrap += "}\n" @@ -377,7 +384,7 @@ def marshall_defs(func_name, func_type, args): return mrsh, mrsh_name -def analyze_fn(match_group, fn): +def analyze_fn(match_group, fn, userspace_only): func, args = match_group try: @@ -395,7 +402,7 @@ def analyze_fn(match_group, fn): marshaller = None marshaller, handler = marshall_defs(func_name, func_type, args) - invocation = wrapper_defs(func_name, func_type, args, fn) + invocation = wrapper_defs(func_name, func_type, args, fn, userspace_only) # Entry in _k_syscall_table table_entry = "[%s] = %s" % (sys_id, handler) @@ -424,6 +431,8 @@ def parse_args(): help="Generate marshalling files (*_mrsh.c)") parser.add_argument("-e", "--syscall-export-llext", help="output C system call export for extensions") + parser.add_argument("-u", "--userspace-only", action="store_true", + help="Only generate the userpace path of wrappers") args = parser.parse_args() @@ -448,7 +457,7 @@ def main(): exported = [] for match_group, fn, to_emit in syscalls: - handler, inv, mrsh, sys_id, entry = analyze_fn(match_group, fn) + handler, inv, mrsh, sys_id, entry = analyze_fn(match_group, fn, args.userspace_only) if fn not in invocations: invocations[fn] = [] diff --git a/subsys/llext/Kconfig b/subsys/llext/Kconfig index 792ce4b2da5..c0dc5e5e881 100644 --- a/subsys/llext/Kconfig +++ b/subsys/llext/Kconfig @@ -83,4 +83,12 @@ config LLEXT_EDK_NAME stating EDK location, used on generated Makefile.cflags. For instance, the default name, "llext-edk", becomes LLEXT_EDK_INSTALL_DIR. +config LLEXT_EDK_USERSPACE_ONLY + bool "Only generate the Userpace codepath on syscall stubs for the EDK" + help + Syscall stubs can contain code that verifies if running code is at user + or kernel space and route the call accordingly. If the EDK is expected + to be used by userspace only extensions, this option will make EDK stubs + not contain the routing code, and only generate the userspace one. + endmenu