Give barebox running under an emulator or a debugger an easy way to report status to whatever started it via the Semihosting SYS_EXIT command.
Only on 64-bit AArch64/RISC-V does SYS_EXIT take a parameter block it can carry the status in. On AArch32 that is SYS_EXIT_EXTENDED, so let's use the latter everywhere. On the off-chance the host doesn't support it, smhexit -p (plain) can be used instead. Assisted-by: Claude:opus-5 Signed-off-by: Ahmad Fatoum <[email protected]> --- I want to make use of this for integration tests: barebox will assert invariants at runtime and exit with this if they fail. --- commands/Kconfig | 9 ++++ commands/Makefile | 1 + commands/smhexit.c | 69 +++++++++++++++++++++++++++++++ drivers/firmware/semihosting.c | 30 +++++++++++++- include/asm-generic/semihosting.h | 2 + 5 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 commands/smhexit.c diff --git a/commands/Kconfig b/commands/Kconfig index c8804c8e17fe..29e574c72952 100644 --- a/commands/Kconfig +++ b/commands/Kconfig @@ -587,6 +587,15 @@ config CMD_RESET Options: -f force RESET, don't call shutdown +config CMD_SMHEXIT + tristate + depends on SEMIHOSTING + prompt "smhexit" + help + End the semihosting session and hand the host an exit status. + + Usage: smhexit [-pf] [STATUS] + config CMD_SAVES tristate depends on CMD_LOADS diff --git a/commands/Makefile b/commands/Makefile index 7f0c68a5e58e..17389465950d 100644 --- a/commands/Makefile +++ b/commands/Makefile @@ -25,6 +25,7 @@ obj-$(CONFIG_CMD_SMC) += smc.o obj-$(CONFIG_CMD_MSLEEP) += msleep.o obj-$(CONFIG_CMD_RESET) += reset.o obj-$(CONFIG_CMD_POWEROFF) += poweroff.o +obj-$(CONFIG_CMD_SMHEXIT) += smhexit.o obj-$(CONFIG_CMD_GO) += go.o obj-$(CONFIG_CMD_PARTITION) += partition.o obj-$(CONFIG_CMD_LS) += ls.o diff --git a/commands/smhexit.c b/commands/smhexit.c new file mode 100644 index 000000000000..b519e9399ae0 --- /dev/null +++ b/commands/smhexit.c @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include <barebox.h> +#include <command.h> +#include <complete.h> +#include <console.h> +#include <getopt.h> +#include <linux/kstrtox.h> +#include <asm/semihosting.h> + +static int do_smhexit(int argc, char *argv[]) +{ + bool extended_flag = true, shutdown_flag = true; + int opt, status = 0; + + while ((opt = getopt(argc, argv, "pf")) > 0) { + switch (opt) { + case 'p': + extended_flag = false; + break; + case 'f': + shutdown_flag = false; + break; + default: + return COMMAND_ERROR_USAGE; + } + } + + argc -= optind; + argv += optind; + + if (argc > 1) + return COMMAND_ERROR_USAGE; + + if (argc == 1 && kstrtoint(argv[0], 0, &status)) + return COMMAND_ERROR_USAGE; + + if (shutdown_flag) + shutdown_barebox(); + + console_flush(); + semihosting_exit(status, extended_flag); + + /* Unreachable */ + return 1; +} + +BAREBOX_CMD_HELP_START(smhexit) +BAREBOX_CMD_HELP_TEXT("Tell the debugger or emulator barebox runs under that the session") +BAREBOX_CMD_HELP_TEXT("is over and hand it STATUS (0 if none is given) as the exit status.") +BAREBOX_CMD_HELP_TEXT("QEMU exits with it, so a boot driven from the outside can report") +BAREBOX_CMD_HELP_TEXT("its outcome without anything having to read the console.") +BAREBOX_CMD_HELP_TEXT("") +BAREBOX_CMD_HELP_TEXT("Options:") +BAREBOX_CMD_HELP_OPT("-p", "use the plain SYS_EXIT call for a host that has no") +BAREBOX_CMD_HELP_OPT("", "SYS_EXIT_EXTENDED. On 32-bit, this loses STATUS.") +BAREBOX_CMD_HELP_TEXT("") +BAREBOX_CMD_HELP_TEXT("Without a host listening the trap instruction is undefined and the") +BAREBOX_CMD_HELP_TEXT("machine stops instead.") +BAREBOX_CMD_HELP_END + +BAREBOX_CMD_START(smhexit) + .cmd = do_smhexit, + BAREBOX_CMD_DESC("end the semihosting session") + BAREBOX_CMD_OPTS("[-pf] [STATUS]") + BAREBOX_CMD_GROUP(CMD_GRP_MISC) + BAREBOX_CMD_HELP(cmd_smhexit_help) + BAREBOX_CMD_COMPLETE(empty_complete) +BAREBOX_CMD_END diff --git a/drivers/firmware/semihosting.c b/drivers/firmware/semihosting.c index 9663959aa49f..9b665dfce07b 100644 --- a/drivers/firmware/semihosting.c +++ b/drivers/firmware/semihosting.c @@ -37,12 +37,40 @@ enum { SEMIHOSTING_SYS_ERRNO = 0x13, /* SYS_GET_CMDLINE is not implemented */ /* SYS_HEAPINFO is not implemented */ - /* angel_SWIreason_ReportException is not implemented */ SEMIHOSTING_SYS_SYSTEM = 0x12, + SEMIHOSTING_SYS_EXIT = 0x18, + SEMIHOSTING_SYS_EXIT_EXTENDED = 0x20, }; +/* the reason code of angel_SWIreason_ReportException we report */ +#define ADP_STOPPED_APPLICATION_EXIT 0x20026 + long semihosting_trap(ulong sysnum, void *addr); +/** + * semihosting_exit - end the semihosting session + * @status: exit status to report to the host + * @extended: use SYS_EXIT_EXTENDED instead of the plain SYS_EXIT + * + * Tell the debugger or the emulator on the other end that barebox is done + * and hand it @status. Does not return. + */ +void __noreturn semihosting_exit(int status, bool extended) +{ + ulong block[2] = { ADP_STOPPED_APPLICATION_EXIT, status }; + void *arg = block; + + if (!extended && !IS_ENABLED(CONFIG_64BIT)) + arg = (void *)ADP_STOPPED_APPLICATION_EXIT; + + semihosting_trap(extended ? SEMIHOSTING_SYS_EXIT_EXTENDED + : SEMIHOSTING_SYS_EXIT, arg); + + /* Unreachable, above command would hang if unsupported */ + BUG(); +} +EXPORT_SYMBOL(semihosting_exit); + static long semihosting_call(ulong sysnum, void *addr) { long ret = semihosting_trap(sysnum, addr); diff --git a/include/asm-generic/semihosting.h b/include/asm-generic/semihosting.h index 67031c331b97..2f745ac8042c 100644 --- a/include/asm-generic/semihosting.h +++ b/include/asm-generic/semihosting.h @@ -3,6 +3,7 @@ #ifndef __ASM_GENERIC_SEMIHOSTING_H #define __ASM_GENERIC_SEMIHOSTING_H +#include <linux/compiler.h> #include <linux/types.h> int semihosting_open(const char *fname, int flags); @@ -19,5 +20,6 @@ int semihosting_remove(const char *fname); int semihosting_rename(const char *fname1, const char *fname2); int semihosting_errno(void); int semihosting_system(const char *command); +void __noreturn semihosting_exit(int status, bool extended); #endif -- 2.47.3
