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


Reply via email to