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)