Add support for the clone3 system call to the SPARC architectures.
The implementation follows the pattern of the original clone syscall.
However, instead of explicitly calling kernel_clone, the clone3
handler calls the generic sys_clone3 handler in kernel/fork.
In case no stack is provided, the parents stack is reused.
The return call conventions for clone on SPARC are kept for clone3:
Parent --> %o0 == child's pid, %o1 == 0
Child --> %o0 == parent's pid, %o1 == 1
Closes: https://github.com/sparclinux/issues/issues/10
Signed-off-by: Ludwig Rydberg <ludwig.rydberg@gaisler.com>
---
arch/sparc/include/asm/syscalls.h | 1 +
arch/sparc/include/asm/unistd.h | 2 --
arch/sparc/kernel/entry.S | 15 +++++++++++++++
arch/sparc/kernel/kernel.h | 1 +
arch/sparc/kernel/process.c | 25 +++++++++++++++++++++++++
arch/sparc/kernel/process_32.c | 2 +-
arch/sparc/kernel/process_64.c | 2 +-
arch/sparc/kernel/syscalls.S | 6 ++++++
arch/sparc/kernel/syscalls/syscall.tbl | 2 +-
9 files changed, 51 insertions(+), 5 deletions(-)
diff --git a/arch/sparc/include/asm/syscalls.h b/arch/sparc/include/asm/syscalls.h
index 35575fbfb9dc..282e62b66518 100644
--- a/arch/sparc/include/asm/syscalls.h
+++ b/arch/sparc/include/asm/syscalls.h
@@ -7,5 +7,6 @@ struct pt_regs;
asmlinkage long sparc_fork(struct pt_regs *regs);
asmlinkage long sparc_vfork(struct pt_regs *regs);
asmlinkage long sparc_clone(struct pt_regs *regs);
+asmlinkage long sparc_clone3(struct pt_regs *regs);
#endif /* _SPARC64_SYSCALLS_H */
diff --git a/arch/sparc/include/asm/unistd.h b/arch/sparc/include/asm/unistd.h
index 3380411a4537..d6bc76706a7a 100644
--- a/arch/sparc/include/asm/unistd.h
+++ b/arch/sparc/include/asm/unistd.h
@@ -49,8 +49,6 @@
#define __ARCH_WANT_COMPAT_STAT
#endif
-#define __ARCH_BROKEN_SYS_CLONE3
-
#ifdef __32bit_syscall_numbers__
/* Sparc 32-bit only has the "setresuid32", "getresuid32" variants,
* it never had the plain ones and there is no value to adding those
diff --git a/arch/sparc/kernel/entry.S b/arch/sparc/kernel/entry.S
index a3fdee4cd6fa..ea51ef52c952 100644
--- a/arch/sparc/kernel/entry.S
+++ b/arch/sparc/kernel/entry.S
@@ -907,6 +907,21 @@ flush_patch_four:
jmpl %l1 + %lo(sparc_vfork), %g0
add %sp, STACKFRAME_SZ, %o0
+ .globl __sys_clone3, flush_patch_five
+__sys_clone3:
+ mov %o7, %l5
+flush_patch_five:
+ FLUSH_ALL_KERNEL_WINDOWS;
+ ld [%curptr + TI_TASK], %o4
+ rd %psr, %g4
+ WRITE_PAUSE
+ rd %wim, %g5
+ WRITE_PAUSE
+ std %g4, [%o4 + AOFF_task_thread + AOFF_thread_fork_kpsr]
+ add %sp, STACKFRAME_SZ, %o0
+ call sparc_clone3
+ mov %l5, %o7
+
.align 4
linux_sparc_ni_syscall:
sethi %hi(sys_ni_syscall), %l7
diff --git a/arch/sparc/kernel/kernel.h b/arch/sparc/kernel/kernel.h
index 8328a3b78a44..4ee85051521a 100644
--- a/arch/sparc/kernel/kernel.h
+++ b/arch/sparc/kernel/kernel.h
@@ -18,6 +18,7 @@ extern int ncpus_probed;
asmlinkage long sparc_clone(struct pt_regs *regs);
asmlinkage long sparc_fork(struct pt_regs *regs);
asmlinkage long sparc_vfork(struct pt_regs *regs);
+asmlinkage long sparc_clone3(struct pt_regs *regs);
#ifdef CONFIG_SPARC64
/* setup_64.c */
diff --git a/arch/sparc/kernel/process.c b/arch/sparc/kernel/process.c
index 7d69877511fa..b8e23295db69 100644
--- a/arch/sparc/kernel/process.c
+++ b/arch/sparc/kernel/process.c
@@ -12,6 +12,7 @@
#include <linux/sched/task.h>
#include <linux/sched/task_stack.h>
#include <linux/signal.h>
+#include <linux/syscalls.h>
#include "kernel.h"
@@ -118,3 +119,27 @@ asmlinkage long sparc_clone(struct pt_regs *regs)
return ret;
}
+
+asmlinkage long sparc_clone3(struct pt_regs *regs)
+{
+ unsigned long sz;
+ long ret;
+ struct clone_args __user *cl_args;
+
+ synchronize_user_stack();
+
+ cl_args = (struct clone_args __user *)regs->u_regs[UREG_I0];
+ sz = regs->u_regs[UREG_I1];
+
+ ret = sys_clone3(cl_args, sz);
+
+ /* If we get an error and potentially restart the system
+ * call, we're screwed because copy_thread() clobbered
+ * the parent's %o1. So detect that case and restore it
+ * here.
+ */
+ if ((unsigned long)ret >= -ERESTART_RESTARTBLOCK)
+ regs->u_regs[UREG_I1] = sz;
+
+ return ret;
+}
diff --git a/arch/sparc/kernel/process_32.c b/arch/sparc/kernel/process_32.c
index 5a28c0e91bf1..216c07971c81 100644
--- a/arch/sparc/kernel/process_32.c
+++ b/arch/sparc/kernel/process_32.c
@@ -261,11 +261,11 @@ extern void ret_from_kernel_thread(void);
int copy_thread(struct task_struct *p, const struct kernel_clone_args *args)
{
u64 clone_flags = args->flags;
- unsigned long sp = args->stack;
unsigned long tls = args->tls;
struct thread_info *ti = task_thread_info(p);
struct pt_regs *childregs, *regs = current_pt_regs();
char *new_stack;
+ unsigned long sp = args->stack ? args->stack : regs->u_regs[UREG_FP];
#ifndef CONFIG_SMP
if(last_task_used_math == current) {
diff --git a/arch/sparc/kernel/process_64.c b/arch/sparc/kernel/process_64.c
index 25781923788a..885d617ba29d 100644
--- a/arch/sparc/kernel/process_64.c
+++ b/arch/sparc/kernel/process_64.c
@@ -568,13 +568,13 @@ void fault_in_user_windows(struct pt_regs *regs)
int copy_thread(struct task_struct *p, const struct kernel_clone_args *args)
{
u64 clone_flags = args->flags;
- unsigned long sp = args->stack;
unsigned long tls = args->tls;
struct thread_info *t = task_thread_info(p);
struct pt_regs *regs = current_pt_regs();
struct sparc_stackf *parent_sf;
unsigned long child_stack_sz;
char *child_trap_frame;
+ unsigned long sp = args->stack ? args->stack : regs->u_regs[UREG_FP];
/* Calculate offset to stack_frame & pt_regs */
child_stack_sz = (STACKFRAME_SZ + TRACEREG_SZ);
diff --git a/arch/sparc/kernel/syscalls.S b/arch/sparc/kernel/syscalls.S
index 0e8ab0602c36..c8d374a37f98 100644
--- a/arch/sparc/kernel/syscalls.S
+++ b/arch/sparc/kernel/syscalls.S
@@ -103,6 +103,12 @@ sys_clone:
ba,pt %xcc, sparc_clone
add %sp, PTREGS_OFF, %o0
+ .align 32
+__sys_clone3:
+ flushw
+ ba,pt %xcc, sparc_clone3
+ add %sp, PTREGS_OFF, %o0
+
.globl ret_from_fork
ret_from_fork:
/* Clear current_thread_info()->new_child. */
diff --git a/arch/sparc/kernel/syscalls/syscall.tbl b/arch/sparc/kernel/syscalls/syscall.tbl
index 39aa26b6a50b..c0307bb09892 100644
--- a/arch/sparc/kernel/syscalls/syscall.tbl
+++ b/arch/sparc/kernel/syscalls/syscall.tbl
@@ -480,7 +480,7 @@
432 common fsmount sys_fsmount
433 common fspick sys_fspick
434 common pidfd_open sys_pidfd_open
-# 435 reserved for clone3
+435 common clone3 __sys_clone3
436 common close_range sys_close_range
437 common openat2 sys_openat2
438 common pidfd_getfd sys_pidfd_getfd
--
2.35.3
Hi Ludwig,
On Fri, 2026-01-16 at 16:30 +0100, Ludwig Rydberg wrote:
> Add support for the clone3 system call to the SPARC architectures.
>
> The implementation follows the pattern of the original clone syscall.
> However, instead of explicitly calling kernel_clone, the clone3
> handler calls the generic sys_clone3 handler in kernel/fork.
> In case no stack is provided, the parents stack is reused.
>
> The return call conventions for clone on SPARC are kept for clone3:
> Parent --> %o0 == child's pid, %o1 == 0
> Child --> %o0 == parent's pid, %o1 == 1
>
> Closes: https://github.com/sparclinux/issues/issues/10
> Signed-off-by: Ludwig Rydberg <ludwig.rydberg@gaisler.com>
> ---
> arch/sparc/include/asm/syscalls.h | 1 +
> arch/sparc/include/asm/unistd.h | 2 --
> arch/sparc/kernel/entry.S | 15 +++++++++++++++
> arch/sparc/kernel/kernel.h | 1 +
> arch/sparc/kernel/process.c | 25 +++++++++++++++++++++++++
> arch/sparc/kernel/process_32.c | 2 +-
> arch/sparc/kernel/process_64.c | 2 +-
> arch/sparc/kernel/syscalls.S | 6 ++++++
> arch/sparc/kernel/syscalls/syscall.tbl | 2 +-
> 9 files changed, 51 insertions(+), 5 deletions(-)
>
> diff --git a/arch/sparc/include/asm/syscalls.h b/arch/sparc/include/asm/syscalls.h
> index 35575fbfb9dc..282e62b66518 100644
> --- a/arch/sparc/include/asm/syscalls.h
> +++ b/arch/sparc/include/asm/syscalls.h
> @@ -7,5 +7,6 @@ struct pt_regs;
> asmlinkage long sparc_fork(struct pt_regs *regs);
> asmlinkage long sparc_vfork(struct pt_regs *regs);
> asmlinkage long sparc_clone(struct pt_regs *regs);
> +asmlinkage long sparc_clone3(struct pt_regs *regs);
>
> #endif /* _SPARC64_SYSCALLS_H */
> diff --git a/arch/sparc/include/asm/unistd.h b/arch/sparc/include/asm/unistd.h
> index 3380411a4537..d6bc76706a7a 100644
> --- a/arch/sparc/include/asm/unistd.h
> +++ b/arch/sparc/include/asm/unistd.h
> @@ -49,8 +49,6 @@
> #define __ARCH_WANT_COMPAT_STAT
> #endif
>
> -#define __ARCH_BROKEN_SYS_CLONE3
> -
> #ifdef __32bit_syscall_numbers__
> /* Sparc 32-bit only has the "setresuid32", "getresuid32" variants,
> * it never had the plain ones and there is no value to adding those
> diff --git a/arch/sparc/kernel/entry.S b/arch/sparc/kernel/entry.S
> index a3fdee4cd6fa..ea51ef52c952 100644
> --- a/arch/sparc/kernel/entry.S
> +++ b/arch/sparc/kernel/entry.S
> @@ -907,6 +907,21 @@ flush_patch_four:
> jmpl %l1 + %lo(sparc_vfork), %g0
> add %sp, STACKFRAME_SZ, %o0
>
> + .globl __sys_clone3, flush_patch_five
> +__sys_clone3:
> + mov %o7, %l5
> +flush_patch_five:
> + FLUSH_ALL_KERNEL_WINDOWS;
> + ld [%curptr + TI_TASK], %o4
> + rd %psr, %g4
> + WRITE_PAUSE
> + rd %wim, %g5
> + WRITE_PAUSE
> + std %g4, [%o4 + AOFF_task_thread + AOFF_thread_fork_kpsr]
> + add %sp, STACKFRAME_SZ, %o0
> + call sparc_clone3
> + mov %l5, %o7
> +
> .align 4
> linux_sparc_ni_syscall:
> sethi %hi(sys_ni_syscall), %l7
> diff --git a/arch/sparc/kernel/kernel.h b/arch/sparc/kernel/kernel.h
> index 8328a3b78a44..4ee85051521a 100644
> --- a/arch/sparc/kernel/kernel.h
> +++ b/arch/sparc/kernel/kernel.h
> @@ -18,6 +18,7 @@ extern int ncpus_probed;
> asmlinkage long sparc_clone(struct pt_regs *regs);
> asmlinkage long sparc_fork(struct pt_regs *regs);
> asmlinkage long sparc_vfork(struct pt_regs *regs);
> +asmlinkage long sparc_clone3(struct pt_regs *regs);
>
> #ifdef CONFIG_SPARC64
> /* setup_64.c */
> diff --git a/arch/sparc/kernel/process.c b/arch/sparc/kernel/process.c
> index 7d69877511fa..b8e23295db69 100644
> --- a/arch/sparc/kernel/process.c
> +++ b/arch/sparc/kernel/process.c
> @@ -12,6 +12,7 @@
> #include <linux/sched/task.h>
> #include <linux/sched/task_stack.h>
> #include <linux/signal.h>
> +#include <linux/syscalls.h>
>
> #include "kernel.h"
>
> @@ -118,3 +119,27 @@ asmlinkage long sparc_clone(struct pt_regs *regs)
>
> return ret;
> }
> +
> +asmlinkage long sparc_clone3(struct pt_regs *regs)
> +{
> + unsigned long sz;
> + long ret;
> + struct clone_args __user *cl_args;
> +
> + synchronize_user_stack();
> +
> + cl_args = (struct clone_args __user *)regs->u_regs[UREG_I0];
> + sz = regs->u_regs[UREG_I1];
> +
> + ret = sys_clone3(cl_args, sz);
> +
> + /* If we get an error and potentially restart the system
> + * call, we're screwed because copy_thread() clobbered
> + * the parent's %o1. So detect that case and restore it
> + * here.
> + */
> + if ((unsigned long)ret >= -ERESTART_RESTARTBLOCK)
> + regs->u_regs[UREG_I1] = sz;
> +
> + return ret;
> +}
> diff --git a/arch/sparc/kernel/process_32.c b/arch/sparc/kernel/process_32.c
> index 5a28c0e91bf1..216c07971c81 100644
> --- a/arch/sparc/kernel/process_32.c
> +++ b/arch/sparc/kernel/process_32.c
> @@ -261,11 +261,11 @@ extern void ret_from_kernel_thread(void);
> int copy_thread(struct task_struct *p, const struct kernel_clone_args *args)
> {
> u64 clone_flags = args->flags;
> - unsigned long sp = args->stack;
> unsigned long tls = args->tls;
> struct thread_info *ti = task_thread_info(p);
> struct pt_regs *childregs, *regs = current_pt_regs();
> char *new_stack;
> + unsigned long sp = args->stack ? args->stack : regs->u_regs[UREG_FP];
>
> #ifndef CONFIG_SMP
> if(last_task_used_math == current) {
> diff --git a/arch/sparc/kernel/process_64.c b/arch/sparc/kernel/process_64.c
> index 25781923788a..885d617ba29d 100644
> --- a/arch/sparc/kernel/process_64.c
> +++ b/arch/sparc/kernel/process_64.c
> @@ -568,13 +568,13 @@ void fault_in_user_windows(struct pt_regs *regs)
> int copy_thread(struct task_struct *p, const struct kernel_clone_args *args)
> {
> u64 clone_flags = args->flags;
> - unsigned long sp = args->stack;
> unsigned long tls = args->tls;
> struct thread_info *t = task_thread_info(p);
> struct pt_regs *regs = current_pt_regs();
> struct sparc_stackf *parent_sf;
> unsigned long child_stack_sz;
> char *child_trap_frame;
> + unsigned long sp = args->stack ? args->stack : regs->u_regs[UREG_FP];
>
> /* Calculate offset to stack_frame & pt_regs */
> child_stack_sz = (STACKFRAME_SZ + TRACEREG_SZ);
> diff --git a/arch/sparc/kernel/syscalls.S b/arch/sparc/kernel/syscalls.S
> index 0e8ab0602c36..c8d374a37f98 100644
> --- a/arch/sparc/kernel/syscalls.S
> +++ b/arch/sparc/kernel/syscalls.S
> @@ -103,6 +103,12 @@ sys_clone:
> ba,pt %xcc, sparc_clone
> add %sp, PTREGS_OFF, %o0
>
> + .align 32
> +__sys_clone3:
> + flushw
> + ba,pt %xcc, sparc_clone3
> + add %sp, PTREGS_OFF, %o0
> +
> .globl ret_from_fork
> ret_from_fork:
> /* Clear current_thread_info()->new_child. */
> diff --git a/arch/sparc/kernel/syscalls/syscall.tbl b/arch/sparc/kernel/syscalls/syscall.tbl
> index 39aa26b6a50b..c0307bb09892 100644
> --- a/arch/sparc/kernel/syscalls/syscall.tbl
> +++ b/arch/sparc/kernel/syscalls/syscall.tbl
> @@ -480,7 +480,7 @@
> 432 common fsmount sys_fsmount
> 433 common fspick sys_fspick
> 434 common pidfd_open sys_pidfd_open
> -# 435 reserved for clone3
> +435 common clone3 __sys_clone3
> 436 common close_range sys_close_range
> 437 common openat2 sys_openat2
> 438 common pidfd_getfd sys_pidfd_getfd
Applied on top of 6.19-rc5, tested on a Sun Netra 240 (UltraSPARC IIIi).
Running the kernel selftest for clone3 works fine:
root@raverin:/usr/src/linux/tools/testing/selftests/clone3# uname -a
Linux raverin 6.19.0-rc5+ #18 Fri Jan 16 16:02:10 UTC 2026 sparc64 GNU/Linux
root@raverin:/usr/src/linux/tools/testing/selftests/clone3# make
CC clone3
CC clone3_clear_sighand
CC clone3_set_tid
CC clone3_cap_checkpoint_restore
root@raverin:/usr/src/linux/tools/testing/selftests/clone3# ./clone3
TAP version 13
1..19
# clone3() syscall supported
# Running test 'simple clone3()'
# [1385] Trying clone3() with flags 0 (size 0)
# I am the parent (1385). My child's pid is 1386
# I am the child, my PID is 1386
# [1385] clone3() with flags says: 0 expected 0
ok 1 simple clone3()
# Running test 'clone3() in a new PID_NS'
# [1385] Trying clone3() with flags 0x20000000 (size 0)
# I am the child, my PID is 1
# I am the parent (1385). My child's pid is 1387
# [1385] clone3() with flags says: 0 expected 0
ok 2 clone3() in a new PID_NS
# Running test 'CLONE_ARGS_SIZE_VER0'
# [1385] Trying clone3() with flags 0 (size 64)
# I am the parent (1385). My child's pid is 1388
# I am the child, my PID is 1388
# [1385] clone3() with flags says: 0 expected 0
ok 3 CLONE_ARGS_SIZE_VER0
# Running test 'CLONE_ARGS_SIZE_VER0 - 8'
# [1385] Trying clone3() with flags 0 (size 56)
# Invalid argument - Failed to create new process
# [1385] clone3() with flags says: -22 expected -22
ok 4 CLONE_ARGS_SIZE_VER0 - 8
# Running test 'sizeof(struct clone_args) + 8'
# [1385] Trying clone3() with flags 0 (size 96)
# I am the parent (1385). My child's pid is 1389
# I am the child, my PID is 1389
# [1385] clone3() with flags says: 0 expected 0
ok 5 sizeof(struct clone_args) + 8
# Running test 'exit_signal with highest 32 bits non-zero'
# [1385] Trying clone3() with flags 0 (size 0)
# Invalid argument - Failed to create new process
# [1385] clone3() with flags says: -22 expected -22
ok 6 exit_signal with highest 32 bits non-zero
# Running test 'negative 32-bit exit_signal'
# [1385] Trying clone3() with flags 0 (size 0)
# Invalid argument - Failed to create new process
# [1385] clone3() with flags says: -22 expected -22
ok 7 negative 32-bit exit_signal
# Running test 'exit_signal not fitting into CSIGNAL mask'
# [1385] Trying clone3() with flags 0 (size 0)
# Invalid argument - Failed to create new process
# [1385] clone3() with flags says: -22 expected -22
ok 8 exit_signal not fitting into CSIGNAL mask
# Running test 'NSIG < exit_signal < CSIG'
# [1385] Trying clone3() with flags 0 (size 0)
# Invalid argument - Failed to create new process
# [1385] clone3() with flags says: -22 expected -22
ok 9 NSIG < exit_signal < CSIG
# Running test 'Arguments sizeof(struct clone_args) + 8'
# [1385] Trying clone3() with flags 0 (size 96)
# I am the parent (1385). My child's pid is 1390
# I am the child, my PID is 1390
# [1385] clone3() with flags says: 0 expected 0
ok 10 Arguments sizeof(struct clone_args) + 8
# Running test 'Arguments sizeof(struct clone_args) + 16'
# [1385] Trying clone3() with flags 0 (size 104)
# Argument list too long - Failed to create new process
# [1385] clone3() with flags says: -7 expected -7
ok 11 Arguments sizeof(struct clone_args) + 16
# Running test 'Arguments sizeof(struct clone_arg) * 2'
# [1385] Trying clone3() with flags 0 (size 104)
# Argument list too long - Failed to create new process
# [1385] clone3() with flags says: -7 expected -7
ok 12 Arguments sizeof(struct clone_arg) * 2
# Running test 'Arguments > page size'
# [1385] Trying clone3() with flags 0 (size 8200)
# Argument list too long - Failed to create new process
# [1385] clone3() with flags says: -7 expected -7
ok 13 Arguments > page size
# Running test 'CLONE_ARGS_SIZE_VER0 in a new PID NS'
# [1385] Trying clone3() with flags 0x20000000 (size 64)
# I am the parent (1385). My child's pid is 1391
# I am the child, my PID is 1
# [1385] clone3() with flags says: 0 expected 0
ok 14 CLONE_ARGS_SIZE_VER0 in a new PID NS
# Running test 'CLONE_ARGS_SIZE_VER0 - 8 in a new PID NS'
# [1385] Trying clone3() with flags 0x20000000 (size 56)
# Invalid argument - Failed to create new process
# [1385] clone3() with flags says: -22 expected -22
ok 15 CLONE_ARGS_SIZE_VER0 - 8 in a new PID NS
# Running test 'sizeof(struct clone_args) + 8 in a new PID NS'
# [1385] Trying clone3() with flags 0x20000000 (size 96)
# I am the parent (1385). My child's pid is 1392
# I am the child, my PID is 1
# [1385] clone3() with flags says: 0 expected 0
ok 16 sizeof(struct clone_args) + 8 in a new PID NS
# Running test 'Arguments > page size in a new PID NS'
# [1385] Trying clone3() with flags 0x20000000 (size 8200)
# Argument list too long - Failed to create new process
# [1385] clone3() with flags says: -7 expected -7
ok 17 Arguments > page size in a new PID NS
# Time namespaces are not supported
ok 18 # SKIP New time NS
# Running test 'exit signal (SIGCHLD) in flags'
# [1385] Trying clone3() with flags 0x14 (size 0)
# Invalid argument - Failed to create new process
# [1385] clone3() with flags says: -22 expected -22
ok 19 exit signal (SIGCHLD) in flags
# 1 skipped test(s) detected. Consider enabling relevant config options to improve coverage.
# Totals: pass:18 fail:0 xfail:0 xpass:0 skip:1 error:0
root@raverin:/usr/src/linux/tools/testing/selftests/clone3#
Tested-by: John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de>
Thanks,
Adrian
--
.''`. John Paul Adrian Glaubitz
: :' : Debian Developer
`. `' Physicist
`- GPG: 62FF 8A75 84E0 2956 9546 0006 7426 3B37 F5B5 F913
On Fri, Jan 16, 2026 at 04:30:50PM +0100, Ludwig Rydberg wrote: > Add support for the clone3 system call to the SPARC architectures. > > The implementation follows the pattern of the original clone syscall. > However, instead of explicitly calling kernel_clone, the clone3 > handler calls the generic sys_clone3 handler in kernel/fork. > In case no stack is provided, the parents stack is reused. > > The return call conventions for clone on SPARC are kept for clone3: > Parent --> %o0 == child's pid, %o1 == 0 > Child --> %o0 == parent's pid, %o1 == 1 One of the benefits of having a new clone3 is that the interface can be made the same across all architectures*, unlike clone, which both passes the arguments in different orders for different architectures and, in the case of SPARC, has this weird return convention inherited from the SunOS syscall interface. Is there a good reason to deviate for clone3 too and keep this annoying oddity going, that requires special-casing SPARC when other architectures can just syscall(__NR_clone3, ...)? Jessica * Even Itanium's clone2 could have been subsumed by it, as clone3 passes the stack base and size rather than the desired stack pointer
Hi Jessica, > One of the benefits of having a new clone3 is that the interface can be > made the same across all architectures*, unlike clone, which both passes > the arguments in different orders for different architectures and, in > the case of SPARC, has this weird return convention inherited from the > SunOS syscall interface. Is there a good reason to deviate for clone3 > too and keep this annoying oddity going, that requires special-casing > SPARC when other architectures can just syscall(__NR_clone3, ...)? > Thanks for commenting on this. No, you're right (there is no good reason). The original implementation just followed how things were but I have a v2 coming up which will follow the regular kernel return value conventions. This will ensure that syscall(__NR_clone3, ...) works on SPARC exactly as it does on other architectures (i.e I'll drop the third patch in the series). Best regards, // Ludwig
Hi,
On Fri, 2026-01-16 at 16:47 +0000, Jessica Clarke wrote:
> On Fri, Jan 16, 2026 at 04:30:50PM +0100, Ludwig Rydberg wrote:
> > Add support for the clone3 system call to the SPARC architectures.
> >
> > The implementation follows the pattern of the original clone syscall.
> > However, instead of explicitly calling kernel_clone, the clone3
> > handler calls the generic sys_clone3 handler in kernel/fork.
> > In case no stack is provided, the parents stack is reused.
> >
> > The return call conventions for clone on SPARC are kept for clone3:
> > Parent --> %o0 == child's pid, %o1 == 0
> > Child --> %o0 == parent's pid, %o1 == 1
>
> One of the benefits of having a new clone3 is that the interface can be
> made the same across all architectures*, unlike clone, which both passes
> the arguments in different orders for different architectures and, in
> the case of SPARC, has this weird return convention inherited from the
> SunOS syscall interface. Is there a good reason to deviate for clone3
> too and keep this annoying oddity going, that requires special-casing
> SPARC when other architectures can just syscall(__NR_clone3, ...)?
Very good point. Since clone3() is a new syscall, I think it would make more
sense to use the same interface as all the other architectures.
The weird syscall interface was already the reason why we had to write custom
code for systemd on SPARC in order to use the raw clone() syscall.
I think the proposed implementation of clone3() for SPARC would actually break
the libcamera build which calls clone3() using the syscall handler [1]:
FAILED: [code=1] src/libcamera/libcamera.so.0.6.0.p/process.cpp.o
c++ -Isrc/libcamera/libcamera.so.0.6.0.p -Isrc/libcamera -I../src/libcamera -Iinclude -I../include -Iinclude/libcamera -Iinclude/libcamera/ipa -Iinclude/libcamera/internal -Isrc/libcamera/proxy -
I/usr/include/p11-kit-1 -I/usr/include/sparc64-linux-gnu -fdiagnostics-color=always -D_GLIBCXX_ASSERTIONS=1 -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -Wextra -Werror -std=c++17 -Wnon-virtual-dtor -
Wno-redundant-move -Wmissing-declarations -Wshadow -include /build/reproducible-path/libcamera-0.6.0/obj-sparc64-linux-gnu/config.h -g -O2 -ffile-prefix-map=/build/reproducible-path/libcamera-0.6.0=.
-fstack-protector-strong -Wformat -Werror=format-security -Wno-error -Wdate-time -D_FORTIFY_SOURCE=2 -fPIC -DLIBCAMERA_BASE_PRIVATE -MD -MQ src/libcamera/libcamera.so.0.6.0.p/process.cpp.o -MF
src/libcamera/libcamera.so.0.6.0.p/process.cpp.o.d -o src/libcamera/libcamera.so.0.6.0.p/process.cpp.o -c ../src/libcamera/process.cpp
../src/libcamera/process.cpp: In member function ‘int libcamera::Process::start(const std::string&, libcamera::Span<const std::__cxx11::basic_string<char> >, libcamera::Span<const int>)’:
../src/libcamera/process.cpp:160:33: error: ‘SYS_clone3’ was not declared in this scope; did you mean ‘SYS_clone’?
160 | long childPid = syscall(SYS_clone3, &cargs, sizeof(cargs));
| ^~~~~~~~~~
| SYS_clone
I'll verify that and report back.
Adrian
> [1] https://buildd.debian.org/status/fetch.php?pkg=libcamera&arch=sparc64&ver=0.6.0-2&stamp=1766489678&raw=0
--
.''`. John Paul Adrian Glaubitz
: :' : Debian Developer
`. `' Physicist
`- GPG: 62FF 8A75 84E0 2956 9546 0006 7426 3B37 F5B5 F913
Hi, On Sat, 2026-01-17 at 22:23 +0100, John Paul Adrian Glaubitz wrote: > Hi, > > On Fri, 2026-01-16 at 16:47 +0000, Jessica Clarke wrote: > > On Fri, Jan 16, 2026 at 04:30:50PM +0100, Ludwig Rydberg wrote: > > > Add support for the clone3 system call to the SPARC architectures. > > > > > > The implementation follows the pattern of the original clone syscall. > > > However, instead of explicitly calling kernel_clone, the clone3 > > > handler calls the generic sys_clone3 handler in kernel/fork. > > > In case no stack is provided, the parents stack is reused. > > > > > > The return call conventions for clone on SPARC are kept for clone3: > > > Parent --> %o0 == child's pid, %o1 == 0 > > > Child --> %o0 == parent's pid, %o1 == 1 > > > > One of the benefits of having a new clone3 is that the interface can be > > made the same across all architectures*, unlike clone, which both passes > > the arguments in different orders for different architectures and, in > > the case of SPARC, has this weird return convention inherited from the > > SunOS syscall interface. Is there a good reason to deviate for clone3 > > too and keep this annoying oddity going, that requires special-casing > > SPARC when other architectures can just syscall(__NR_clone3, ...)? > > Very good point. Since clone3() is a new syscall, I think it would make more > sense to use the same interface as all the other architectures. > > The weird syscall interface was already the reason why we had to write custom > code for systemd on SPARC in order to use the raw clone() syscall. > > I think the proposed implementation of clone3() for SPARC would actually break > the libcamera build which calls clone3() using the syscall handler [1]: > > FAILED: [code=1] src/libcamera/libcamera.so.0.6.0.p/process.cpp.o > c++ -Isrc/libcamera/libcamera.so.0.6.0.p -Isrc/libcamera -I../src/libcamera -Iinclude -I../include -Iinclude/libcamera -Iinclude/libcamera/ipa -Iinclude/libcamera/internal -Isrc/libcamera/proxy - > I/usr/include/p11-kit-1 -I/usr/include/sparc64-linux-gnu -fdiagnostics-color=always -D_GLIBCXX_ASSERTIONS=1 -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -Wextra -Werror -std=c++17 -Wnon-virtual-dtor - > Wno-redundant-move -Wmissing-declarations -Wshadow -include /build/reproducible-path/libcamera-0.6.0/obj-sparc64-linux-gnu/config.h -g -O2 -ffile-prefix-map=/build/reproducible-path/libcamera-0.6.0=. > -fstack-protector-strong -Wformat -Werror=format-security -Wno-error -Wdate-time -D_FORTIFY_SOURCE=2 -fPIC -DLIBCAMERA_BASE_PRIVATE -MD -MQ src/libcamera/libcamera.so.0.6.0.p/process.cpp.o -MF > src/libcamera/libcamera.so.0.6.0.p/process.cpp.o.d -o src/libcamera/libcamera.so.0.6.0.p/process.cpp.o -c ../src/libcamera/process.cpp > ../src/libcamera/process.cpp: In member function ‘int libcamera::Process::start(const std::string&, libcamera::Span<const std::__cxx11::basic_string<char> >, libcamera::Span<const int>)’: > ../src/libcamera/process.cpp:160:33: error: ‘SYS_clone3’ was not declared in this scope; did you mean ‘SYS_clone’? > 160 | long childPid = syscall(SYS_clone3, &cargs, sizeof(cargs)); > | ^~~~~~~~~~ > | SYS_clone > > I'll verify that and report back. I can confirm that libcamera builds fine and passes its testsuite with a patched kernel: Ok: 38 Expected Fail: 1 Fail: 0 Skipped: 31 Full log written to /home/glaubitz/libcamera/libcamera-0.6.0/obj-sparc64-linux-gnu/meson-logs/testlog.txt make[1]: Leaving directory '/home/glaubitz/libcamera/libcamera-0.6.0' Whether the currently chosen interface is the right one, is another question though. Adrian -- .''`. John Paul Adrian Glaubitz : :' : Debian Developer `. `' Physicist `- GPG: 62FF 8A75 84E0 2956 9546 0006 7426 3B37 F5B5 F913
© 2016 - 2026 Red Hat, Inc.