diff mbox series

tools/nolibc: Add support for SPARC

Message ID 20250316-nolibc-sparc-v1-1-2e97022d5e2c@weissschuh.net (mailing list archive)
State New
Headers show
Series tools/nolibc: Add support for SPARC | expand

Commit Message

Thomas Weißschuh March 16, 2025, 1:55 p.m. UTC
Add support for 32bit and 64bit SPARC to nolibc.

Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
---
This is only tested on QEMU.
Any tests on real hardware would be very welcome.
---
 tools/include/nolibc/arch-sparc.h           | 191 ++++++++++++++++++++++++++++
 tools/include/nolibc/arch.h                 |   2 +
 tools/testing/selftests/nolibc/Makefile     |  11 ++
 tools/testing/selftests/nolibc/run-tests.sh |   2 +
 4 files changed, 206 insertions(+)


---
base-commit: bceb73904c855c78402dca94c82915f078f259dd
change-id: 20250226-nolibc-sparc-abf4775dc813

Best regards,

Comments

Willy Tarreau March 17, 2025, 7:37 a.m. UTC | #1
On Sun, Mar 16, 2025 at 02:55:02PM +0100, Thomas Weißschuh wrote:
> Add support for 32bit and 64bit SPARC to nolibc.

Oh nice!

> Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
> ---
> This is only tested on QEMU.
> Any tests on real hardware would be very welcome.

I still have a working U60 here, but under solaris. Such machines are
not trivial to boot on alternate OSes (and when you find a working
image usually it's based on an old kernel). But I've run it under
Linux 20 years ago, so I know it was supported. I may give it a try
when I find a moment, but let's not wait for this!

A few comments below:

> diff --git a/tools/include/nolibc/arch-sparc.h b/tools/include/nolibc/arch-sparc.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..cb5543eca87bb4d52cfba4c0668e35cbbf6dd124
> --- /dev/null
> +++ b/tools/include/nolibc/arch-sparc.h
> @@ -0,0 +1,191 @@
> +/* SPDX-License-Identifier: LGPL-2.1 OR MIT */
> +/*
> + * SPARC (32bit and 64bit) specific definitions for NOLIBC
> + * Copyright (C) 2025 Thomas Weißschuh <linux@weissschuh.net>
> + */
> +
> +#ifndef _NOLIBC_ARCH_SPARC_H
> +#define _NOLIBC_ARCH_SPARC_H
> +
> +#include <linux/unistd.h>
> +
> +#include "compiler.h"
> +#include "crt.h"
> +
> +/*
> + * Syscalls for SPARC:
> + *   - registers are native word size
> + *   - syscall number is passed in g1
> + *   - arguments are in o0-o5
> + *   - the system call is performed by calling a trap instruction
> + *   - syscall return value is in 0a
                                     ^^
What is "0a" here ? I suspect a typo and you meant "o0".

> +/* startup code */
> +void __attribute__((weak, noreturn)) __nolibc_entrypoint __no_stack_protector _start(void)
> +{
> +	__asm__ volatile (
> +		/*
> +		 * Save stack pointer to o0, as arg1 of _start_c.
> +		 * Account for window save area and stack bias.
> +		 */
> +#ifdef __arch64__
> +		"add %sp, 128 + 2047, %o0\n"

It's really unclear where this magical 2047 comes from, I think it must
be explained in the comment above so that someone disagreeing with it
later can figure whether it's right or wrong.

> +#else
> +		"add %sp, 64, %o0\n"
> +#endif

Also, I could be wrong, but from my old memories of playing with the
stack on SPARC long ago, I seem to remember that the stack is growing
down. Thus I find these "add" suspicious or at least confusing. You
mention "window save area and stack bias" above, I'm not sure what it
refers to, but if we can safely erase parts of the stack because too
much was preserved, maybe some more explanation about the initial and
target layouts is deserved here.

> +		"b,a _start_c\n"     /* transfer to c runtime */

OK great, the delayed slot is covered! (that type of thing can work
by pure luck in one test and fail in another one depending on what
bytes follow the jump).

Thanks!

Acked-by: Willy Tarreau <w@1wt.eu>

Willy
Thomas Weißschuh March 17, 2025, 5:52 p.m. UTC | #2
On 2025-03-17 08:37:46+0100, Willy Tarreau wrote:
> On Sun, Mar 16, 2025 at 02:55:02PM +0100, Thomas Weißschuh wrote:
> > Add support for 32bit and 64bit SPARC to nolibc.
> 
> Oh nice!
> 
> > Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
> > ---
> > This is only tested on QEMU.
> > Any tests on real hardware would be very welcome.
> 
> I still have a working U60 here, but under solaris. Such machines are
> not trivial to boot on alternate OSes (and when you find a working
> image usually it's based on an old kernel).

An old kernel should be perfectly fine, no?

> But I've run it under
> Linux 20 years ago, so I know it was supported. I may give it a try
> when I find a moment, but let's not wait for this!

Thanks!

> A few comments below:
> 
> > diff --git a/tools/include/nolibc/arch-sparc.h b/tools/include/nolibc/arch-sparc.h
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..cb5543eca87bb4d52cfba4c0668e35cbbf6dd124
> > --- /dev/null
> > +++ b/tools/include/nolibc/arch-sparc.h
> > @@ -0,0 +1,191 @@
> > +/* SPDX-License-Identifier: LGPL-2.1 OR MIT */
> > +/*
> > + * SPARC (32bit and 64bit) specific definitions for NOLIBC
> > + * Copyright (C) 2025 Thomas Weißschuh <linux@weissschuh.net>
> > + */
> > +
> > +#ifndef _NOLIBC_ARCH_SPARC_H
> > +#define _NOLIBC_ARCH_SPARC_H
> > +
> > +#include <linux/unistd.h>
> > +
> > +#include "compiler.h"
> > +#include "crt.h"
> > +
> > +/*
> > + * Syscalls for SPARC:
> > + *   - registers are native word size
> > + *   - syscall number is passed in g1
> > + *   - arguments are in o0-o5
> > + *   - the system call is performed by calling a trap instruction
> > + *   - syscall return value is in 0a
>                                      ^^
> What is "0a" here ? I suspect a typo and you meant "o0".

Correct, will fix.

> > +/* startup code */
> > +void __attribute__((weak, noreturn)) __nolibc_entrypoint __no_stack_protector _start(void)
> > +{
> > +	__asm__ volatile (
> > +		/*
> > +		 * Save stack pointer to o0, as arg1 of _start_c.
> > +		 * Account for window save area and stack bias.
> > +		 */
> > +#ifdef __arch64__
> > +		"add %sp, 128 + 2047, %o0\n"
> 
> It's really unclear where this magical 2047 comes from, I think it must
> be explained in the comment above so that someone disagreeing with it
> later can figure whether it's right or wrong.

128 is the context window and 2047 is the stack bias.
I'll try to make it clearer.

> 
> > +#else
> > +		"add %sp, 64, %o0\n"
> > +#endif
> 
> Also, I could be wrong, but from my old memories of playing with the
> stack on SPARC long ago, I seem to remember that the stack is growing
> down. Thus I find these "add" suspicious or at least confusing. You
> mention "window save area and stack bias" above, I'm not sure what it
> refers to, but if we can safely erase parts of the stack because too
> much was preserved, maybe some more explanation about the initial and
> target layouts is deserved here.

There is a graphic in the psABI [0] under "Process Stack and Registers".
I'll write something based on that.

> > +		"b,a _start_c\n"     /* transfer to c runtime */
> 
> OK great, the delayed slot is covered! (that type of thing can work
> by pure luck in one test and fail in another one depending on what
> bytes follow the jump).

Yeah, it brings memories to the work on MIPS support.

> Thanks!
> 
> Acked-by: Willy Tarreau <w@1wt.eu>

Thanks!

[0] https://uclibc.org/docs/psABI-sparc.pdf
Willy Tarreau March 17, 2025, 6:14 p.m. UTC | #3
On Mon, Mar 17, 2025 at 06:52:57PM +0100, Thomas Weißschuh wrote:
> On 2025-03-17 08:37:46+0100, Willy Tarreau wrote:
> > On Sun, Mar 16, 2025 at 02:55:02PM +0100, Thomas Weißschuh wrote:
> > > Add support for 32bit and 64bit SPARC to nolibc.
> > 
> > Oh nice!
> > 
> > > Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
> > > ---
> > > This is only tested on QEMU.
> > > Any tests on real hardware would be very welcome.
> > 
> > I still have a working U60 here, but under solaris. Such machines are
> > not trivial to boot on alternate OSes (and when you find a working
> > image usually it's based on an old kernel).
> 
> An old kernel should be perfectly fine, no?

I don't think so (it depends how old in fact), keep in mind that
we've removed support for a number of legacy syscalls.

> > > +/* startup code */
> > > +void __attribute__((weak, noreturn)) __nolibc_entrypoint __no_stack_protector _start(void)
> > > +{
> > > +	__asm__ volatile (
> > > +		/*
> > > +		 * Save stack pointer to o0, as arg1 of _start_c.
> > > +		 * Account for window save area and stack bias.
> > > +		 */
> > > +#ifdef __arch64__
> > > +		"add %sp, 128 + 2047, %o0\n"
> > 
> > It's really unclear where this magical 2047 comes from, I think it must
> > be explained in the comment above so that someone disagreeing with it
> > later can figure whether it's right or wrong.
> 
> 128 is the context window and 2047 is the stack bias.
> I'll try to make it clearer.

OK thanks, but that remains quite strange to me. How can we end up
here with such an unaligned stack ? At the very minimum I'd expect
all offsets to be multiple of 8.

> > Also, I could be wrong, but from my old memories of playing with the
> > stack on SPARC long ago, I seem to remember that the stack is growing
> > down. Thus I find these "add" suspicious or at least confusing. You
> > mention "window save area and stack bias" above, I'm not sure what it
> > refers to, but if we can safely erase parts of the stack because too
> > much was preserved, maybe some more explanation about the initial and
> > target layouts is deserved here.
> 
> There is a graphic in the psABI [0] under "Process Stack and Registers".

Thanks for the link! Are you sure you can get rid of the window save
area ? I'm seeing that apparently it's used with save / restore, which
*if I remember well (30 years ago)* were used along with register bank
switches. In 3-12 it's written:

  Some registers have assigned roles.
    %sp or %o6 The stack pointer holds the limit of the current stack frame,
    %which is the address of the stacks bottommost, valid word. Stack contents
    %below the stack pointer are undened. At all times the stack pointer must
    %point to a doubleword aligned, 16- word window save area.

> I'll write something based on that.

Thanks!

> > > +		"b,a _start_c\n"     /* transfer to c runtime */
> > 
> > OK great, the delayed slot is covered! (that type of thing can work
> > by pure luck in one test and fail in another one depending on what
> > bytes follow the jump).
> 
> Yeah, it brings memories to the work on MIPS support.

absolutely!

Thanks,
Willy
Chris Torek March 18, 2025, 1:57 a.m. UTC | #4
On Mon, Mar 17, 2025 at 11:38 AM Willy Tarreau <w@1wt.eu> wrote:
> OK thanks, but that remains quite strange to me. How can we end up
> here with such an unaligned stack ? At the very minimum I'd expect
> all offsets to be multiple of 8.

It's a peculiar feature of the version 9 SPARC architecture and runtime.
This also ties into your window save area question.  Let's start with these:

 * There are 16 save-able registers in a window.
 * Before V9, registers were 32 bits wide.
 * V9 and later, registers are 64 bits wide.
 * Each stack frame must provide an area for register data.

Now 32 bits = 4 bytes, times 16 regs = 64 bytes. So for V8 and lower, the
register save area is  [%sp+0] through [%sp+63] inclusive.

Now V9 comes along and we need 128 bytes. But we're going to
run old V8 code in compatibility mode! How will we tell that some
function f() is running in V8 mode instead of V9 mode? [footnote]

Someone decided that the way to tell would be to use a deliberate
weird alignment of the stack pointer. If the stack pointer was 7 mod 8,
then we're in 64 bit V9 mode and [%sp+2047+0] through
[%sp+2047+127] inclusive are the register save area. If not, it
must be 0 mod 8 and we're in V8 mode and things are as before.

Why 2047? Well, by observation, it's more common to need negative
offsets from the stack pointer (for a large stack-area array for instance)
than it is to need positive ones (register window save area and
overflow function argument area beyond that). But the instruction
set is more or less symmetric, with a 13-bit immediate constant
offset of -4096 to +4095.  Solution: add some offset to the stack
pointer so that function-stack memory is [%sp-4096] through [%sp+2046],
a 6 kilobyte range instead of a 4k one.

The stack offset therefore helps solve both problems: the offset
indicates whether to use V8 or V9 register dump conventions
and, at the same time, increases the amount of easily-accessed
stack memory.

[footnote] This provides the ability to dynamically link V8 and V9
code together.  As far as I know this was never used, so that a per
process mode bit suffices just as well. Still, the offset went in.

Chris
diff mbox series

Patch

diff --git a/tools/include/nolibc/arch-sparc.h b/tools/include/nolibc/arch-sparc.h
new file mode 100644
index 0000000000000000000000000000000000000000..cb5543eca87bb4d52cfba4c0668e35cbbf6dd124
--- /dev/null
+++ b/tools/include/nolibc/arch-sparc.h
@@ -0,0 +1,191 @@ 
+/* SPDX-License-Identifier: LGPL-2.1 OR MIT */
+/*
+ * SPARC (32bit and 64bit) specific definitions for NOLIBC
+ * Copyright (C) 2025 Thomas Weißschuh <linux@weissschuh.net>
+ */
+
+#ifndef _NOLIBC_ARCH_SPARC_H
+#define _NOLIBC_ARCH_SPARC_H
+
+#include <linux/unistd.h>
+
+#include "compiler.h"
+#include "crt.h"
+
+/*
+ * Syscalls for SPARC:
+ *   - registers are native word size
+ *   - syscall number is passed in g1
+ *   - arguments are in o0-o5
+ *   - the system call is performed by calling a trap instruction
+ *   - syscall return value is in 0a
+ *   - syscall error flag is in the carry bit of the processor status register
+ */
+
+#ifdef __arch64__
+
+#define _NOLIBC_SYSCALL "t	0x6d\n"                                       \
+			"bcs,a	%%xcc, 1f\n"                                  \
+			"sub	%%g0, %%o0, %%o0\n"                           \
+			"1:\n"
+
+#else
+
+#define _NOLIBC_SYSCALL "t	0x10\n"                                       \
+			"bcs,a	1f\n"                                         \
+			"sub	%%g0, %%o0, %%o0\n"                           \
+			"1:\n"
+
+#endif /* __arch64__ */
+
+#define my_syscall0(num)                                                      \
+({                                                                            \
+	register long _num  __asm__ ("g1") = (num);                           \
+	register long _arg1 __asm__ ("o0");                                   \
+									      \
+	__asm__ volatile (                                                    \
+		_NOLIBC_SYSCALL                                               \
+		: "+r"(_arg1)                                                 \
+		: "r"(_num)                                                   \
+		: "memory", "cc"                                              \
+	);                                                                    \
+	_arg1;                                                                \
+})
+
+#define my_syscall1(num, arg1)                                                \
+({                                                                            \
+	register long _num  __asm__ ("g1") = (num);                           \
+	register long _arg1 __asm__ ("o0") = (long)(arg1);                    \
+									      \
+	__asm__ volatile (                                                    \
+		_NOLIBC_SYSCALL                                               \
+		: "+r"(_arg1)                                                 \
+		: "r"(_num)                                                   \
+		: "memory", "cc"                                              \
+	);                                                                    \
+	_arg1;                                                                \
+})
+
+#define my_syscall2(num, arg1, arg2)                                          \
+({                                                                            \
+	register long _num  __asm__ ("g1") = (num);                           \
+	register long _arg1 __asm__ ("o0") = (long)(arg1);                    \
+	register long _arg2 __asm__ ("o1") = (long)(arg2);                    \
+									      \
+	__asm__ volatile (                                                    \
+		_NOLIBC_SYSCALL                                               \
+		: "+r"(_arg1)                                                 \
+		: "r"(_arg2), "r"(_num)                                       \
+		: "memory", "cc"                                              \
+	);                                                                    \
+	_arg1;                                                                \
+})
+
+#define my_syscall3(num, arg1, arg2, arg3)                                    \
+({                                                                            \
+	register long _num  __asm__ ("g1") = (num);                           \
+	register long _arg1 __asm__ ("o0") = (long)(arg1);                    \
+	register long _arg2 __asm__ ("o1") = (long)(arg2);                    \
+	register long _arg3 __asm__ ("o2") = (long)(arg3);                    \
+									      \
+	__asm__ volatile (                                                    \
+		_NOLIBC_SYSCALL                                               \
+		: "+r"(_arg1)                                                 \
+		: "r"(_arg2), "r"(_arg3), "r"(_num)                           \
+		: "memory", "cc"                                              \
+	);                                                                    \
+	_arg1;                                                                \
+})
+
+#define my_syscall4(num, arg1, arg2, arg3, arg4)                              \
+({                                                                            \
+	register long _num  __asm__ ("g1") = (num);                           \
+	register long _arg1 __asm__ ("o0") = (long)(arg1);                    \
+	register long _arg2 __asm__ ("o1") = (long)(arg2);                    \
+	register long _arg3 __asm__ ("o2") = (long)(arg3);                    \
+	register long _arg4 __asm__ ("o3") = (long)(arg4);                    \
+									      \
+	__asm__ volatile (                                                    \
+		_NOLIBC_SYSCALL                                               \
+		: "+r"(_arg1)                                                 \
+		: "r"(_arg2), "r"(_arg3), "r"(_arg4), "r"(_num)               \
+		: "memory", "cc"                                              \
+	);                                                                    \
+	_arg1;                                                                \
+})
+
+#define my_syscall5(num, arg1, arg2, arg3, arg4, arg5)                        \
+({                                                                            \
+	register long _num  __asm__ ("g1") = (num);                           \
+	register long _arg1 __asm__ ("o0") = (long)(arg1);                    \
+	register long _arg2 __asm__ ("o1") = (long)(arg2);                    \
+	register long _arg3 __asm__ ("o2") = (long)(arg3);                    \
+	register long _arg4 __asm__ ("o3") = (long)(arg4);                    \
+	register long _arg5 __asm__ ("o4") = (long)(arg5);                    \
+									      \
+	__asm__ volatile (                                                    \
+		_NOLIBC_SYSCALL                                               \
+		: "+r"(_arg1)                                                 \
+		: "r"(_arg2), "r"(_arg3), "r"(_arg4), "r"(_arg5), "r"(_num)   \
+		: "memory", "cc"                                              \
+	);                                                                    \
+	_arg1;                                                                \
+})
+
+#define my_syscall6(num, arg1, arg2, arg3, arg4, arg5, arg6)                  \
+({                                                                            \
+	register long _num  __asm__ ("g1") = (num);                           \
+	register long _arg1 __asm__ ("o0") = (long)(arg1);                    \
+	register long _arg2 __asm__ ("o1") = (long)(arg2);                    \
+	register long _arg3 __asm__ ("o2") = (long)(arg3);                    \
+	register long _arg4 __asm__ ("o3") = (long)(arg4);                    \
+	register long _arg5 __asm__ ("o4") = (long)(arg5);                    \
+	register long _arg6 __asm__ ("o5") = (long)(arg6);                    \
+									      \
+	__asm__ volatile (                                                    \
+		_NOLIBC_SYSCALL                                               \
+		: "+r"(_arg1)                                                 \
+		: "r"(_arg2), "r"(_arg3), "r"(_arg4), "r"(_arg5), "r"(_arg6), \
+		  "r"(_num)                                                   \
+		: "memory", "cc"                                              \
+	);                                                                    \
+	_arg1;                                                                \
+})
+
+/* startup code */
+void __attribute__((weak, noreturn)) __nolibc_entrypoint __no_stack_protector _start(void)
+{
+	__asm__ volatile (
+		/*
+		 * Save stack pointer to o0, as arg1 of _start_c.
+		 * Account for window save area and stack bias.
+		 */
+#ifdef __arch64__
+		"add %sp, 128 + 2047, %o0\n"
+#else
+		"add %sp, 64, %o0\n"
+#endif
+		"b,a _start_c\n"     /* transfer to c runtime */
+	);
+	__nolibc_entrypoint_epilogue();
+}
+
+static pid_t getpid(void);
+
+static __attribute__((unused))
+pid_t sys_fork(void)
+{
+	pid_t parent, ret;
+
+	parent = getpid();
+	ret = my_syscall0(__NR_fork);
+
+	/* The syscall returns the parent pid in the child instead of 0 */
+	if (ret == parent)
+		return 0;
+	else
+		return ret;
+}
+#define sys_fork sys_fork
+
+#endif /* _NOLIBC_ARCH_SPARC_H */
diff --git a/tools/include/nolibc/arch.h b/tools/include/nolibc/arch.h
index 8a2c143c0fba288147e5a7bf9db38ffb08367616..b8c1da9a88d1593d5a97f60909ede5d0c17699eb 100644
--- a/tools/include/nolibc/arch.h
+++ b/tools/include/nolibc/arch.h
@@ -33,6 +33,8 @@ 
 #include "arch-s390.h"
 #elif defined(__loongarch__)
 #include "arch-loongarch.h"
+#elif defined(__sparc__)
+#include "arch-sparc.h"
 #else
 #error Unsupported Architecture
 #endif
diff --git a/tools/testing/selftests/nolibc/Makefile b/tools/testing/selftests/nolibc/Makefile
index 58bcbbd029bc3ad9ccac968191b703ccf5df0717..5060e189dc842d761dd13d70b8afdb2ff3390bc5 100644
--- a/tools/testing/selftests/nolibc/Makefile
+++ b/tools/testing/selftests/nolibc/Makefile
@@ -56,6 +56,8 @@  ARCH_mips32be    = mips
 ARCH_riscv32     = riscv
 ARCH_riscv64     = riscv
 ARCH_s390x       = s390
+ARCH_sparc32     = sparc
+ARCH_sparc64     = sparc
 ARCH            := $(or $(ARCH_$(XARCH)),$(XARCH))
 
 # kernel image names by architecture
@@ -76,6 +78,8 @@  IMAGE_riscv64    = arch/riscv/boot/Image
 IMAGE_s390x      = arch/s390/boot/bzImage
 IMAGE_s390       = arch/s390/boot/bzImage
 IMAGE_loongarch  = arch/loongarch/boot/vmlinuz.efi
+IMAGE_sparc32    = arch/sparc/boot/image
+IMAGE_sparc64    = arch/sparc/boot/image
 IMAGE            = $(objtree)/$(IMAGE_$(XARCH))
 IMAGE_NAME       = $(notdir $(IMAGE))
 
@@ -97,6 +101,8 @@  DEFCONFIG_riscv64    = defconfig
 DEFCONFIG_s390x      = defconfig
 DEFCONFIG_s390       = defconfig compat.config
 DEFCONFIG_loongarch  = defconfig
+DEFCONFIG_sparc32    = sparc32_defconfig
+DEFCONFIG_sparc64    = sparc64_defconfig
 DEFCONFIG            = $(DEFCONFIG_$(XARCH))
 
 EXTRACONFIG           = $(EXTRACONFIG_$(XARCH))
@@ -122,6 +128,8 @@  QEMU_ARCH_riscv64    = riscv64
 QEMU_ARCH_s390x      = s390x
 QEMU_ARCH_s390       = s390x
 QEMU_ARCH_loongarch  = loongarch64
+QEMU_ARCH_sparc32    = sparc
+QEMU_ARCH_sparc64    = sparc64
 QEMU_ARCH            = $(QEMU_ARCH_$(XARCH))
 
 QEMU_ARCH_USER_ppc64le = ppc64le
@@ -152,6 +160,8 @@  QEMU_ARGS_riscv64    = -M virt -append "console=ttyS0 panic=-1 $(TEST:%=NOLIBC_T
 QEMU_ARGS_s390x      = -M s390-ccw-virtio -append "console=ttyS0 panic=-1 $(TEST:%=NOLIBC_TEST=%)"
 QEMU_ARGS_s390       = -M s390-ccw-virtio -append "console=ttyS0 panic=-1 $(TEST:%=NOLIBC_TEST=%)"
 QEMU_ARGS_loongarch  = -M virt -append "console=ttyS0,115200 panic=-1 $(TEST:%=NOLIBC_TEST=%)"
+QEMU_ARGS_sparc32    = -M SS-5 -m 256M -append "console=ttyS0,115200 panic=-1 $(TEST:%=NOLIBC_TEST=%)"
+QEMU_ARGS_sparc64    = -M sun4u -append "console=ttyS0,115200 panic=-1 $(TEST:%=NOLIBC_TEST=%)"
 QEMU_ARGS            = -m 1G $(QEMU_ARGS_$(XARCH)) $(QEMU_ARGS_BIOS) $(QEMU_ARGS_EXTRA)
 
 # OUTPUT is only set when run from the main makefile, otherwise
@@ -174,6 +184,7 @@  CFLAGS_s390x = -m64
 CFLAGS_s390 = -m31
 CFLAGS_mips32le = -EL -mabi=32 -fPIC
 CFLAGS_mips32be = -EB -mabi=32
+CFLAGS_sparc32 = $(call cc-option,-m32)
 CFLAGS_STACKPROTECTOR ?= $(call cc-option,-mstack-protector-guard=global $(call cc-option,-fstack-protector-all))
 CFLAGS  ?= -Os -fno-ident -fno-asynchronous-unwind-tables -std=c89 -W -Wall -Wextra \
 		$(call cc-option,-fno-stack-protector) $(call cc-option,-Wmissing-prototypes) \
diff --git a/tools/testing/selftests/nolibc/run-tests.sh b/tools/testing/selftests/nolibc/run-tests.sh
index 0299a0912d4049dd12217f9835b81d231e1d2bfd..040956a9f5b8dda3e78abc0d4b6073f4fcd9e3ee 100755
--- a/tools/testing/selftests/nolibc/run-tests.sh
+++ b/tools/testing/selftests/nolibc/run-tests.sh
@@ -25,6 +25,7 @@  all_archs=(
 	riscv32 riscv64
 	s390x s390
 	loongarch
+	sparc32 sparc64
 )
 archs="${all_archs[@]}"
 
@@ -111,6 +112,7 @@  crosstool_arch() {
 	loongarch) echo loongarch64;;
 	mips*) echo mips;;
 	s390*) echo s390;;
+	sparc*) echo sparc64;;
 	*) echo "$1";;
 	esac
 }