diff options
| author | Jerome Forissier <jerome.forissier@linaro.org> | 2025-04-18 16:09:32 +0200 | 
|---|---|---|
| committer | Tom Rini <trini@konsulko.com> | 2025-04-23 13:19:44 -0600 | 
| commit | a27844cc9443341ffe1314ccc4c384a54ad1ae32 (patch) | |
| tree | 4a99e50a494582975b7fd3f9d65db01fa9141ddb /arch/sandbox | |
| parent | 3691328ef69555148962654e7f834fd0b5273fcb (diff) | |
sandbox: add initjmp()
Add initjm[() to sandbox, a non-standard extension to setjmp()/
longjmp() allowing to initialize a jump buffer with a function pointer
and a stack pointer. This will be useful to later introduce threads.
With this new function it becomes possible to longjmp() to a particular
function pointer (rather than to a point previously reached during
program execution as is the case with setjmp()), and with a custom stack.
Both things are needed to spin off a new thread. Then the usual
setjmp()/longjmp() pair is enough to save and restore a context, i.e.,
switch thread. The implementation is taken verbatim from barebox [1] with
the exception of the additional stack_sz argument. It is quite complex
because contrary to U-Boot platform code we don't know how the system's
C library implements the jump buffer, so we can't just write the function
and stack pointers into it.
[1] https://github.com/barebox/barebox/blob/b2a15c383ddc/arch/sandbox/os/setjmp.c
Signed-off-by: Jerome Forissier <jerome.forissier@linaro.org>
Diffstat (limited to 'arch/sandbox')
| -rw-r--r-- | arch/sandbox/cpu/Makefile | 11 | ||||
| -rw-r--r-- | arch/sandbox/cpu/initjmp.c | 175 | 
2 files changed, 185 insertions, 1 deletions
| diff --git a/arch/sandbox/cpu/Makefile b/arch/sandbox/cpu/Makefile index bfcdc335d32..038ad78accc 100644 --- a/arch/sandbox/cpu/Makefile +++ b/arch/sandbox/cpu/Makefile @@ -5,7 +5,7 @@  # (C) Copyright 2000-2003  # Wolfgang Denk, DENX Software Engineering, wd@denx.de. -obj-y	:= cache.o cpu.o state.o +obj-y	:= cache.o cpu.o state.o initjmp.o  extra-y	:= start.o os.o  extra-$(CONFIG_SANDBOX_SDL)    += sdl.o  obj-$(CONFIG_XPL_BUILD)	+= spl.o @@ -29,6 +29,15 @@ cmd_cc_eth-raw-os.o = $(CC) $(filter-out -nostdinc, \  $(obj)/eth-raw-os.o: $(src)/eth-raw-os.c FORCE  	$(call if_changed_dep,cc_eth-raw-os.o) +# initjmp.c is build in the system environment, so needs standard includes +# CFLAGS_REMOVE_initjmp.o cannot be used to drop header include path +quiet_cmd_cc_initjmp.o = CC $(quiet_modtag)  $@ +cmd_cc_initjmp.o = $(CC) $(filter-out -nostdinc, \ +	$(patsubst -I%,-idirafter%,$(c_flags))) -c -o $@ $< + +$(obj)/initjmp.o: $(src)/initjmp.c FORCE +	$(call if_changed_dep,cc_initjmp.o) +  # sdl.c fails to build with -fshort-wchar using musl  cmd_cc_sdl.o = $(CC) $(filter-out -nostdinc -fshort-wchar, \  	$(patsubst -I%,-idirafter%,$(c_flags))) -fno-lto -c -o $@ $< diff --git a/arch/sandbox/cpu/initjmp.c b/arch/sandbox/cpu/initjmp.c new file mode 100644 index 00000000000..6e72d32cb4b --- /dev/null +++ b/arch/sandbox/cpu/initjmp.c @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * An implementation of initjmp() in C, that plays well with the system's + * setjmp() and longjmp() functions. + * Taken verbatim from arch/sandbox/os/setjmp.c in the barebox project. + * Modified so that initjmp() accepts a stack_size argument. + * + * Copyright (C) 2006  Anthony Liguori <anthony@codemonkey.ws> + * Copyright (C) 2011  Kevin Wolf <kwolf@redhat.com> + * Copyright (C) 2012  Alex Barcelo <abarcelo@ac.upc.edu> + * Copyright (C) 2021  Ahmad Fatoum, Pengutronix + * Copyright (C) 2025  Linaro Ltd. + * This file is partly based on pth_mctx.c, from the GNU Portable Threads + *  Copyright (c) 1999-2006 Ralf S. Engelschall <rse@engelschall.com> + */ + +/* XXX Is there a nicer way to disable glibc's stack check for longjmp? */ +#ifdef _FORTIFY_SOURCE +#undef _FORTIFY_SOURCE +#endif + +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <setjmp.h> +#include <signal.h> + +typedef sigjmp_buf _jmp_buf __attribute__((aligned((16)))); +_Static_assert(sizeof(_jmp_buf) <= 512, "sigjmp_buf size exceeds expectation"); + +/* + * Information for the signal handler (trampoline) + */ +static struct { +	_jmp_buf *reenter; +	void (*entry)(void); +	volatile sig_atomic_t called; +} tr_state; + +/* + * "boot" function + * This is what starts the coroutine, is called from the trampoline + * (from the signal handler when it is not signal handling, read ahead + * for more information). + */ +static void __attribute__((noinline, noreturn)) +coroutine_bootstrap(void (*entry)(void)) +{ +	for (;;) +		entry(); +} + +/* + * This is used as the signal handler. This is called with the brand new stack + * (thanks to sigaltstack). We have to return, given that this is a signal + * handler and the sigmask and some other things are changed. + */ +static void coroutine_trampoline(int signal) +{ +	/* Get the thread specific information */ +	tr_state.called = 1; + +	/* +	 * Here we have to do a bit of a ping pong between the caller, given that +	 * this is a signal handler and we have to do a return "soon". Then the +	 * caller can reestablish everything and do a siglongjmp here again. +	 */ +	if (!sigsetjmp(*tr_state.reenter, 0)) { +		return; +	} + +	/* +	 * Ok, the caller has siglongjmp'ed back to us, so now prepare +	 * us for the real machine state switching. We have to jump +	 * into another function here to get a new stack context for +	 * the auto variables (which have to be auto-variables +	 * because the start of the thread happens later). Else with +	 * PIC (i.e. Position Independent Code which is used when PTH +	 * is built as a shared library) most platforms would +	 * horrible core dump as experience showed. +	 */ +	coroutine_bootstrap(tr_state.entry); +} + +int __attribute__((weak)) initjmp(_jmp_buf jmp, void (*func)(void), +				  void *stack_base, size_t stack_size) +{ +	struct sigaction sa; +	struct sigaction osa; +	stack_t ss; +	stack_t oss; +	sigset_t sigs; +	sigset_t osigs; + +	/* The way to manipulate stack is with the sigaltstack function. We +	 * prepare a stack, with it delivering a signal to ourselves and then +	 * put sigsetjmp/siglongjmp where needed. +	 * This has been done keeping coroutine-ucontext (from the QEMU project) +	 * as a model and with the pth ideas (GNU Portable Threads). +	 * See coroutine-ucontext for the basics of the coroutines and see +	 * pth_mctx.c (from the pth project) for the +	 * sigaltstack way of manipulating stacks. +	 */ + +	tr_state.entry = func; +	tr_state.reenter = (void *)jmp; + +	/* +	 * Preserve the SIGUSR2 signal state, block SIGUSR2, +	 * and establish our signal handler. The signal will +	 * later transfer control onto the signal stack. +	 */ +	sigemptyset(&sigs); +	sigaddset(&sigs, SIGUSR2); +	pthread_sigmask(SIG_BLOCK, &sigs, &osigs); +	sa.sa_handler = coroutine_trampoline; +	sigfillset(&sa.sa_mask); +	sa.sa_flags = SA_ONSTACK; +	if (sigaction(SIGUSR2, &sa, &osa) != 0) { +		return -1; +	} + +	/* +	 * Set the new stack. +	 */ +	ss.ss_sp = stack_base; +	ss.ss_size = stack_size; +	ss.ss_flags = 0; +	if (sigaltstack(&ss, &oss) < 0) { +		return -1; +	} + +	/* +	 * Now transfer control onto the signal stack and set it up. +	 * It will return immediately via "return" after the sigsetjmp() +	 * was performed. Be careful here with race conditions.  The +	 * signal can be delivered the first time sigsuspend() is +	 * called. +	 */ +	tr_state.called = 0; +	pthread_kill(pthread_self(), SIGUSR2); +	sigfillset(&sigs); +	sigdelset(&sigs, SIGUSR2); +	while (!tr_state.called) { +		sigsuspend(&sigs); +	} + +	/* +	 * Inform the system that we are back off the signal stack by +	 * removing the alternative signal stack. Be careful here: It +	 * first has to be disabled, before it can be removed. +	 */ +	sigaltstack(NULL, &ss); +	ss.ss_flags = SS_DISABLE; +	if (sigaltstack(&ss, NULL) < 0) { +		return -1; +	} +	sigaltstack(NULL, &ss); +	if (!(oss.ss_flags & SS_DISABLE)) { +		sigaltstack(&oss, NULL); +	} + +	/* +	 * Restore the old SIGUSR2 signal handler and mask +	 */ +	sigaction(SIGUSR2, &osa, NULL); +	pthread_sigmask(SIG_SETMASK, &osigs, NULL); + +	/* +	 * jmp can now be used to enter the trampoline again, but not as a +	 * signal handler. Instead it's longjmp'd to directly. +	 */ +	return 0; +} + | 
