Commit abc38ba36c for qemu.org
commit abc38ba36c946f846296061731070abbb223e518
Author: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Date: Wed Oct 28 20:25:32 2026 +0100
tests/tcg/mips64el: Add bare-metal harness for R5900 system tests
There is no libc for the R5900, so its tests can not run in user
mode [*]. Provide what is needed to run them bare-metal on the
'none' machine: start-up code, linker script, minimal assert.h,
inttypes.h, stdio.h and string.h, and a gdb script.
A first generic loader loads the ELF at its physical addresses, a
second one sets the PC to the KSEG0 entry point, sign-extended as the
CPU is 64-bit. Tests end in test_done(), where the gdb script checks
the status and kills QEMU. Tests use 32-bit pointers, so gdb must
send 64-bit addresses to the remote stub.
No test is registered yet.
[*] https://lore.kernel.org/qemu-devel/alpine.DEB.2.21.2103172243160.21463@angie.orcam.me.uk/
Inspired-by: Maciej W. Rozycki <macro@orcam.me.uk>
Signed-off-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
Message-ID: <20261028192538.31600-5-philmd@oss.qualcomm.com>
diff --git a/tests/tcg/mips64el/meson.build b/tests/tcg/mips64el/meson.build
index 606f300804..a68ceab3b4 100644
--- a/tests/tcg/mips64el/meson.build
+++ b/tests/tcg/mips64el/meson.build
@@ -1,3 +1,5 @@
+subdir('system')
+
if 'qemu-mips64el' in emulators
tcg_tests += {
'mips64el-linux-user': {
diff --git a/tests/tcg/mips64el/system/boot.S b/tests/tcg/mips64el/system/boot.S
new file mode 100644
index 0000000000..ce8f88c06d
--- /dev/null
+++ b/tests/tcg/mips64el/system/boot.S
@@ -0,0 +1,26 @@
+/*
+ * Minimal start-up code for bare-metal R5900 tests
+ *
+ * The CPU starts in kernel mode at _start (PC set by the generic
+ * loader device). Set up a stack, run main() and report its return
+ * value to test_done(), where the gdb script is waiting.
+ *
+ * Author:
+ * Philippe Mathieu-Daudé
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+ .set noreorder
+ .section .text.boot, "ax"
+ .globl _start
+ .ent _start
+_start:
+ la $sp, _stack_top
+ jal main
+ nop
+ jal test_done
+ move $a0, $v0
+1: b 1b
+ nop
+ .end _start
diff --git a/tests/tcg/mips64el/system/harness.c b/tests/tcg/mips64el/system/harness.c
new file mode 100644
index 0000000000..a0f345449e
--- /dev/null
+++ b/tests/tcg/mips64el/system/harness.c
@@ -0,0 +1,69 @@
+/*
+ * Bare-metal harness for R5900 tests
+ *
+ * There is no libc nor any way to exit QEMU on the 'none' machine.
+ * Tests end in test_done(), where the gdb script reads the status.
+ *
+ * Author:
+ * Philippe Mathieu-Daudé
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+int test_failed_line;
+const char *test_failed_message;
+
+void __attribute__((noinline, noreturn)) test_done(int status)
+{
+ for (;;) {
+ /* The gdb script stops here */
+ }
+}
+
+void test_assert_fail(int line)
+{
+ test_failed_line = line;
+ test_done(1);
+}
+
+void test_message(const char *msg)
+{
+ test_failed_message = msg;
+}
+
+void *memset(void *s, int c, size_t n)
+{
+ unsigned char *p = s;
+
+ while (n--) {
+ *p++ = c;
+ }
+ return s;
+}
+
+void *memcpy(void *dest, const void *src, size_t n)
+{
+ unsigned char *d = dest;
+ const unsigned char *s = src;
+
+ while (n--) {
+ *d++ = *s++;
+ }
+ return dest;
+}
+
+int memcmp(const void *s1, const void *s2, size_t n)
+{
+ const unsigned char *a = s1, *b = s2;
+
+ for (; n; n--, a++, b++) {
+ if (*a != *b) {
+ return *a - *b;
+ }
+ }
+ return 0;
+}
diff --git a/tests/tcg/mips64el/system/include/assert.h b/tests/tcg/mips64el/system/include/assert.h
new file mode 100644
index 0000000000..d2286ca004
--- /dev/null
+++ b/tests/tcg/mips64el/system/include/assert.h
@@ -0,0 +1,18 @@
+/*
+ * Freestanding assert() for bare-metal R5900 tests
+ *
+ * Author:
+ * Philippe Mathieu-Daudé
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef R5900_SYSTEM_ASSERT_H
+#define R5900_SYSTEM_ASSERT_H
+
+void test_assert_fail(int line) __attribute__((noreturn));
+void test_done(int status) __attribute__((noreturn));
+
+#define assert(cond) ((cond) ? (void)0 : test_assert_fail(__LINE__))
+
+#endif
diff --git a/tests/tcg/mips64el/system/include/inttypes.h b/tests/tcg/mips64el/system/include/inttypes.h
new file mode 100644
index 0000000000..d44124d543
--- /dev/null
+++ b/tests/tcg/mips64el/system/include/inttypes.h
@@ -0,0 +1,10 @@
+/*
+ * Freestanding <inttypes.h> for bare-metal R5900 tests
+ *
+ * Author:
+ * Philippe Mathieu-Daudé
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <stdint.h>
diff --git a/tests/tcg/mips64el/system/include/stdio.h b/tests/tcg/mips64el/system/include/stdio.h
new file mode 100644
index 0000000000..cbb32a1e9d
--- /dev/null
+++ b/tests/tcg/mips64el/system/include/stdio.h
@@ -0,0 +1,20 @@
+/*
+ * Freestanding <stdio.h> for bare-metal R5900 tests
+ *
+ * There is no console: fprintf() only records its format string,
+ * which the gdb script reports when the test fails.
+ *
+ * Author:
+ * Philippe Mathieu-Daudé
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef R5900_SYSTEM_STDIO_H
+#define R5900_SYSTEM_STDIO_H
+
+void test_message(const char *msg);
+
+#define fprintf(stream, fmt, ...) test_message(fmt)
+
+#endif
diff --git a/tests/tcg/mips64el/system/include/string.h b/tests/tcg/mips64el/system/include/string.h
new file mode 100644
index 0000000000..14bed074f7
--- /dev/null
+++ b/tests/tcg/mips64el/system/include/string.h
@@ -0,0 +1,19 @@
+/*
+ * Freestanding <string.h> for bare-metal R5900 tests
+ *
+ * Author:
+ * Philippe Mathieu-Daudé
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef R5900_SYSTEM_STRING_H
+#define R5900_SYSTEM_STRING_H
+
+#include <stddef.h>
+
+void *memset(void *s, int c, size_t n);
+void *memcpy(void *dest, const void *src, size_t n);
+int memcmp(const void *s1, const void *s2, size_t n);
+
+#endif
diff --git a/tests/tcg/mips64el/system/kernel.ld b/tests/tcg/mips64el/system/kernel.ld
new file mode 100644
index 0000000000..c6382f8251
--- /dev/null
+++ b/tests/tcg/mips64el/system/kernel.ld
@@ -0,0 +1,46 @@
+/*
+ * Linker script for bare-metal R5900 tests
+ *
+ * Code runs from KSEG0 (unmapped, cached) and is loaded at the
+ * corresponding physical address, in RAM mapped at zero.
+ *
+ * Author:
+ * Philippe Mathieu-Daudé
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+ENTRY(_start)
+
+KSEG0 = 0x80000000;
+
+SECTIONS
+{
+ . = KSEG0 + 0x100000;
+ .text : AT(ADDR(.text) - KSEG0) {
+ *(.text.boot)
+ *(.text*)
+ }
+ .rodata : AT(ADDR(.rodata) - KSEG0) {
+ *(.rodata*)
+ }
+ .data : AT(ADDR(.data) - KSEG0) {
+ *(.data*)
+ *(.sdata*)
+ }
+ .bss : AT(ADDR(.bss) - KSEG0) {
+ *(.sbss*)
+ *(.bss*)
+ *(COMMON)
+ }
+ . = ALIGN(16) + 0x10000;
+ _stack_top = .;
+
+ /DISCARD/ : {
+ *(.MIPS.abiflags)
+ *(.reginfo)
+ *(.pdr)
+ *(.gnu.attributes)
+ *(.comment)
+ }
+}
diff --git a/tests/tcg/mips64el/system/meson.build b/tests/tcg/mips64el/system/meson.build
new file mode 100644
index 0000000000..265a2eca4f
--- /dev/null
+++ b/tests/tcg/mips64el/system/meson.build
@@ -0,0 +1,33 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+tests = []
+
+cflags = ['-nostdlib', '-ffreestanding',
+ '-march=r5900', '-msoft-float',
+ '-mno-abicalls', '-fno-pic', '-G0',
+ '-I', meson.current_source_dir() / 'include',
+ files('boot.S')[0], files('harness.c')[0],
+ '-Wl,-T', files('kernel.ld')[0],
+ '-Wl,--no-warn-rwx-segments', '-Wl,--build-id=none']
+
+qemu_args = ['-M', 'none', '-cpu', 'R5900', '-m', '16M',
+ '-display', 'none',
+ '-device', 'loader,file=@BINARY@',
+ '-device', 'loader,addr=0xffffffff80100000,cpu-num=0']
+
+# Pointers are 32-bit (o32, n32), sign-extend them for the 64-bit CPU
+gdb_test = ['--gdb-args=-ex "set remoteaddresssize 64"',
+ '--test', files('test-r5900.py')]
+
+if 'qemu-system-mips64el' in emulators
+ tcg_tests += {
+ 'mips64el-softmmu': {
+ 'cc': 'mips64el-linux-gnuabi64-gcc',
+ 'cc_dockerfile': 'debian-all-test-cross',
+ 'folder': 'mips64el/system',
+ 'gdb_arch': 'mips64el',
+ 'qemu': emulators['qemu-system-mips64el'],
+ 'tests': tests,
+ }
+ }
+endif
diff --git a/tests/tcg/mips64el/system/test-r5900.py b/tests/tcg/mips64el/system/test-r5900.py
new file mode 100644
index 0000000000..2e7ed191b3
--- /dev/null
+++ b/tests/tcg/mips64el/system/test-r5900.py
@@ -0,0 +1,30 @@
+"""Run a bare-metal R5900 test and check its result
+
+The guest runs until test_done(); a non-zero status means the test
+failed: test_failed_line holds the line of a failed assert(), and
+test_failed_message the last message the test tried to print.
+
+SPDX-License-Identifier: GPL-2.0-or-later
+"""
+
+import gdb
+from test_gdbstub import main, report
+
+
+def run_test():
+ gdb.execute("break test_done")
+ gdb.execute("continue")
+
+ status = int(gdb.parse_and_eval("status"))
+ details = ""
+ if status:
+ line = int(gdb.parse_and_eval("test_failed_line"))
+ message = gdb.parse_and_eval("test_failed_message")
+ if line:
+ details = f" (assert failed at line {line})"
+ elif int(message):
+ details = f" ({message.string().strip()})"
+ report(status == 0, "guest test" + details)
+
+
+main(run_test)