linux-um.lists.infradead.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/9] um: Add SMP support
@ 2025-07-27  6:29 Tiwei Bie
  2025-07-27  6:29 ` [PATCH 1/9] um: Stop tracking virtual CPUs via mm_cpumask() Tiwei Bie
                   ` (8 more replies)
  0 siblings, 9 replies; 20+ messages in thread
From: Tiwei Bie @ 2025-07-27  6:29 UTC (permalink / raw)
  To: richard, anton.ivanov, johannes; +Cc: linux-um, tiwei.btw, tiwei.bie

From: Tiwei Bie <tiwei.btw@antgroup.com>

This series adds initial symmetric multi-processing (SMP) support
to UML. With this support, users can tell UML to start multiple
virtual CPUs, each represented as a separate host thread.

In UML, kthreads and normal threads (when running in kernel mode)
can be scheduled and executed simultaneously on different virtual
CPUs. However, the userspace code of normal threads currently still
runs within their respective single-threaded stubs.

So SMP within the kernel is supported. SMP across different guest
processes is also supported. Currently, the limitation is that
multi-threading within the same guest process does not yet support
true SMP in userspace, as it would require converting the stub into
a multi-threaded implementation, which is not covered in this series.

Extending the stub to support true SMP in userspace could be done
later and requires careful design. It may be better to handle it in
a follow-up series.

Here are some steps to try out SMP support in UML:

1. Build UML with CONFIG_SMP=y and, for example, CONFIG_NR_CPUS=8.

2. Launch a UML instance with, for example, 8 virtual CPUs.

 $ ./linux mem=16G ncpus=8 seccomp=on init=/bin/sh ubd0=$your_rootfs_image

RFC: https://lore.kernel.org/linux-um/20250713172536.404809-1-tiwei.bie@linux.dev/

Tiwei Bie (9):
  um: Stop tracking virtual CPUs via mm_cpumask()
  um: Remove unused cpu_data and current_cpu_data macros
  um: vdso: Implement __vdso_getcpu() via syscall
  um: Preserve errno within signal handler
  um: Turn signals_* into thread-local variables
  um: Determine sleep based on need_resched()
  um: Define timers on a per-CPU basis
  um: Support directing IO signals to calling thread
  um: Add initial SMP support

 arch/um/Kconfig                         |  39 +++-
 arch/um/include/asm/Kbuild              |   3 +-
 arch/um/include/asm/current.h           |   5 +-
 arch/um/include/asm/hardirq.h           |  24 ++-
 arch/um/include/asm/irqflags.h          |   4 +-
 arch/um/include/asm/mmu.h               |  10 +
 arch/um/include/asm/mmu_context.h       |  11 -
 arch/um/include/asm/percpu.h            |  20 ++
 arch/um/include/asm/pgtable.h           |   2 +
 arch/um/include/asm/processor-generic.h |   8 +-
 arch/um/include/asm/smp.h               |  23 +-
 arch/um/include/asm/spinlock.h          |   8 +
 arch/um/include/linux/smp-internal.h    |  21 ++
 arch/um/include/linux/time-internal.h   |   3 +
 arch/um/include/shared/kern_util.h      |   2 +
 arch/um/include/shared/longjmp.h        |   3 +-
 arch/um/include/shared/os.h             |  15 +-
 arch/um/include/shared/skas/mm_id.h     |   3 +
 arch/um/include/shared/smp.h            |  19 ++
 arch/um/kernel/Makefile                 |   1 +
 arch/um/kernel/irq.c                    |  35 +++-
 arch/um/kernel/ksyms.c                  |   2 +-
 arch/um/kernel/mem.c                    |   2 +
 arch/um/kernel/process.c                |  20 +-
 arch/um/kernel/skas/mmu.c               |  32 ++-
 arch/um/kernel/smp.c                    | 266 ++++++++++++++++++++++++
 arch/um/kernel/time.c                   |  54 +++--
 arch/um/kernel/tlb.c                    |   5 +-
 arch/um/kernel/trap.c                   |   2 +-
 arch/um/kernel/um_arch.c                |  24 ++-
 arch/um/os-Linux/Makefile               |   4 +-
 arch/um/os-Linux/file.c                 |  10 +-
 arch/um/os-Linux/main.c                 |   5 +-
 arch/um/os-Linux/process.c              |  10 +
 arch/um/os-Linux/signal.c               |  14 +-
 arch/um/os-Linux/skas/process.c         |   9 +
 arch/um/os-Linux/smp.c                  |  86 ++++++++
 arch/um/os-Linux/start_up.c             |   4 +
 arch/um/os-Linux/time.c                 |  42 ++--
 arch/x86/um/vdso/um_vdso.c              |  18 +-
 40 files changed, 769 insertions(+), 99 deletions(-)
 create mode 100644 arch/um/include/asm/percpu.h
 create mode 100644 arch/um/include/asm/spinlock.h
 create mode 100644 arch/um/include/linux/smp-internal.h
 create mode 100644 arch/um/include/shared/smp.h
 create mode 100644 arch/um/kernel/smp.c
 create mode 100644 arch/um/os-Linux/smp.c

-- 
2.34.1



^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH 1/9] um: Stop tracking virtual CPUs via mm_cpumask()
  2025-07-27  6:29 [PATCH 0/9] um: Add SMP support Tiwei Bie
@ 2025-07-27  6:29 ` Tiwei Bie
  2025-07-27  6:29 ` [PATCH 2/9] um: Remove unused cpu_data and current_cpu_data macros Tiwei Bie
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 20+ messages in thread
From: Tiwei Bie @ 2025-07-27  6:29 UTC (permalink / raw)
  To: richard, anton.ivanov, johannes; +Cc: linux-um, tiwei.btw, tiwei.bie

From: Tiwei Bie <tiwei.btw@antgroup.com>

In UML, each user address space is represented as a separate stub
process on the host. Therefore, user address spaces do not require
TLB management on UML virtual CPUs, and it's unnecessary to track
which virtual CPUs they have executed on.

Signed-off-by: Tiwei Bie <tiwei.btw@antgroup.com>
---
 arch/um/include/asm/mmu_context.h | 11 -----------
 1 file changed, 11 deletions(-)

diff --git a/arch/um/include/asm/mmu_context.h b/arch/um/include/asm/mmu_context.h
index 0bbb24868557..c727e56ba116 100644
--- a/arch/um/include/asm/mmu_context.h
+++ b/arch/um/include/asm/mmu_context.h
@@ -13,20 +13,9 @@
 #include <asm/mm_hooks.h>
 #include <asm/mmu.h>
 
-#define activate_mm activate_mm
-static inline void activate_mm(struct mm_struct *old, struct mm_struct *new)
-{
-}
-
 static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next, 
 			     struct task_struct *tsk)
 {
-	unsigned cpu = smp_processor_id();
-
-	if (prev != next) {
-		cpumask_clear_cpu(cpu, mm_cpumask(prev));
-		cpumask_set_cpu(cpu, mm_cpumask(next));
-	}
 }
 
 #define init_new_context init_new_context
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 2/9] um: Remove unused cpu_data and current_cpu_data macros
  2025-07-27  6:29 [PATCH 0/9] um: Add SMP support Tiwei Bie
  2025-07-27  6:29 ` [PATCH 1/9] um: Stop tracking virtual CPUs via mm_cpumask() Tiwei Bie
@ 2025-07-27  6:29 ` Tiwei Bie
  2025-07-27  6:29 ` [PATCH 3/9] um: vdso: Implement __vdso_getcpu() via syscall Tiwei Bie
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 20+ messages in thread
From: Tiwei Bie @ 2025-07-27  6:29 UTC (permalink / raw)
  To: richard, anton.ivanov, johannes; +Cc: linux-um, tiwei.btw, tiwei.bie

From: Tiwei Bie <tiwei.btw@antgroup.com>

These two macros have no users. Remove them.

Signed-off-by: Tiwei Bie <tiwei.btw@antgroup.com>
---
 arch/um/include/asm/processor-generic.h | 2 --
 1 file changed, 2 deletions(-)

diff --git a/arch/um/include/asm/processor-generic.h b/arch/um/include/asm/processor-generic.h
index 8a789c17acd8..236fdfd7cdbe 100644
--- a/arch/um/include/asm/processor-generic.h
+++ b/arch/um/include/asm/processor-generic.h
@@ -81,8 +81,6 @@ struct cpuinfo_um {
 
 extern struct cpuinfo_um boot_cpu_data;
 
-#define cpu_data(cpu)    boot_cpu_data
-#define current_cpu_data boot_cpu_data
 #define cache_line_size()	(boot_cpu_data.cache_alignment)
 
 #define KSTK_REG(tsk, reg) get_thread_reg(reg, &tsk->thread.switch_buf)
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 3/9] um: vdso: Implement __vdso_getcpu() via syscall
  2025-07-27  6:29 [PATCH 0/9] um: Add SMP support Tiwei Bie
  2025-07-27  6:29 ` [PATCH 1/9] um: Stop tracking virtual CPUs via mm_cpumask() Tiwei Bie
  2025-07-27  6:29 ` [PATCH 2/9] um: Remove unused cpu_data and current_cpu_data macros Tiwei Bie
@ 2025-07-27  6:29 ` Tiwei Bie
  2025-07-27  6:29 ` [PATCH 4/9] um: Preserve errno within signal handler Tiwei Bie
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 20+ messages in thread
From: Tiwei Bie @ 2025-07-27  6:29 UTC (permalink / raw)
  To: richard, anton.ivanov, johannes; +Cc: linux-um, tiwei.btw, tiwei.bie

From: Tiwei Bie <tiwei.btw@antgroup.com>

We are going to support SMP in UML, so we can not hard code
the CPU and NUMA node in __vdso_getcpu() anymore.

Signed-off-by: Tiwei Bie <tiwei.btw@antgroup.com>
---
 arch/x86/um/vdso/um_vdso.c | 18 ++++++++----------
 1 file changed, 8 insertions(+), 10 deletions(-)

diff --git a/arch/x86/um/vdso/um_vdso.c b/arch/x86/um/vdso/um_vdso.c
index cbae2584124f..ee40ac446c1c 100644
--- a/arch/x86/um/vdso/um_vdso.c
+++ b/arch/x86/um/vdso/um_vdso.c
@@ -17,7 +17,7 @@
 int __vdso_clock_gettime(clockid_t clock, struct __kernel_old_timespec *ts);
 int __vdso_gettimeofday(struct __kernel_old_timeval *tv, struct timezone *tz);
 __kernel_old_time_t __vdso_time(__kernel_old_time_t *t);
-long __vdso_getcpu(unsigned int *cpu, unsigned int *node, struct getcpu_cache *unused);
+long __vdso_getcpu(unsigned int *cpu, unsigned int *node, struct getcpu_cache *tcache);
 
 int __vdso_clock_gettime(clockid_t clock, struct __kernel_old_timespec *ts)
 {
@@ -60,18 +60,16 @@ __kernel_old_time_t __vdso_time(__kernel_old_time_t *t)
 __kernel_old_time_t time(__kernel_old_time_t *t) __attribute__((weak, alias("__vdso_time")));
 
 long
-__vdso_getcpu(unsigned int *cpu, unsigned int *node, struct getcpu_cache *unused)
+__vdso_getcpu(unsigned int *cpu, unsigned int *node, struct getcpu_cache *tcache)
 {
-	/*
-	 * UML does not support SMP, we can cheat here. :)
-	 */
+	long ret;
 
-	if (cpu)
-		*cpu = 0;
-	if (node)
-		*node = 0;
+	asm volatile("syscall"
+		: "=a" (ret)
+		: "0" (__NR_getcpu), "D" (cpu), "S" (node), "d" (tcache)
+		: "rcx", "r11", "memory");
 
-	return 0;
+	return ret;
 }
 
 long getcpu(unsigned int *cpu, unsigned int *node, struct getcpu_cache *tcache)
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 4/9] um: Preserve errno within signal handler
  2025-07-27  6:29 [PATCH 0/9] um: Add SMP support Tiwei Bie
                   ` (2 preceding siblings ...)
  2025-07-27  6:29 ` [PATCH 3/9] um: vdso: Implement __vdso_getcpu() via syscall Tiwei Bie
@ 2025-07-27  6:29 ` Tiwei Bie
  2025-07-27  6:29 ` [PATCH 5/9] um: Turn signals_* into thread-local variables Tiwei Bie
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 20+ messages in thread
From: Tiwei Bie @ 2025-07-27  6:29 UTC (permalink / raw)
  To: richard, anton.ivanov, johannes; +Cc: linux-um, tiwei.btw, tiwei.bie

From: Tiwei Bie <tiwei.btw@antgroup.com>

We rely on errno to determine if a syscall has failed. However,
the current signal handler may modify errno. Preserve errno within
the signal handler to ensure it remains async-signal safe.

Signed-off-by: Tiwei Bie <tiwei.btw@antgroup.com>
---
 arch/um/os-Linux/signal.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/arch/um/os-Linux/signal.c b/arch/um/os-Linux/signal.c
index 11f07f498270..217a71244b69 100644
--- a/arch/um/os-Linux/signal.c
+++ b/arch/um/os-Linux/signal.c
@@ -201,8 +201,11 @@ static void hard_handler(int sig, siginfo_t *si, void *p)
 {
 	ucontext_t *uc = p;
 	mcontext_t *mc = &uc->uc_mcontext;
+	int errno_saved = errno;
 
 	(*handlers[sig])(sig, (struct siginfo *)si, mc);
+
+	errno = errno_saved;
 }
 
 void set_handler(int sig)
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 5/9] um: Turn signals_* into thread-local variables
  2025-07-27  6:29 [PATCH 0/9] um: Add SMP support Tiwei Bie
                   ` (3 preceding siblings ...)
  2025-07-27  6:29 ` [PATCH 4/9] um: Preserve errno within signal handler Tiwei Bie
@ 2025-07-27  6:29 ` Tiwei Bie
  2025-07-27  6:29 ` [PATCH 6/9] um: Determine sleep based on need_resched() Tiwei Bie
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 20+ messages in thread
From: Tiwei Bie @ 2025-07-27  6:29 UTC (permalink / raw)
  To: richard, anton.ivanov, johannes; +Cc: linux-um, tiwei.btw, tiwei.bie

From: Tiwei Bie <tiwei.btw@antgroup.com>

Turn signals_enabled, signals_pending and signals_active into
thread-local variables. This enables us to control and track
signals independently on each CPU thread. This is a preparation
for adding SMP support.

Signed-off-by: Tiwei Bie <tiwei.btw@antgroup.com>
---
 arch/um/include/asm/irqflags.h   |  4 ++--
 arch/um/include/shared/longjmp.h |  3 +--
 arch/um/include/shared/os.h      |  1 +
 arch/um/kernel/ksyms.c           |  2 +-
 arch/um/os-Linux/signal.c        | 11 ++++++++---
 5 files changed, 13 insertions(+), 8 deletions(-)

diff --git a/arch/um/include/asm/irqflags.h b/arch/um/include/asm/irqflags.h
index 1e69ef5bc35e..31e49e0894c5 100644
--- a/arch/um/include/asm/irqflags.h
+++ b/arch/um/include/asm/irqflags.h
@@ -2,7 +2,7 @@
 #ifndef __UM_IRQFLAGS_H
 #define __UM_IRQFLAGS_H
 
-extern int signals_enabled;
+int um_get_signals(void);
 int um_set_signals(int enable);
 void block_signals(void);
 void unblock_signals(void);
@@ -10,7 +10,7 @@ void unblock_signals(void);
 #define arch_local_save_flags arch_local_save_flags
 static inline unsigned long arch_local_save_flags(void)
 {
-	return signals_enabled;
+	return um_get_signals();
 }
 
 #define arch_local_irq_restore arch_local_irq_restore
diff --git a/arch/um/include/shared/longjmp.h b/arch/um/include/shared/longjmp.h
index 8863319039f3..c53e43d980c8 100644
--- a/arch/um/include/shared/longjmp.h
+++ b/arch/um/include/shared/longjmp.h
@@ -5,7 +5,6 @@
 #include <sysdep/archsetjmp.h>
 #include <os.h>
 
-extern int signals_enabled;
 extern int setjmp(jmp_buf);
 extern void longjmp(jmp_buf, int);
 
@@ -15,7 +14,7 @@ extern void longjmp(jmp_buf, int);
 
 #define UML_SETJMP(buf) ({				\
 	int n, enable;					\
-	enable = *(volatile int *)&signals_enabled;	\
+	enable = um_get_signals();			\
 	n = setjmp(*buf);				\
 	if(n != 0)					\
 		um_set_signals_trace(enable);		\
diff --git a/arch/um/include/shared/os.h b/arch/um/include/shared/os.h
index b35cc8ce333b..324d4eed3385 100644
--- a/arch/um/include/shared/os.h
+++ b/arch/um/include/shared/os.h
@@ -243,6 +243,7 @@ extern void send_sigio_to_self(void);
 extern int change_sig(int signal, int on);
 extern void block_signals(void);
 extern void unblock_signals(void);
+extern int um_get_signals(void);
 extern int um_set_signals(int enable);
 extern int um_set_signals_trace(int enable);
 extern void deliver_alarm(void);
diff --git a/arch/um/kernel/ksyms.c b/arch/um/kernel/ksyms.c
index f2fb77da08cf..96314c31e61c 100644
--- a/arch/um/kernel/ksyms.c
+++ b/arch/um/kernel/ksyms.c
@@ -6,8 +6,8 @@
 #include <linux/module.h>
 #include <os.h>
 
+EXPORT_SYMBOL(um_get_signals);
 EXPORT_SYMBOL(um_set_signals);
-EXPORT_SYMBOL(signals_enabled);
 
 EXPORT_SYMBOL(os_stat_fd);
 EXPORT_SYMBOL(os_stat_file);
diff --git a/arch/um/os-Linux/signal.c b/arch/um/os-Linux/signal.c
index 217a71244b69..4f8c6b2b4888 100644
--- a/arch/um/os-Linux/signal.c
+++ b/arch/um/os-Linux/signal.c
@@ -68,12 +68,12 @@ static void sig_handler_common(int sig, struct siginfo *si, mcontext_t *mc)
 #define SIGCHLD_BIT 2
 #define SIGCHLD_MASK (1 << SIGCHLD_BIT)
 
-int signals_enabled;
+static __thread int signals_enabled;
 #if IS_ENABLED(CONFIG_UML_TIME_TRAVEL_SUPPORT)
 static int signals_blocked, signals_blocked_pending;
 #endif
-static unsigned int signals_pending;
-static unsigned int signals_active = 0;
+static __thread unsigned int signals_pending;
+static __thread unsigned int signals_active;
 
 static void sig_handler(int sig, struct siginfo *si, mcontext_t *mc)
 {
@@ -345,6 +345,11 @@ void unblock_signals(void)
 	}
 }
 
+int um_get_signals(void)
+{
+	return signals_enabled;
+}
+
 int um_set_signals(int enable)
 {
 	int ret;
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 6/9] um: Determine sleep based on need_resched()
  2025-07-27  6:29 [PATCH 0/9] um: Add SMP support Tiwei Bie
                   ` (4 preceding siblings ...)
  2025-07-27  6:29 ` [PATCH 5/9] um: Turn signals_* into thread-local variables Tiwei Bie
@ 2025-07-27  6:29 ` Tiwei Bie
  2025-07-27  6:29 ` [PATCH 7/9] um: Define timers on a per-CPU basis Tiwei Bie
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 20+ messages in thread
From: Tiwei Bie @ 2025-07-27  6:29 UTC (permalink / raw)
  To: richard, anton.ivanov, johannes; +Cc: linux-um, tiwei.btw, tiwei.bie

From: Tiwei Bie <tiwei.btw@antgroup.com>

With SMP and NO_HZ enabled, the CPU may still need to sleep even
if the timer is disarmed. Switch to deciding whether to sleep based
on pending resched. This is a preparation for adding SMP support.

Signed-off-by: Tiwei Bie <tiwei.btw@antgroup.com>
---
 arch/um/include/shared/kern_util.h |  1 +
 arch/um/kernel/process.c           | 12 ++++++++++--
 arch/um/os-Linux/time.c            | 11 +++++------
 3 files changed, 16 insertions(+), 8 deletions(-)

diff --git a/arch/um/include/shared/kern_util.h b/arch/um/include/shared/kern_util.h
index 00ca3e12fd9a..3daaa5c4b35d 100644
--- a/arch/um/include/shared/kern_util.h
+++ b/arch/um/include/shared/kern_util.h
@@ -55,6 +55,7 @@ extern int __uml_cant_sleep(void);
 extern int get_current_pid(void);
 extern int copy_from_user_proc(void *to, void *from, int size);
 extern char *uml_strdup(const char *string);
+int uml_need_resched(void);
 
 extern unsigned long to_irq_stack(unsigned long *mask_out);
 extern unsigned long from_irq_stack(int nested);
diff --git a/arch/um/kernel/process.c b/arch/um/kernel/process.c
index 1be644de9e41..01b935c00454 100644
--- a/arch/um/kernel/process.c
+++ b/arch/um/kernel/process.c
@@ -209,10 +209,13 @@ int arch_dup_task_struct(struct task_struct *dst,
 
 void um_idle_sleep(void)
 {
-	if (time_travel_mode != TT_MODE_OFF)
+	if (time_travel_mode != TT_MODE_OFF) {
 		time_travel_sleep();
-	else
+	} else {
+		raw_local_irq_enable();
 		os_idle_sleep();
+		raw_local_irq_disable();
+	}
 }
 
 void arch_cpu_idle(void)
@@ -225,6 +228,11 @@ int __uml_cant_sleep(void) {
 	/* Is in_interrupt() really needed? */
 }
 
+int uml_need_resched(void)
+{
+	return need_resched();
+}
+
 extern exitcall_t __uml_exitcall_begin, __uml_exitcall_end;
 
 void do_uml_exitcalls(void)
diff --git a/arch/um/os-Linux/time.c b/arch/um/os-Linux/time.c
index 4d5591d96d8c..f25a4196bab7 100644
--- a/arch/um/os-Linux/time.c
+++ b/arch/um/os-Linux/time.c
@@ -98,18 +98,17 @@ long long os_nsecs(void)
  */
 void os_idle_sleep(void)
 {
-	struct itimerspec its;
 	sigset_t set, old;
 
-	/* block SIGALRM while we analyze the timer state */
+	/* Block SIGALRM while performing the need_resched check. */
 	sigemptyset(&set);
 	sigaddset(&set, SIGALRM);
 	sigprocmask(SIG_BLOCK, &set, &old);
 
-	/* check the timer, and if it'll fire then wait for it */
-	timer_gettime(event_high_res_timer, &its);
-	if (its.it_value.tv_sec || its.it_value.tv_nsec)
+	/* Sleep if no resched is pending. */
+	if (!uml_need_resched())
 		sigsuspend(&old);
-	/* either way, restore the signal mask */
+
+	/* Restore the signal mask. */
 	sigprocmask(SIG_UNBLOCK, &set, NULL);
 }
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 7/9] um: Define timers on a per-CPU basis
  2025-07-27  6:29 [PATCH 0/9] um: Add SMP support Tiwei Bie
                   ` (5 preceding siblings ...)
  2025-07-27  6:29 ` [PATCH 6/9] um: Determine sleep based on need_resched() Tiwei Bie
@ 2025-07-27  6:29 ` Tiwei Bie
  2025-07-27  6:29 ` [PATCH 8/9] um: Support directing IO signals to calling thread Tiwei Bie
  2025-07-27  6:29 ` [PATCH 9/9] um: Add initial SMP support Tiwei Bie
  8 siblings, 0 replies; 20+ messages in thread
From: Tiwei Bie @ 2025-07-27  6:29 UTC (permalink / raw)
  To: richard, anton.ivanov, johannes; +Cc: linux-um, tiwei.btw, tiwei.bie

From: Tiwei Bie <tiwei.btw@antgroup.com>

Define timers on a per-CPU basis to enable each CPU to have its
own timer. This is a preparation for adding SMP support.

Signed-off-by: Tiwei Bie <tiwei.btw@antgroup.com>
---
 arch/um/include/linux/time-internal.h |  3 ++
 arch/um/include/shared/os.h           |  6 +--
 arch/um/kernel/irq.c                  |  2 +-
 arch/um/kernel/time.c                 | 54 +++++++++++++++++++--------
 arch/um/os-Linux/main.c               |  2 +-
 arch/um/os-Linux/time.c               | 28 +++++++++-----
 6 files changed, 66 insertions(+), 29 deletions(-)

diff --git a/arch/um/include/linux/time-internal.h b/arch/um/include/linux/time-internal.h
index 138908b999d7..c274eb5ad55e 100644
--- a/arch/um/include/linux/time-internal.h
+++ b/arch/um/include/linux/time-internal.h
@@ -90,4 +90,7 @@ extern unsigned long tt_extra_sched_jiffies;
  * which is intentional since we really shouldn't link it in that case.
  */
 void time_travel_ndelay(unsigned long nsec);
+
+int um_setup_timer(void);
+
 #endif /* __TIMER_INTERNAL_H__ */
diff --git a/arch/um/include/shared/os.h b/arch/um/include/shared/os.h
index 324d4eed3385..0ca6e4548671 100644
--- a/arch/um/include/shared/os.h
+++ b/arch/um/include/shared/os.h
@@ -269,9 +269,9 @@ extern void os_warn(const char *fmt, ...)
 /* time.c */
 extern void os_idle_sleep(void);
 extern int os_timer_create(void);
-extern int os_timer_set_interval(unsigned long long nsecs);
-extern int os_timer_one_shot(unsigned long long nsecs);
-extern void os_timer_disable(void);
+extern int os_timer_set_interval(int cpu, unsigned long long nsecs);
+extern int os_timer_one_shot(int cpu, unsigned long long nsecs);
+extern void os_timer_disable(int cpu);
 extern long long os_persistent_clock_emulation(void);
 extern long long os_nsecs(void);
 
diff --git a/arch/um/kernel/irq.c b/arch/um/kernel/irq.c
index 0dfaf96bb7da..5ed8014e43e4 100644
--- a/arch/um/kernel/irq.c
+++ b/arch/um/kernel/irq.c
@@ -683,7 +683,7 @@ void __init init_IRQ(void)
 {
 	int i;
 
-	irq_set_chip_and_handler(TIMER_IRQ, &alarm_irq_type, handle_edge_irq);
+	irq_set_chip_and_handler(TIMER_IRQ, &alarm_irq_type, handle_percpu_irq);
 
 	for (i = 1; i < UM_LAST_SIGNAL_IRQ; i++)
 		irq_set_chip_and_handler(i, &normal_irq_type, handle_edge_irq);
diff --git a/arch/um/kernel/time.c b/arch/um/kernel/time.c
index ae0fa2173778..79c40fe5c80a 100644
--- a/arch/um/kernel/time.c
+++ b/arch/um/kernel/time.c
@@ -625,9 +625,10 @@ void time_travel_sleep(void)
 	 * controller application.
 	 */
 	unsigned long long next = S64_MAX;
+	int cpu = raw_smp_processor_id();
 
 	if (time_travel_mode == TT_MODE_BASIC)
-		os_timer_disable();
+		os_timer_disable(cpu);
 
 	time_travel_update_time(next, true);
 
@@ -638,9 +639,9 @@ void time_travel_sleep(void)
 			 * This is somewhat wrong - we should get the first
 			 * one sooner like the os_timer_one_shot() below...
 			 */
-			os_timer_set_interval(time_travel_timer_interval);
+			os_timer_set_interval(cpu, time_travel_timer_interval);
 		} else {
-			os_timer_one_shot(time_travel_timer_event.time - next);
+			os_timer_one_shot(cpu, time_travel_timer_event.time - next);
 		}
 	}
 }
@@ -758,6 +759,8 @@ extern u64 time_travel_ext_req(u32 op, u64 time);
 #define time_travel_del_event(e) do { } while (0)
 #endif
 
+static struct clock_event_device timer_clockevent[NR_CPUS];
+
 void timer_handler(int sig, struct siginfo *unused_si, struct uml_pt_regs *regs)
 {
 	unsigned long flags;
@@ -780,12 +783,14 @@ void timer_handler(int sig, struct siginfo *unused_si, struct uml_pt_regs *regs)
 
 static int itimer_shutdown(struct clock_event_device *evt)
 {
+	int cpu = evt - &timer_clockevent[0];
+
 	if (time_travel_mode != TT_MODE_OFF)
 		time_travel_del_event(&time_travel_timer_event);
 
 	if (time_travel_mode != TT_MODE_INFCPU &&
 	    time_travel_mode != TT_MODE_EXTERNAL)
-		os_timer_disable();
+		os_timer_disable(cpu);
 
 	return 0;
 }
@@ -793,6 +798,7 @@ static int itimer_shutdown(struct clock_event_device *evt)
 static int itimer_set_periodic(struct clock_event_device *evt)
 {
 	unsigned long long interval = NSEC_PER_SEC / HZ;
+	int cpu = evt - &timer_clockevent[0];
 
 	if (time_travel_mode != TT_MODE_OFF) {
 		time_travel_del_event(&time_travel_timer_event);
@@ -805,7 +811,7 @@ static int itimer_set_periodic(struct clock_event_device *evt)
 
 	if (time_travel_mode != TT_MODE_INFCPU &&
 	    time_travel_mode != TT_MODE_EXTERNAL)
-		os_timer_set_interval(interval);
+		os_timer_set_interval(cpu, interval);
 
 	return 0;
 }
@@ -825,7 +831,7 @@ static int itimer_next_event(unsigned long delta,
 
 	if (time_travel_mode != TT_MODE_INFCPU &&
 	    time_travel_mode != TT_MODE_EXTERNAL)
-		return os_timer_one_shot(delta);
+		return os_timer_one_shot(raw_smp_processor_id(), delta);
 
 	return 0;
 }
@@ -835,10 +841,9 @@ static int itimer_one_shot(struct clock_event_device *evt)
 	return itimer_next_event(0, evt);
 }
 
-static struct clock_event_device timer_clockevent = {
+static struct clock_event_device _timer_clockevent = {
 	.name			= "posix-timer",
 	.rating			= 250,
-	.cpumask		= cpu_possible_mask,
 	.features		= CLOCK_EVT_FEAT_PERIODIC |
 				  CLOCK_EVT_FEAT_ONESHOT,
 	.set_state_shutdown	= itimer_shutdown,
@@ -856,6 +861,9 @@ static struct clock_event_device timer_clockevent = {
 
 static irqreturn_t um_timer(int irq, void *dev)
 {
+	int cpu = raw_smp_processor_id();
+	struct clock_event_device *evt = &timer_clockevent[cpu];
+
 	/*
 	 * Interrupt the (possibly) running userspace process, technically this
 	 * should only happen if userspace is currently executing.
@@ -867,7 +875,7 @@ static irqreturn_t um_timer(int irq, void *dev)
 	    get_current()->mm)
 		os_alarm_process(get_current()->mm->context.id.pid);
 
-	(*timer_clockevent.event_handler)(&timer_clockevent);
+	evt->event_handler(evt);
 
 	return IRQ_HANDLED;
 }
@@ -904,6 +912,23 @@ static struct clocksource timer_clocksource = {
 	.flags		= CLOCK_SOURCE_IS_CONTINUOUS,
 };
 
+int um_setup_timer(void)
+{
+	int cpu = raw_smp_processor_id();
+	struct clock_event_device *evt = &timer_clockevent[cpu];
+	int err;
+
+	err = os_timer_create();
+	if (err)
+		return err;
+
+	memcpy(evt, &_timer_clockevent, sizeof(*evt));
+	evt->cpumask = cpumask_of(cpu);
+	clockevents_register_device(evt);
+
+	return 0;
+}
+
 static void __init um_timer_setup(void)
 {
 	int err;
@@ -913,8 +938,8 @@ static void __init um_timer_setup(void)
 		printk(KERN_ERR "register_timer : request_irq failed - "
 		       "errno = %d\n", -err);
 
-	err = os_timer_create();
-	if (err != 0) {
+	err = um_setup_timer();
+	if (err) {
 		printk(KERN_ERR "creation of timer failed - errno = %d\n", -err);
 		return;
 	}
@@ -924,7 +949,6 @@ static void __init um_timer_setup(void)
 		printk(KERN_ERR "clocksource_register_hz returned %d\n", err);
 		return;
 	}
-	clockevents_register_device(&timer_clockevent);
 }
 
 void read_persistent_clock64(struct timespec64 *ts)
@@ -961,21 +985,21 @@ static int setup_time_travel(char *str)
 {
 	if (strcmp(str, "=inf-cpu") == 0) {
 		time_travel_mode = TT_MODE_INFCPU;
-		timer_clockevent.name = "time-travel-timer-infcpu";
+		_timer_clockevent.name = "time-travel-timer-infcpu";
 		timer_clocksource.name = "time-travel-clock";
 		return 1;
 	}
 
 	if (strncmp(str, "=ext:", 5) == 0) {
 		time_travel_mode = TT_MODE_EXTERNAL;
-		timer_clockevent.name = "time-travel-timer-external";
+		_timer_clockevent.name = "time-travel-timer-external";
 		timer_clocksource.name = "time-travel-clock-external";
 		return time_travel_connect_external(str + 5);
 	}
 
 	if (!*str) {
 		time_travel_mode = TT_MODE_BASIC;
-		timer_clockevent.name = "time-travel-timer";
+		_timer_clockevent.name = "time-travel-timer";
 		timer_clocksource.name = "time-travel-clock";
 		return 1;
 	}
diff --git a/arch/um/os-Linux/main.c b/arch/um/os-Linux/main.c
index 3c63ce19e3bf..730723106228 100644
--- a/arch/um/os-Linux/main.c
+++ b/arch/um/os-Linux/main.c
@@ -171,7 +171,7 @@ int __init main(int argc, char **argv, char **envp)
 	 */
 
 	/* stop timers and set timer signal to be ignored */
-	os_timer_disable();
+	os_timer_disable(0);
 
 	/* disable SIGIO for the fds and set SIGIO to be ignored */
 	err = deactivate_all_fds();
diff --git a/arch/um/os-Linux/time.c b/arch/um/os-Linux/time.c
index f25a4196bab7..e38b6f84287b 100644
--- a/arch/um/os-Linux/time.c
+++ b/arch/um/os-Linux/time.c
@@ -16,7 +16,7 @@
 #include <os.h>
 #include <string.h>
 
-static timer_t event_high_res_timer = 0;
+static timer_t event_high_res_timer[CONFIG_NR_CPUS] = { 0 };
 
 static inline long long timespec_to_ns(const struct timespec *ts)
 {
@@ -31,20 +31,30 @@ long long os_persistent_clock_emulation(void)
 	return timespec_to_ns(&realtime_tp);
 }
 
+#ifndef sigev_notify_thread_id
+#define sigev_notify_thread_id _sigev_un._tid
+#endif
+
 /**
  * os_timer_create() - create an new posix (interval) timer
  */
 int os_timer_create(void)
 {
-	timer_t *t = &event_high_res_timer;
+	timer_t *t = &event_high_res_timer[0];
+	struct sigevent sev = {
+		.sigev_notify = SIGEV_THREAD_ID,
+		.sigev_signo = SIGALRM,
+		.sigev_value.sival_ptr = t,
+		.sigev_notify_thread_id = gettid(),
+	};
 
-	if (timer_create(CLOCK_MONOTONIC, NULL, t) == -1)
+	if (timer_create(CLOCK_MONOTONIC, &sev, t) == -1)
 		return -1;
 
 	return 0;
 }
 
-int os_timer_set_interval(unsigned long long nsecs)
+int os_timer_set_interval(int cpu, unsigned long long nsecs)
 {
 	struct itimerspec its;
 
@@ -54,13 +64,13 @@ int os_timer_set_interval(unsigned long long nsecs)
 	its.it_interval.tv_sec = nsecs / UM_NSEC_PER_SEC;
 	its.it_interval.tv_nsec = nsecs % UM_NSEC_PER_SEC;
 
-	if (timer_settime(event_high_res_timer, 0, &its, NULL) == -1)
+	if (timer_settime(event_high_res_timer[cpu], 0, &its, NULL) == -1)
 		return -errno;
 
 	return 0;
 }
 
-int os_timer_one_shot(unsigned long long nsecs)
+int os_timer_one_shot(int cpu, unsigned long long nsecs)
 {
 	struct itimerspec its = {
 		.it_value.tv_sec = nsecs / UM_NSEC_PER_SEC,
@@ -70,19 +80,19 @@ int os_timer_one_shot(unsigned long long nsecs)
 		.it_interval.tv_nsec = 0, // we cheat here
 	};
 
-	timer_settime(event_high_res_timer, 0, &its, NULL);
+	timer_settime(event_high_res_timer[cpu], 0, &its, NULL);
 	return 0;
 }
 
 /**
  * os_timer_disable() - disable the posix (interval) timer
  */
-void os_timer_disable(void)
+void os_timer_disable(int cpu)
 {
 	struct itimerspec its;
 
 	memset(&its, 0, sizeof(struct itimerspec));
-	timer_settime(event_high_res_timer, 0, &its, NULL);
+	timer_settime(event_high_res_timer[cpu], 0, &its, NULL);
 }
 
 long long os_nsecs(void)
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 8/9] um: Support directing IO signals to calling thread
  2025-07-27  6:29 [PATCH 0/9] um: Add SMP support Tiwei Bie
                   ` (6 preceding siblings ...)
  2025-07-27  6:29 ` [PATCH 7/9] um: Define timers on a per-CPU basis Tiwei Bie
@ 2025-07-27  6:29 ` Tiwei Bie
  2025-07-27  6:29 ` [PATCH 9/9] um: Add initial SMP support Tiwei Bie
  8 siblings, 0 replies; 20+ messages in thread
From: Tiwei Bie @ 2025-07-27  6:29 UTC (permalink / raw)
  To: richard, anton.ivanov, johannes; +Cc: linux-um, tiwei.btw, tiwei.bie

From: Tiwei Bie <tiwei.btw@antgroup.com>

Extend os_set_fd_async() to allow the calling thread to direct
I/O availability signals to itself. After this change, existing
users will explicitly direct these signals to the CPU0 thread,
which was achieved by ignoring SIGIO in helper threads. This is
a preparation for adding SMP support.

Signed-off-by: Tiwei Bie <tiwei.btw@antgroup.com>
---
 arch/um/include/shared/os.h |  2 +-
 arch/um/kernel/irq.c        |  4 ++--
 arch/um/os-Linux/file.c     | 10 +++++++---
 3 files changed, 10 insertions(+), 6 deletions(-)

diff --git a/arch/um/include/shared/os.h b/arch/um/include/shared/os.h
index 0ca6e4548671..ca377421181d 100644
--- a/arch/um/include/shared/os.h
+++ b/arch/um/include/shared/os.h
@@ -155,7 +155,7 @@ extern int os_pread_file(int fd, void *buf, int len, unsigned long long offset);
 extern int os_pwrite_file(int fd, const void *buf, int count, unsigned long long offset);
 extern int os_file_modtime(const char *file, long long *modtime);
 extern int os_pipe(int *fd, int stream, int close_on_exec);
-extern int os_set_fd_async(int fd);
+extern int os_set_fd_async(int fd, int self);
 extern int os_clear_fd_async(int fd);
 extern int os_set_fd_block(int fd, int blocking);
 extern int os_accept_connection(int fd);
diff --git a/arch/um/kernel/irq.c b/arch/um/kernel/irq.c
index 5ed8014e43e4..193b6374d890 100644
--- a/arch/um/kernel/irq.c
+++ b/arch/um/kernel/irq.c
@@ -302,7 +302,7 @@ static int activate_fd(int irq, int fd, enum um_irq_type type, void *dev_id,
 	int err, events = os_event_mask(type);
 	unsigned long flags;
 
-	err = os_set_fd_async(fd);
+	err = os_set_fd_async(fd, 0);
 	if (err < 0)
 		goto out;
 
@@ -607,7 +607,7 @@ void um_irqs_resume(void)
 	raw_spin_lock_irqsave(&irq_lock, flags);
 	list_for_each_entry(entry, &active_fds, list) {
 		if (entry->suspended) {
-			int err = os_set_fd_async(entry->fd);
+			int err = os_set_fd_async(entry->fd, 0);
 
 			WARN(err < 0, "os_set_fd_async returned %d\n", err);
 			entry->suspended = false;
diff --git a/arch/um/os-Linux/file.c b/arch/um/os-Linux/file.c
index 617886d1fb1e..2544a0fc9fbb 100644
--- a/arch/um/os-Linux/file.c
+++ b/arch/um/os-Linux/file.c
@@ -391,8 +391,12 @@ int os_pipe(int *fds, int stream, int close_on_exec)
 	return err;
 }
 
-int os_set_fd_async(int fd)
+int os_set_fd_async(int fd, int self)
 {
+	struct f_owner_ex owner = {
+		.type = F_OWNER_TID,
+		.pid = self ? gettid() : os_getpid(),
+	};
 	int err, flags;
 
 	flags = fcntl(fd, F_GETFL);
@@ -408,9 +412,9 @@ int os_set_fd_async(int fd)
 	}
 
 	if ((fcntl(fd, F_SETSIG, SIGIO) < 0) ||
-	    (fcntl(fd, F_SETOWN, os_getpid()) < 0)) {
+	    (fcntl(fd, F_SETOWN_EX, &owner) < 0)) {
 		err = -errno;
-		printk(UM_KERN_ERR "os_set_fd_async : Failed to fcntl F_SETOWN "
+		printk(UM_KERN_ERR "os_set_fd_async : Failed to fcntl F_SETOWN_EX "
 		       "(or F_SETSIG) fd %d, errno = %d\n", fd, errno);
 		return err;
 	}
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 9/9] um: Add initial SMP support
  2025-07-27  6:29 [PATCH 0/9] um: Add SMP support Tiwei Bie
                   ` (7 preceding siblings ...)
  2025-07-27  6:29 ` [PATCH 8/9] um: Support directing IO signals to calling thread Tiwei Bie
@ 2025-07-27  6:29 ` Tiwei Bie
  2025-07-28 10:47   ` Johannes Berg
  2025-07-28 13:55   ` Johannes Berg
  8 siblings, 2 replies; 20+ messages in thread
From: Tiwei Bie @ 2025-07-27  6:29 UTC (permalink / raw)
  To: richard, anton.ivanov, johannes; +Cc: linux-um, tiwei.btw, tiwei.bie

From: Tiwei Bie <tiwei.btw@antgroup.com>

Add initial symmetric multi-processing (SMP) support to UML. With
this support, it is now possible for users to tell UML to start
multiple virtual CPUs, each represented as a separate host thread.

In UML, kthreads and normal threads (when running in kernel mode)
can be scheduled and executed simultaneously on different virtual
CPUs. However, the userspace code of normal threads still runs
within their single-threaded stubs, meaning that true SMP support
in userspace is not yet realized within the same process.

Signed-off-by: Tiwei Bie <tiwei.btw@antgroup.com>
---
 arch/um/Kconfig                         |  39 +++-
 arch/um/include/asm/Kbuild              |   3 +-
 arch/um/include/asm/current.h           |   5 +-
 arch/um/include/asm/hardirq.h           |  24 ++-
 arch/um/include/asm/mmu.h               |  10 +
 arch/um/include/asm/percpu.h            |  20 ++
 arch/um/include/asm/pgtable.h           |   2 +
 arch/um/include/asm/processor-generic.h |   6 +
 arch/um/include/asm/smp.h               |  23 +-
 arch/um/include/asm/spinlock.h          |   8 +
 arch/um/include/linux/smp-internal.h    |  21 ++
 arch/um/include/shared/kern_util.h      |   1 +
 arch/um/include/shared/os.h             |   6 +
 arch/um/include/shared/skas/mm_id.h     |   3 +
 arch/um/include/shared/smp.h            |  19 ++
 arch/um/kernel/Makefile                 |   1 +
 arch/um/kernel/irq.c                    |  29 +++
 arch/um/kernel/mem.c                    |   2 +
 arch/um/kernel/process.c                |   8 +-
 arch/um/kernel/skas/mmu.c               |  32 ++-
 arch/um/kernel/smp.c                    | 266 ++++++++++++++++++++++++
 arch/um/kernel/tlb.c                    |   5 +-
 arch/um/kernel/trap.c                   |   2 +-
 arch/um/kernel/um_arch.c                |  24 ++-
 arch/um/os-Linux/Makefile               |   4 +-
 arch/um/os-Linux/main.c                 |   3 +-
 arch/um/os-Linux/process.c              |  10 +
 arch/um/os-Linux/skas/process.c         |   9 +
 arch/um/os-Linux/smp.c                  |  86 ++++++++
 arch/um/os-Linux/start_up.c             |   4 +
 arch/um/os-Linux/time.c                 |   7 +-
 31 files changed, 655 insertions(+), 27 deletions(-)
 create mode 100644 arch/um/include/asm/percpu.h
 create mode 100644 arch/um/include/asm/spinlock.h
 create mode 100644 arch/um/include/linux/smp-internal.h
 create mode 100644 arch/um/include/shared/smp.h
 create mode 100644 arch/um/kernel/smp.c
 create mode 100644 arch/um/os-Linux/smp.c

diff --git a/arch/um/Kconfig b/arch/um/Kconfig
index 9083bfdb7735..9678fe25be22 100644
--- a/arch/um/Kconfig
+++ b/arch/um/Kconfig
@@ -30,6 +30,7 @@ config UML
 	select HAVE_GCC_PLUGINS
 	select ARCH_SUPPORTS_LTO_CLANG
 	select ARCH_SUPPORTS_LTO_CLANG_THIN
+	select ARCH_USE_QUEUED_RWLOCKS
 	select TRACE_IRQFLAGS_SUPPORT
 	select TTY # Needed for line.c
 	select HAVE_ARCH_VMAP_STACK
@@ -79,10 +80,41 @@ config HZ
 	int
 	default 100
 
-config NR_CPUS
+config SMP
+	bool "Symmetric multi-processing support"
+	default n
+	help
+	  This option enables UML SMP support.
+
+	  With this enabled, it is possible for users to tell UML to start
+	  multiple virtual processors. Each virtual processor is represented
+	  as a separate host thread.
+
+	  In UML, kthreads and normal threads (when running in kernel mode)
+	  can be scheduled and executed simultaneously on different virtual
+	  processors. However, the userspace code of normal threads still
+	  runs within their respective single-threaded stubs, meaning that
+	  true SMP support in userspace is not yet realized within the same
+	  process.
+
+config NR_CPUS_RANGE_BEGIN
+	int
+	default 1 if !SMP
+	default 2
+
+config NR_CPUS_RANGE_END
+	int
+	default 256
+
+config NR_CPUS_DEFAULT
 	int
-	range 1 1
-	default 1
+	default 2 if  SMP
+	default 1 if !SMP
+
+config NR_CPUS
+	int "Maximum number of CPUs" if SMP
+	range NR_CPUS_RANGE_BEGIN NR_CPUS_RANGE_END
+	default NR_CPUS_DEFAULT
 
 source "arch/$(HEADER_ARCH)/um/Kconfig"
 
@@ -258,6 +290,7 @@ source "arch/um/drivers/Kconfig"
 
 config ARCH_SUSPEND_POSSIBLE
 	def_bool y
+	depends on !SMP
 
 menu "Power management options"
 
diff --git a/arch/um/include/asm/Kbuild b/arch/um/include/asm/Kbuild
index 04ab3b653a48..22c6409e7815 100644
--- a/arch/um/include/asm/Kbuild
+++ b/arch/um/include/asm/Kbuild
@@ -17,10 +17,11 @@ generic-y += module.h
 generic-y += module.lds.h
 generic-y += param.h
 generic-y += parport.h
-generic-y += percpu.h
 generic-y += preempt.h
+generic-y += qrwlock.h
 generic-y += runtime-const.h
 generic-y += softirq_stack.h
+generic-y += spinlock_types.h
 generic-y += switch_to.h
 generic-y += topology.h
 generic-y += trace_clock.h
diff --git a/arch/um/include/asm/current.h b/arch/um/include/asm/current.h
index 8accc6d6f502..c563af70dcf2 100644
--- a/arch/um/include/asm/current.h
+++ b/arch/um/include/asm/current.h
@@ -7,15 +7,16 @@
 
 #ifndef __ASSEMBLER__
 
+#include <asm/smp.h>
+
 struct task_struct;
 extern struct task_struct *cpu_tasks[NR_CPUS];
 
 static __always_inline struct task_struct *get_current(void)
 {
-	return cpu_tasks[0];
+	return cpu_tasks[raw_smp_processor_id()];
 }
 
-
 #define current get_current()
 
 #endif /* __ASSEMBLER__ */
diff --git a/arch/um/include/asm/hardirq.h b/arch/um/include/asm/hardirq.h
index 52e2c36267a9..8de71752a9b8 100644
--- a/arch/um/include/asm/hardirq.h
+++ b/arch/um/include/asm/hardirq.h
@@ -2,8 +2,30 @@
 #ifndef __ASM_UM_HARDIRQ_H
 #define __ASM_UM_HARDIRQ_H
 
-#include <asm-generic/hardirq.h>
+#include <linux/cache.h>
+#include <linux/threads.h>
 
 #define __ARCH_IRQ_EXIT_IRQS_DISABLED 1
 
+typedef struct {
+	unsigned int __softirq_pending;
+#if IS_ENABLED(CONFIG_SMP)
+	unsigned int irq_resched_count;
+	unsigned int irq_call_count;
+#endif
+} ____cacheline_aligned irq_cpustat_t;
+
+DECLARE_PER_CPU_SHARED_ALIGNED(irq_cpustat_t, irq_stat);
+
+#define __ARCH_IRQ_STAT
+
+#define inc_irq_stat(member)	this_cpu_inc(irq_stat.member)
+
+#include <linux/irq.h>
+
+static inline void ack_bad_irq(unsigned int irq)
+{
+	pr_crit("unexpected IRQ trap at vector %02x\n", irq);
+}
+
 #endif /* __ASM_UM_HARDIRQ_H */
diff --git a/arch/um/include/asm/mmu.h b/arch/um/include/asm/mmu.h
index 4d0e4239f3cc..07d48738b402 100644
--- a/arch/um/include/asm/mmu.h
+++ b/arch/um/include/asm/mmu.h
@@ -7,16 +7,26 @@
 #define __ARCH_UM_MMU_H
 
 #include "linux/types.h"
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
 #include <mm_id.h>
 
 typedef struct mm_context {
 	struct mm_id id;
+	struct mutex turnstile;
 
 	struct list_head list;
 
 	/* Address range in need of a TLB sync */
+	spinlock_t sync_tlb_lock;
 	unsigned long sync_tlb_range_from;
 	unsigned long sync_tlb_range_to;
 } mm_context_t;
 
+#define INIT_MM_CONTEXT(mm)						\
+	.context = {							\
+		.turnstile = __MUTEX_INITIALIZER(mm.context.turnstile),	\
+		.sync_tlb_lock = __SPIN_LOCK_INITIALIZER(mm.context.sync_tlb_lock), \
+	}
+
 #endif
diff --git a/arch/um/include/asm/percpu.h b/arch/um/include/asm/percpu.h
new file mode 100644
index 000000000000..77e13a38f220
--- /dev/null
+++ b/arch/um/include/asm/percpu.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __ASM_UM_PERCPU_H
+#define __ASM_UM_PERCPU_H
+
+#include <linux/threads.h>
+
+#if IS_ENABLED(CONFIG_SMP)
+
+#define __per_cpu_offset __per_cpu_offset
+#ifndef __ASSEMBLER__
+extern unsigned long __per_cpu_offset[NR_CPUS];
+#endif
+
+#define per_cpu_offset(x) (__per_cpu_offset[x])
+
+#endif /* CONFIG_SMP */
+
+#include <asm-generic/percpu.h>
+
+#endif /* __ASM_UM_PERCPU_H */
diff --git a/arch/um/include/asm/pgtable.h b/arch/um/include/asm/pgtable.h
index 24fdea6f88c3..91aec3698475 100644
--- a/arch/um/include/asm/pgtable.h
+++ b/arch/um/include/asm/pgtable.h
@@ -225,6 +225,8 @@ static inline void set_pte(pte_t *pteptr, pte_t pteval)
 static inline void um_tlb_mark_sync(struct mm_struct *mm, unsigned long start,
 				    unsigned long end)
 {
+	guard(spinlock_irqsave)(&mm->context.sync_tlb_lock);
+
 	if (!mm->context.sync_tlb_range_to) {
 		mm->context.sync_tlb_range_from = start;
 		mm->context.sync_tlb_range_to = end;
diff --git a/arch/um/include/asm/processor-generic.h b/arch/um/include/asm/processor-generic.h
index 236fdfd7cdbe..0c4dd680f8bb 100644
--- a/arch/um/include/asm/processor-generic.h
+++ b/arch/um/include/asm/processor-generic.h
@@ -81,6 +81,12 @@ struct cpuinfo_um {
 
 extern struct cpuinfo_um boot_cpu_data;
 
+#if IS_ENABLED(CONFIG_SMP)
+extern struct cpuinfo_um uml_cpu_data[];
+#else
+#define uml_cpu_data     (&boot_cpu_data)
+#endif
+
 #define cache_line_size()	(boot_cpu_data.cache_alignment)
 
 #define KSTK_REG(tsk, reg) get_thread_reg(reg, &tsk->thread.switch_buf)
diff --git a/arch/um/include/asm/smp.h b/arch/um/include/asm/smp.h
index a8cc1d46ddcb..242ad4da5723 100644
--- a/arch/um/include/asm/smp.h
+++ b/arch/um/include/asm/smp.h
@@ -2,6 +2,27 @@
 #ifndef __UM_SMP_H
 #define __UM_SMP_H
 
-#define hard_smp_processor_id()		0
+#if IS_ENABLED(CONFIG_SMP)
+
+#include <linux/bitops.h>
+#include <asm/current.h>
+#include <linux/cpumask.h>
+#include <shared/smp.h>
+
+#define raw_smp_processor_id() uml_curr_cpu()
+
+void arch_smp_send_reschedule(int cpu);
+
+void arch_send_call_function_single_ipi(int cpu);
+
+void arch_send_call_function_ipi_mask(const struct cpumask *mask);
+
+static inline void smp_cpus_done(unsigned int maxcpus) { }
+
+#else /* !CONFIG_SMP */
+
+#define raw_smp_processor_id() 0
+
+#endif /* CONFIG_SMP */
 
 #endif
diff --git a/arch/um/include/asm/spinlock.h b/arch/um/include/asm/spinlock.h
new file mode 100644
index 000000000000..f2258443c316
--- /dev/null
+++ b/arch/um/include/asm/spinlock.h
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __ASM_UM_SPINLOCK_H
+#define __ASM_UM_SPINLOCK_H
+
+#include <asm/processor.h>
+#include <asm-generic/spinlock.h>
+
+#endif /* __ASM_UM_SPINLOCK_H */
diff --git a/arch/um/include/linux/smp-internal.h b/arch/um/include/linux/smp-internal.h
new file mode 100644
index 000000000000..e7ad2b879cbb
--- /dev/null
+++ b/arch/um/include/linux/smp-internal.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __UM_SMP_INTERNAL_H
+#define __UM_SMP_INTERNAL_H
+
+#if IS_ENABLED(CONFIG_SMP)
+
+int smp_sigio_handler(struct uml_pt_regs *regs);
+void prefill_possible_map(void);
+
+#else /* !CONFIG_SMP */
+
+static inline int smp_sigio_handler(struct uml_pt_regs *regs)
+{
+	return 0;
+}
+
+static inline void prefill_possible_map(void) { }
+
+#endif /* CONFIG_SMP */
+
+#endif /* __UM_SMP_INTERNAL_H */
diff --git a/arch/um/include/shared/kern_util.h b/arch/um/include/shared/kern_util.h
index 3daaa5c4b35d..52db56400a06 100644
--- a/arch/um/include/shared/kern_util.h
+++ b/arch/um/include/shared/kern_util.h
@@ -14,6 +14,7 @@ struct siginfo;
 extern int uml_exitcode;
 
 extern int kmalloc_ok;
+extern int disable_kmalloc[];
 
 #define UML_ROUND_UP(addr) \
 	((((unsigned long) addr) + PAGE_SIZE - 1) & PAGE_MASK)
diff --git a/arch/um/include/shared/os.h b/arch/um/include/shared/os.h
index ca377421181d..e26655a7763e 100644
--- a/arch/um/include/shared/os.h
+++ b/arch/um/include/shared/os.h
@@ -216,6 +216,8 @@ extern int can_drop_memory(void);
 
 void os_set_pdeathsig(void);
 
+int os_futex_wake(void *uaddr, unsigned int val);
+
 /* execvp.c */
 extern int execvp_noalloc(char *buf, const char *file, char *const argv[]);
 /* helper.c */
@@ -339,4 +341,8 @@ extern void um_trace_signals_off(void);
 /* time-travel */
 extern void deliver_time_travel_irqs(void);
 
+/* smp.c */
+int start_cpu_thread(int cpu);
+void start_idle_thread_secondary(void *arg, jmp_buf *switch_buf);
+
 #endif
diff --git a/arch/um/include/shared/skas/mm_id.h b/arch/um/include/shared/skas/mm_id.h
index 4f977ef5dda5..bb9f8bd32ccb 100644
--- a/arch/um/include/shared/skas/mm_id.h
+++ b/arch/um/include/shared/skas/mm_id.h
@@ -19,6 +19,9 @@ struct mm_id {
 	int syscall_fd_map[STUB_MAX_FDS];
 };
 
+void enter_turnstile(struct mm_id *mm_id);
+void exit_turnstile(struct mm_id *mm_id);
+
 void notify_mm_kill(int pid);
 
 #endif
diff --git a/arch/um/include/shared/smp.h b/arch/um/include/shared/smp.h
new file mode 100644
index 000000000000..c1049835bd1d
--- /dev/null
+++ b/arch/um/include/shared/smp.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __UM_SHARED_SMP_H
+#define __UM_SHARED_SMP_H
+
+#if IS_ENABLED(CONFIG_SMP)
+
+extern int uml_ncpus;
+
+int uml_curr_cpu(void);
+void uml_start_idle(void *opaque);
+
+#else /* !CONFIG_SMP */
+
+#define uml_ncpus 1
+#define uml_curr_cpu() 0
+
+#endif /* CONFIG_SMP */
+
+#endif /* __UM_SHARED_SMP_H */
diff --git a/arch/um/kernel/Makefile b/arch/um/kernel/Makefile
index b8f4e9281599..be60bc451b3f 100644
--- a/arch/um/kernel/Makefile
+++ b/arch/um/kernel/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_GPROF)	+= gprof_syms.o
 obj-$(CONFIG_OF) += dtb.o
 obj-$(CONFIG_EARLY_PRINTK) += early_printk.o
 obj-$(CONFIG_STACKTRACE) += stacktrace.o
+obj-$(CONFIG_SMP) += smp.o
 
 USER_OBJS := config.o
 
diff --git a/arch/um/kernel/irq.c b/arch/um/kernel/irq.c
index 193b6374d890..c57cfbef6f2b 100644
--- a/arch/um/kernel/irq.c
+++ b/arch/um/kernel/irq.c
@@ -20,8 +20,12 @@
 #include <os.h>
 #include <irq_user.h>
 #include <irq_kern.h>
+#include <linux/smp-internal.h>
 #include <linux/time-internal.h>
 
+DEFINE_PER_CPU_SHARED_ALIGNED(irq_cpustat_t, irq_stat);
+
+#define irq_stats(x)		(&per_cpu(irq_stat, x))
 
 /* When epoll triggers we do not know why it did so
  * we can also have different IRQs for read and write.
@@ -205,6 +209,9 @@ static void _sigio_handler(struct uml_pt_regs *regs,
 	if (!irqs_suspended)
 		irq_do_pending_events(timetravel_handlers_only);
 
+	if (smp_sigio_handler(regs))
+		return;
+
 	while (1) {
 		/* This is now lockless - epoll keeps back-referencesto the irqs
 		 * which have trigger it so there is no need to walk the irq
@@ -696,3 +703,25 @@ void sigchld_handler(int sig, struct siginfo *unused_si,
 {
 	do_IRQ(SIGCHLD_IRQ, regs);
 }
+
+/*
+ * /proc/interrupts printing for arch specific interrupts
+ */
+int arch_show_interrupts(struct seq_file *p, int prec)
+{
+#if IS_ENABLED(CONFIG_SMP)
+	int cpu;
+
+	seq_printf(p, "%*s: ", prec, "RES");
+	for_each_online_cpu(cpu)
+		seq_printf(p, "%10u ", irq_stats(cpu)->irq_resched_count);
+	seq_puts(p, "  Rescheduling interrupts\n");
+
+	seq_printf(p, "%*s: ", prec, "CAL");
+	for_each_online_cpu(cpu)
+		seq_printf(p, "%10u ", irq_stats(cpu)->irq_call_count);
+	seq_puts(p, "  Function call interrupts\n");
+#endif
+
+	return 0;
+}
diff --git a/arch/um/kernel/mem.c b/arch/um/kernel/mem.c
index 76bec7de81b5..8e7742140e93 100644
--- a/arch/um/kernel/mem.c
+++ b/arch/um/kernel/mem.c
@@ -53,6 +53,8 @@ pgd_t swapper_pg_dir[PTRS_PER_PGD];
 /* Initialized at boot time, and readonly after that */
 int kmalloc_ok = 0;
 
+int disable_kmalloc[NR_CPUS] = { 0 };
+
 /* Used during early boot */
 static unsigned long brk_end;
 
diff --git a/arch/um/kernel/process.c b/arch/um/kernel/process.c
index 01b935c00454..ec390f334853 100644
--- a/arch/um/kernel/process.c
+++ b/arch/um/kernel/process.c
@@ -35,6 +35,7 @@
 #include <os.h>
 #include <skas.h>
 #include <registers.h>
+#include <linux/smp-internal.h>
 #include <linux/time-internal.h>
 #include <linux/elfcore.h>
 
@@ -185,11 +186,12 @@ int copy_thread(struct task_struct * p, const struct kernel_clone_args *args)
 
 void initial_thread_cb(void (*proc)(void *), void *arg)
 {
-	int save_kmalloc_ok = kmalloc_ok;
+	int cpu = raw_smp_processor_id();
+	int save_kmalloc = disable_kmalloc[cpu];
 
-	kmalloc_ok = 0;
+	disable_kmalloc[cpu] = 1;
 	initial_thread_cb_skas(proc, arg);
-	kmalloc_ok = save_kmalloc_ok;
+	disable_kmalloc[cpu] = save_kmalloc;
 }
 
 int arch_dup_task_struct(struct task_struct *dst,
diff --git a/arch/um/kernel/skas/mmu.c b/arch/um/kernel/skas/mmu.c
index afe9a2f251ef..d3a71008f43e 100644
--- a/arch/um/kernel/skas/mmu.c
+++ b/arch/um/kernel/skas/mmu.c
@@ -23,12 +23,30 @@ static_assert(sizeof(struct stub_data) == STUB_DATA_PAGES * UM_KERN_PAGE_SIZE);
 static spinlock_t mm_list_lock;
 static struct list_head mm_list;
 
+void enter_turnstile(struct mm_id *mm_id)
+{
+	struct mm_context *ctx = container_of(mm_id, struct mm_context, id);
+
+	mutex_lock(&ctx->turnstile);
+}
+
+void exit_turnstile(struct mm_id *mm_id)
+{
+	struct mm_context *ctx = container_of(mm_id, struct mm_context, id);
+
+	mutex_unlock(&ctx->turnstile);
+}
+
 int init_new_context(struct task_struct *task, struct mm_struct *mm)
 {
 	struct mm_id *new_id = &mm->context.id;
 	unsigned long stack = 0;
 	int ret = -ENOMEM;
 
+	mutex_init(&mm->context.turnstile);
+
+	spin_lock_init(&mm->context.sync_tlb_lock);
+
 	stack = __get_free_pages(GFP_KERNEL | __GFP_ZERO, ilog2(STUB_DATA_PAGES));
 	if (stack == 0)
 		goto out;
@@ -73,6 +91,9 @@ void destroy_context(struct mm_struct *mm)
 		return;
 	}
 
+	scoped_guard(spinlock_irqsave, &mm_list_lock)
+		list_del(&mm->context.list);
+
 	if (mmu->id.pid > 0) {
 		os_kill_ptraced_process(mmu->id.pid, 1);
 		mmu->id.pid = -1;
@@ -82,10 +103,6 @@ void destroy_context(struct mm_struct *mm)
 		os_close_file(mmu->id.sock);
 
 	free_pages(mmu->id.stack, ilog2(STUB_DATA_PAGES));
-
-	guard(spinlock_irqsave)(&mm_list_lock);
-
-	list_del(&mm->context.list);
 }
 
 static irqreturn_t mm_sigchld_irq(int irq, void* dev)
@@ -110,12 +127,11 @@ static irqreturn_t mm_sigchld_irq(int irq, void* dev)
 				/* Marks the MM as dead */
 				mm_context->id.pid = -1;
 
-				/*
-				 * NOTE: If SMP is implemented, a futex_wake
-				 * needs to be added here.
-				 */
 				stub_data = (void *)mm_context->id.stack;
 				stub_data->futex = FUTEX_IN_KERN;
+#if IS_ENABLED(CONFIG_SMP)
+				os_futex_wake(&stub_data->futex, 1);
+#endif
 
 				/*
 				 * NOTE: Currently executing syscalls by
diff --git a/arch/um/kernel/smp.c b/arch/um/kernel/smp.c
new file mode 100644
index 000000000000..53bd57dda13a
--- /dev/null
+++ b/arch/um/kernel/smp.c
@@ -0,0 +1,266 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2000 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
+ * Copyright (C) 2025 Ant Group
+ *     Author: Tiwei Bie <tiwei.btw@antgroup.com>
+ */
+
+#include <linux/sched.h>
+#include <linux/sched/task.h>
+#include <linux/sched/task_stack.h>
+#include <linux/module.h>
+#include <linux/threads.h>
+#include <linux/interrupt.h>
+#include <linux/err.h>
+#include <linux/hardirq.h>
+#include <linux/cpu.h>
+#include <linux/smp-internal.h>
+#include <asm/smp.h>
+#include <asm/processor.h>
+#include <init.h>
+#include <kern.h>
+#include <os.h>
+#include <smp.h>
+
+/*
+ * Per CPU bogomips and other parameters
+ * The only piece used here is the ipi pipe, which is set before SMP is
+ * started and never changed.
+ */
+struct cpuinfo_um uml_cpu_data[NR_CPUS];
+
+void arch_smp_send_reschedule(int cpu)
+{
+	os_write_file(uml_cpu_data[cpu].ipi_pipe[1], "R", 1);
+}
+
+void arch_send_call_function_single_ipi(int cpu)
+{
+	os_write_file(uml_cpu_data[cpu].ipi_pipe[1], "I", 1);
+}
+
+void arch_send_call_function_ipi_mask(const struct cpumask *mask)
+{
+	int cpu;
+
+	for_each_cpu(cpu, mask)
+		os_write_file(uml_cpu_data[cpu].ipi_pipe[1], "M", 1);
+}
+
+void smp_send_stop(void)
+{
+	int cpu;
+
+	pr_info("Stopping all CPUs...");
+	for (cpu = 0; cpu < num_online_cpus(); cpu++) {
+		if (cpu == current_thread_info()->cpu)
+			continue;
+		os_write_file(uml_cpu_data[cpu].ipi_pipe[1], "S", 1);
+	}
+	pr_cont(" done\n");
+}
+
+static cpumask_t smp_commenced_mask = CPU_MASK_NONE;
+static cpumask_t cpu_callin_map = CPU_MASK_NONE;
+
+static int idle_proc(void *unused)
+{
+	int err, cpu = raw_smp_processor_id();
+
+	err = os_pipe(uml_cpu_data[cpu].ipi_pipe, 1, 1);
+	if (err)
+		panic("CPU#%d failed to create IPI pipe, err = %d", cpu, err);
+
+	os_set_fd_async(uml_cpu_data[cpu].ipi_pipe[0], 1);
+
+	wmb();
+	if (cpumask_test_and_set_cpu(cpu, &cpu_callin_map)) {
+		pr_err("huh, CPU#%d already present??\n", cpu);
+		BUG();
+	}
+
+	while (!cpumask_test_cpu(cpu, &smp_commenced_mask))
+		cpu_relax();
+
+	notify_cpu_starting(cpu);
+	set_cpu_online(cpu, true);
+
+	err = um_setup_timer();
+	if (err)
+		panic("CPU#%d failed to setup timer, err = %d", cpu, err);
+
+	cpu_startup_entry(CPUHP_AP_ONLINE_IDLE);
+	return 0;
+}
+
+static struct task_struct *idle_threads[NR_CPUS];
+static char irqstacks[NR_CPUS][THREAD_SIZE] __aligned(THREAD_SIZE);
+
+void uml_start_idle(void *opaque)
+{
+	int cpu = raw_smp_processor_id();
+	struct mm_struct *mm = &init_mm;
+	struct task_struct *p = idle_threads[cpu];
+
+	p->thread_info.cpu = cpu;
+
+	stack_protections((unsigned long) &irqstacks[cpu]);
+	set_sigstack(&irqstacks[cpu], THREAD_SIZE);
+
+	mmgrab(mm);
+	p->active_mm = mm;
+
+	p->thread.request.thread.proc = idle_proc;
+	p->thread.request.thread.arg = NULL;
+
+	new_thread(task_stack_page(p), &p->thread.switch_buf, new_thread_handler);
+	start_idle_thread_secondary(opaque, &p->thread.switch_buf);
+}
+
+static struct task_struct *new_idle_thread(int cpu)
+{
+	struct task_struct *new_task;
+
+	new_task = fork_idle(cpu);
+	if (IS_ERR(new_task))
+		panic("%s: fork_idle failed, err = %ld", __func__,
+		      PTR_ERR(new_task));
+
+	cpu_tasks[cpu] = new_task;
+	return new_task;
+}
+
+void __init smp_prepare_cpus(unsigned int maxcpus)
+{
+	unsigned long waittime;
+	int err, cpu, me = smp_processor_id();
+
+	set_cpu_online(me, true);
+	cpumask_set_cpu(me, &cpu_callin_map);
+
+	err = os_pipe(uml_cpu_data[me].ipi_pipe, 1, 1);
+	if (err)
+		panic("CPU#0 failed to create IPI pipe, err = %d", err);
+
+	os_set_fd_async(uml_cpu_data[me].ipi_pipe[0], 1);
+
+	for (cpu = 1; cpu < uml_ncpus; cpu++) {
+		pr_info("Booting processor %d...", cpu);
+
+		idle_threads[cpu] = new_idle_thread(cpu);
+		err = start_cpu_thread(cpu);
+		if (err)
+			panic("CPU#%d failed to start cpu thread, err = %d", cpu, err);
+
+		waittime = 200000000;
+		while (waittime-- && !cpumask_test_cpu(cpu, &cpu_callin_map))
+			cpu_relax();
+
+		pr_cont(" %s\n", cpumask_test_cpu(cpu, &cpu_callin_map) ? "done" : "failed");
+		set_cpu_present(cpu, true);
+	}
+}
+
+void smp_prepare_boot_cpu(void)
+{
+	set_cpu_online(smp_processor_id(), true);
+}
+
+int __cpu_up(unsigned int cpu, struct task_struct *tidle)
+{
+	cpumask_set_cpu(cpu, &smp_commenced_mask);
+	while (!cpu_online(cpu))
+		mb();
+	return 0;
+}
+
+static void IPI_handler(int cpu, struct uml_pt_regs *regs)
+{
+	struct pt_regs *old_regs = set_irq_regs((struct pt_regs *)regs);
+	unsigned char c;
+	int fd;
+
+	irq_enter();
+
+	if (current->mm)
+		os_alarm_process(current->mm->context.id.pid);
+
+	fd = uml_cpu_data[cpu].ipi_pipe[0];
+	while (os_read_file(fd, &c, 1) == 1) {
+		switch (c) {
+		case 'R':
+			inc_irq_stat(irq_resched_count);
+			scheduler_ipi();
+			break;
+
+		case 'S':
+			pr_info("CPU#%d stopping\n", cpu);
+			while (1)
+				pause();
+			break;
+
+		case 'I':
+			inc_irq_stat(irq_call_count);
+			generic_smp_call_function_single_interrupt();
+			break;
+
+		case 'M':
+			inc_irq_stat(irq_call_count);
+			generic_smp_call_function_interrupt();
+			break;
+
+		default:
+			pr_err("CPU#%d received unknown IPI [%c]!\n", cpu, c);
+			break;
+		}
+	}
+
+	irq_exit();
+	set_irq_regs(old_regs);
+}
+
+int smp_sigio_handler(struct uml_pt_regs *regs)
+{
+	int cpu = raw_smp_processor_id();
+
+	IPI_handler(cpu, regs);
+	if (cpu != 0)
+		return 1;
+	return 0;
+}
+
+EXPORT_SYMBOL(uml_curr_cpu);
+
+/* Set in uml_ncpus_setup */
+int uml_ncpus = 1;
+
+void __init prefill_possible_map(void)
+{
+	int cpu;
+
+	for (cpu = 0; cpu < uml_ncpus; cpu++)
+		set_cpu_possible(cpu, true);
+	for (; cpu < NR_CPUS; cpu++)
+		set_cpu_possible(cpu, false);
+}
+
+static int __init uml_ncpus_setup(char *line, int *add)
+{
+	*add = 0;
+
+	if (kstrtoint(line, 10, &uml_ncpus)) {
+		os_warn("%s: Couldn't parse '%s'\n", __func__, line);
+		return -1;
+	}
+
+	uml_ncpus = clamp(uml_ncpus, 1, NR_CPUS);
+
+	return 0;
+}
+
+__uml_setup("ncpus=", uml_ncpus_setup,
+"ncpus=<# of desired CPUs>\n"
+"    This tells UML how many virtual processors to start. The maximum\n"
+"    number of supported virtual processors can be obtained by querying\n"
+"    the CONFIG_NR_CPUS option using --showconfig.\n\n"
+);
diff --git a/arch/um/kernel/tlb.c b/arch/um/kernel/tlb.c
index cf7e0d4407f2..39608cccf2c6 100644
--- a/arch/um/kernel/tlb.c
+++ b/arch/um/kernel/tlb.c
@@ -162,9 +162,11 @@ int um_tlb_sync(struct mm_struct *mm)
 {
 	pgd_t *pgd;
 	struct vm_ops ops;
-	unsigned long addr = mm->context.sync_tlb_range_from, next;
+	unsigned long addr, next;
 	int ret = 0;
 
+	guard(spinlock_irqsave)(&mm->context.sync_tlb_lock);
+
 	if (mm->context.sync_tlb_range_to == 0)
 		return 0;
 
@@ -177,6 +179,7 @@ int um_tlb_sync(struct mm_struct *mm)
 		ops.unmap = unmap;
 	}
 
+	addr = mm->context.sync_tlb_range_from;
 	pgd = pgd_offset(mm, addr);
 	do {
 		next = pgd_addr_end(addr, mm->context.sync_tlb_range_to);
diff --git a/arch/um/kernel/trap.c b/arch/um/kernel/trap.c
index 5b80a3a89c20..177615820a4c 100644
--- a/arch/um/kernel/trap.c
+++ b/arch/um/kernel/trap.c
@@ -316,7 +316,7 @@ unsigned long segv(struct faultinfo fi, unsigned long ip, int is_user,
 	if (!is_user && regs)
 		current->thread.segv_regs = container_of(regs, struct pt_regs, regs);
 
-	if (!is_user && init_mm.context.sync_tlb_range_to) {
+	if (!is_user && address >= start_vm && address < end_vm) {
 		/*
 		 * Kernel has pending updates from set_ptes that were not
 		 * flushed yet. Syncing them should fix the pagefault (if not
diff --git a/arch/um/kernel/um_arch.c b/arch/um/kernel/um_arch.c
index 2f5ee045bc7a..a84fba9992f2 100644
--- a/arch/um/kernel/um_arch.c
+++ b/arch/um/kernel/um_arch.c
@@ -19,6 +19,7 @@
 #include <linux/kmsg_dump.h>
 #include <linux/suspend.h>
 #include <linux/random.h>
+#include <linux/smp-internal.h>
 
 #include <asm/processor.h>
 #include <asm/cpufeature.h>
@@ -32,6 +33,7 @@
 #include <kern_util.h>
 #include <mem_user.h>
 #include <os.h>
+#include <smp.h>
 
 #include "um_arch.h"
 
@@ -74,6 +76,12 @@ static int show_cpuinfo(struct seq_file *m, void *v)
 {
 	int i = 0;
 
+#if IS_ENABLED(CONFIG_SMP)
+	i = (struct cpuinfo_um *) v - uml_cpu_data;
+	if (!cpu_online(i))
+		return 0;
+#endif
+
 	seq_printf(m, "processor\t: %d\n", i);
 	seq_printf(m, "vendor_id\t: User Mode Linux\n");
 	seq_printf(m, "model name\t: UML\n");
@@ -90,13 +98,12 @@ static int show_cpuinfo(struct seq_file *m, void *v)
 		   loops_per_jiffy/(500000/HZ),
 		   (loops_per_jiffy/(5000/HZ)) % 100);
 
-
 	return 0;
 }
 
 static void *c_start(struct seq_file *m, loff_t *pos)
 {
-	return *pos < nr_cpu_ids ? &boot_cpu_data + *pos : NULL;
+	return *pos < nr_cpu_ids ? uml_cpu_data + *pos : NULL;
 }
 
 static void *c_next(struct seq_file *m, void *v, loff_t *pos)
@@ -426,6 +433,7 @@ void __init setup_arch(char **cmdline_p)
 	strscpy(boot_command_line, command_line, COMMAND_LINE_SIZE);
 	*cmdline_p = command_line;
 	setup_hostinfo(host_info, sizeof host_info);
+	prefill_possible_map();
 
 	if (os_getrandom(rng_seed, sizeof(rng_seed), 0) == sizeof(rng_seed)) {
 		add_bootloader_randomness(rng_seed, sizeof(rng_seed));
@@ -460,6 +468,18 @@ void apply_alternatives(struct alt_instr *start, struct alt_instr *end)
 {
 }
 
+#if IS_ENABLED(CONFIG_SMP)
+void alternatives_smp_module_add(struct module *mod, char *name,
+				 void *locks, void *locks_end,
+				 void *text,  void *text_end)
+{
+}
+
+void alternatives_smp_module_del(struct module *mod)
+{
+}
+#endif
+
 void *text_poke(void *addr, const void *opcode, size_t len)
 {
 	/*
diff --git a/arch/um/os-Linux/Makefile b/arch/um/os-Linux/Makefile
index fae836713487..70c73c22f715 100644
--- a/arch/um/os-Linux/Makefile
+++ b/arch/um/os-Linux/Makefile
@@ -16,8 +16,10 @@ CFLAGS_main.o += -Wno-frame-larger-than
 
 obj-$(CONFIG_ARCH_REUSE_HOST_VSYSCALL_AREA) += elf_aux.o
 
+obj-$(CONFIG_SMP) += smp.o
+
 USER_OBJS := $(user-objs-y) elf_aux.o execvp.o file.o helper.o irq.o \
 	main.o mem.o process.o registers.o sigio.o signal.o start_up.o time.o \
-	tty.o umid.o util.o
+	tty.o umid.o util.o smp.o
 
 include $(srctree)/arch/um/scripts/Makefile.rules
diff --git a/arch/um/os-Linux/main.c b/arch/um/os-Linux/main.c
index 730723106228..92028c14d2a3 100644
--- a/arch/um/os-Linux/main.c
+++ b/arch/um/os-Linux/main.c
@@ -16,6 +16,7 @@
 #include <init.h>
 #include <kern_util.h>
 #include <os.h>
+#include <smp.h>
 #include <um_malloc.h>
 #include "internal.h"
 
@@ -207,7 +208,7 @@ void *__wrap_malloc(int size)
 {
 	void *ret;
 
-	if (!kmalloc_ok)
+	if (!kmalloc_ok || disable_kmalloc[uml_curr_cpu()])
 		return __real_malloc(size);
 	else if (size <= UM_KERN_PAGE_SIZE)
 		/* finding contiguous pages can be hard*/
diff --git a/arch/um/os-Linux/process.c b/arch/um/os-Linux/process.c
index 00b49e90d05f..87aec6b5beeb 100644
--- a/arch/um/os-Linux/process.c
+++ b/arch/um/os-Linux/process.c
@@ -10,6 +10,7 @@
 #include <errno.h>
 #include <signal.h>
 #include <fcntl.h>
+#include <linux/futex.h>
 #include <sys/mman.h>
 #include <sys/ptrace.h>
 #include <sys/prctl.h>
@@ -189,3 +190,12 @@ void os_set_pdeathsig(void)
 {
 	prctl(PR_SET_PDEATHSIG, SIGKILL);
 }
+
+int os_futex_wake(void *uaddr, unsigned int val)
+{
+	int r;
+
+	CATCH_EINTR(r = syscall(__NR_futex, uaddr, FUTEX_WAKE, val,
+				NULL, NULL, 0));
+	return r < 0 ? -errno : r;
+}
diff --git a/arch/um/os-Linux/skas/process.c b/arch/um/os-Linux/skas/process.c
index 78f48fa9db8b..d3440a6c6616 100644
--- a/arch/um/os-Linux/skas/process.c
+++ b/arch/um/os-Linux/skas/process.c
@@ -563,6 +563,13 @@ void userspace(struct uml_pt_regs *regs)
 	while (1) {
 		struct mm_id *mm_id = current_mm_id();
 
+		/*
+		 * At any given time, only one CPU thread can enter the
+		 * turnstile to operate on the same stub process, including
+		 * executing stub system calls (mmap and munmap).
+		 */
+		enter_turnstile(mm_id);
+
 		/*
 		 * When we are in time-travel mode, userspace can theoretically
 		 * do a *lot* of work without being scheduled. The problem with
@@ -740,6 +747,8 @@ void userspace(struct uml_pt_regs *regs)
 			}
 		}
 
+		exit_turnstile(mm_id);
+
 		UPT_SYSCALL_NR(regs) = -1; /* Assume: It's not a syscall */
 
 		if (sig) {
diff --git a/arch/um/os-Linux/smp.c b/arch/um/os-Linux/smp.c
new file mode 100644
index 000000000000..0877ccc54717
--- /dev/null
+++ b/arch/um/os-Linux/smp.c
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2025 Ant Group
+ * Author: Tiwei Bie <tiwei.btw@antgroup.com>
+ */
+
+#include <stdint.h>
+#include <errno.h>
+#include <pthread.h>
+#include <signal.h>
+#include <kern_util.h>
+#include <um_malloc.h>
+#include <os.h>
+#include <smp.h>
+
+struct cpu_thread_data {
+	int cpu;
+	sigset_t sigset;
+};
+
+static __thread int __curr_cpu;
+
+int uml_curr_cpu(void)
+{
+	return __curr_cpu;
+}
+
+static pthread_t cpu_threads[CONFIG_NR_CPUS];
+
+static void *cpu_thread(void *arg)
+{
+	struct cpu_thread_data *data = arg;
+
+	__curr_cpu = data->cpu;
+
+	uml_start_idle(data);
+
+	return NULL;
+}
+
+int start_cpu_thread(int cpu)
+{
+	struct cpu_thread_data *data;
+	sigset_t sigset, oset;
+	int err;
+
+	data = uml_kmalloc(sizeof(*data), UM_GFP_ATOMIC);
+	if (!data)
+		return -ENOMEM;
+
+	sigfillset(&sigset);
+	if (sigprocmask(SIG_SETMASK, &sigset, &oset) < 0) {
+		err = errno;
+		goto err;
+	}
+
+	data->cpu = cpu;
+	data->sigset = oset;
+
+	err = pthread_create(&cpu_threads[cpu], NULL, cpu_thread, data);
+	if (sigprocmask(SIG_SETMASK, &oset, NULL) < 0)
+		panic("Failed to restore the signal mask: %d", errno);
+	if (err != 0)
+		goto err;
+
+	return 0;
+
+err:
+	kfree(data);
+	return -err;
+}
+
+void start_idle_thread_secondary(void *arg, jmp_buf *switch_buf)
+{
+	struct cpu_thread_data *data = arg;
+
+	if (sigprocmask(SIG_SETMASK, &data->sigset, NULL) < 0)
+		panic("Failed to restore the signal mask: %d", errno);
+
+	kfree(data);
+	longjmp(*switch_buf, 1);
+
+	/* unreachable */
+	printk(UM_KERN_ERR "impossible long jump!");
+	fatal_sigsegv();
+}
diff --git a/arch/um/os-Linux/start_up.c b/arch/um/os-Linux/start_up.c
index a827c2e01aa5..61c61e9c246c 100644
--- a/arch/um/os-Linux/start_up.c
+++ b/arch/um/os-Linux/start_up.c
@@ -22,6 +22,7 @@
 #include <asm/unistd.h>
 #include <init.h>
 #include <os.h>
+#include <smp.h>
 #include <kern_util.h>
 #include <mem_user.h>
 #include <ptrace_user.h>
@@ -481,6 +482,9 @@ void __init os_early_checks(void)
 			fatal("SECCOMP userspace requested but not functional!\n");
 	}
 
+	if (uml_ncpus > 1)
+		fatal("SMP is not supported with PTRACE userspace.\n");
+
 	using_seccomp = 0;
 	check_ptrace();
 
diff --git a/arch/um/os-Linux/time.c b/arch/um/os-Linux/time.c
index e38b6f84287b..ecf2fde01889 100644
--- a/arch/um/os-Linux/time.c
+++ b/arch/um/os-Linux/time.c
@@ -14,6 +14,7 @@
 #include <sys/time.h>
 #include <kern_util.h>
 #include <os.h>
+#include <smp.h>
 #include <string.h>
 
 static timer_t event_high_res_timer[CONFIG_NR_CPUS] = { 0 };
@@ -40,7 +41,8 @@ long long os_persistent_clock_emulation(void)
  */
 int os_timer_create(void)
 {
-	timer_t *t = &event_high_res_timer[0];
+	int cpu = uml_curr_cpu();
+	timer_t *t = &event_high_res_timer[cpu];
 	struct sigevent sev = {
 		.sigev_notify = SIGEV_THREAD_ID,
 		.sigev_signo = SIGALRM,
@@ -110,9 +112,10 @@ void os_idle_sleep(void)
 {
 	sigset_t set, old;
 
-	/* Block SIGALRM while performing the need_resched check. */
+	/* Block SIGALRM and SIGIO while performing the need_resched check. */
 	sigemptyset(&set);
 	sigaddset(&set, SIGALRM);
+	sigaddset(&set, SIGIO);
 	sigprocmask(SIG_BLOCK, &set, &old);
 
 	/* Sleep if no resched is pending. */
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* Re: [PATCH 9/9] um: Add initial SMP support
  2025-07-27  6:29 ` [PATCH 9/9] um: Add initial SMP support Tiwei Bie
@ 2025-07-28 10:47   ` Johannes Berg
  2025-07-28 15:28     ` Randy Dunlap
  2025-07-28 16:04     ` Tiwei Bie
  2025-07-28 13:55   ` Johannes Berg
  1 sibling, 2 replies; 20+ messages in thread
From: Johannes Berg @ 2025-07-28 10:47 UTC (permalink / raw)
  To: Tiwei Bie, richard, anton.ivanov; +Cc: linux-um, tiwei.btw, linux-arch

On Sun, 2025-07-27 at 14:29 +0800, Tiwei Bie wrote:
> 
> +++ b/arch/um/include/asm/smp.h
> @@ -2,6 +2,27 @@
>  #ifndef __UM_SMP_H
>  #define __UM_SMP_H
>  
> -#define hard_smp_processor_id()		0
> +#if IS_ENABLED(CONFIG_SMP)
> +
> +#include <linux/bitops.h>
> +#include <asm/current.h>
> +#include <linux/cpumask.h>
> +#include <shared/smp.h>
> +
> +#define raw_smp_processor_id() uml_curr_cpu()
> +
> +void arch_smp_send_reschedule(int cpu);
> +
> +void arch_send_call_function_single_ipi(int cpu);
> +
> +void arch_send_call_function_ipi_mask(const struct cpumask *mask);
> +
> +static inline void smp_cpus_done(unsigned int maxcpus) { }
> +
> +#else /* !CONFIG_SMP */
> +
> +#define raw_smp_processor_id() 0

This seems a bit odd to me, linux/smp.h also defines
raw_smp_processor_id() to 0 the same way, unconditionally.

It almost seems to me we should define raw_smp_processor_id() only for
SMP? But then also __smp_processor_id()? Maybe not?

linux-arch folks, do you have any comments?

> --- /dev/null
> +++ b/arch/um/include/asm/spinlock.h
> @@ -0,0 +1,8 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#ifndef __ASM_UM_SPINLOCK_H
> +#define __ASM_UM_SPINLOCK_H
> +
> +#include <asm/processor.h>
> +#include <asm-generic/spinlock.h>
> +
> +#endif /* __ASM_UM_SPINLOCK_H */

Do we need this file? Maybe asm-generic should be including the right
things it needs?

> +void enter_turnstile(struct mm_id *mm_id);
> +void exit_turnstile(struct mm_id *mm_id);

We could add __acquires(turnstile) and __releases(turnstile) or
something, to have sparse check that it's locked/unlocked correctly, but
not sure it's worth it.

> +int disable_kmalloc[NR_CPUS] = { 0 };

nit: you can remove the "0".

> +int smp_sigio_handler(struct uml_pt_regs *regs)
> +{
> +	int cpu = raw_smp_processor_id();
> +
> +	IPI_handler(cpu, regs);
> +	if (cpu != 0)
> +		return 1;
> +	return 0;

nit: "return cpu != 0;" perhaps

> +__uml_setup("ncpus=", uml_ncpus_setup,
> +"ncpus=<# of desired CPUs>\n"
> +"    This tells UML how many virtual processors to start. The maximum\n"
> +"    number of supported virtual processors can be obtained by querying\n"
> +"    the CONFIG_NR_CPUS option using --showconfig.\n\n"


I feel like probably this should at least for now be mutually exclusive
with time-travel= parameters, at least if it's not 1? Or perhaps only
with time-travel=ext?

The timer code is in another patch, will look at that also. I guess
until then it's more of a gut feeling on "how would this work" :)

johannes


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 9/9] um: Add initial SMP support
  2025-07-27  6:29 ` [PATCH 9/9] um: Add initial SMP support Tiwei Bie
  2025-07-28 10:47   ` Johannes Berg
@ 2025-07-28 13:55   ` Johannes Berg
  2025-07-28 16:06     ` Tiwei Bie
  1 sibling, 1 reply; 20+ messages in thread
From: Johannes Berg @ 2025-07-28 13:55 UTC (permalink / raw)
  To: Tiwei Bie, richard, anton.ivanov; +Cc: linux-um, tiwei.btw

On Sun, 2025-07-27 at 14:29 +0800, Tiwei Bie wrote:
> 
> +static void IPI_handler(int cpu, struct uml_pt_regs *regs)
> +{
> +	struct pt_regs *old_regs = set_irq_regs((struct pt_regs *)regs);
> +	unsigned char c;
> +	int fd;
> +
> +	irq_enter();
> +
> +	if (current->mm)
> +		os_alarm_process(current->mm->context.id.pid);
> +
> +	fd = uml_cpu_data[cpu].ipi_pipe[0];
> +	while (os_read_file(fd, &c, 1) == 1) {

We were discussing the IPI stuff and started thinking maybe an RT signal
with sigqueue() passing the value to the si_value in SA_SIGINFO data
would be possible and have less overhead? That way there's nothing on
the SIGIO path, and you don't need a read() for the type of IPI?

johannes


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 9/9] um: Add initial SMP support
  2025-07-28 10:47   ` Johannes Berg
@ 2025-07-28 15:28     ` Randy Dunlap
  2025-07-28 16:04     ` Tiwei Bie
  1 sibling, 0 replies; 20+ messages in thread
From: Randy Dunlap @ 2025-07-28 15:28 UTC (permalink / raw)
  To: Johannes Berg, Tiwei Bie, richard, anton.ivanov
  Cc: linux-um, tiwei.btw, linux-arch



On 7/28/25 3:47 AM, Johannes Berg wrote:
>> +int smp_sigio_handler(struct uml_pt_regs *regs)
>> +{
>> +	int cpu = raw_smp_processor_id();
>> +
>> +	IPI_handler(cpu, regs);
>> +	if (cpu != 0)
>> +		return 1;
>> +	return 0;
> nit: "return cpu != 0;" perhaps

	return !!cpu;

-- 
~Randy



^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 9/9] um: Add initial SMP support
  2025-07-28 10:47   ` Johannes Berg
  2025-07-28 15:28     ` Randy Dunlap
@ 2025-07-28 16:04     ` Tiwei Bie
  2025-07-28 16:27       ` Johannes Berg
  1 sibling, 1 reply; 20+ messages in thread
From: Tiwei Bie @ 2025-07-28 16:04 UTC (permalink / raw)
  To: johannes
  Cc: richard, anton.ivanov, linux-um, linux-arch, tiwei.btw, tiwei.bie

On Mon, 28 Jul 2025 12:47:08 +0200, Johannes Berg wrote:
> On Sun, 2025-07-27 at 14:29 +0800, Tiwei Bie wrote:
> > 
> > +++ b/arch/um/include/asm/smp.h
> > @@ -2,6 +2,27 @@
> >  #ifndef __UM_SMP_H
> >  #define __UM_SMP_H
> >  
> > -#define hard_smp_processor_id()		0
> > +#if IS_ENABLED(CONFIG_SMP)
> > +
> > +#include <linux/bitops.h>
> > +#include <asm/current.h>
> > +#include <linux/cpumask.h>
> > +#include <shared/smp.h>
> > +
> > +#define raw_smp_processor_id() uml_curr_cpu()
> > +
> > +void arch_smp_send_reschedule(int cpu);
> > +
> > +void arch_send_call_function_single_ipi(int cpu);
> > +
> > +void arch_send_call_function_ipi_mask(const struct cpumask *mask);
> > +
> > +static inline void smp_cpus_done(unsigned int maxcpus) { }
> > +
> > +#else /* !CONFIG_SMP */
> > +
> > +#define raw_smp_processor_id() 0
> 
> This seems a bit odd to me, linux/smp.h also defines
> raw_smp_processor_id() to 0 the same way, unconditionally.
> 
> It almost seems to me we should define raw_smp_processor_id() only for
> SMP? But then also __smp_processor_id()? Maybe not?

I think you're right. I should't define raw_smp_processor_id() for non-SMP.

> 
> linux-arch folks, do you have any comments?
> 
> > --- /dev/null
> > +++ b/arch/um/include/asm/spinlock.h
> > @@ -0,0 +1,8 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +#ifndef __ASM_UM_SPINLOCK_H
> > +#define __ASM_UM_SPINLOCK_H
> > +
> > +#include <asm/processor.h>
> > +#include <asm-generic/spinlock.h>
> > +
> > +#endif /* __ASM_UM_SPINLOCK_H */
> 
> Do we need this file? Maybe asm-generic should be including the right
> things it needs?

I added this file to include asm/processor.h; otherwise, there would be
a lot of compilation errors. Other architectures seem to do the same:

$ grep -r asm/processor.h arch/ | grep asm/spinlock.h
arch/arm/include/asm/spinlock.h:#include <asm/processor.h>
arch/alpha/include/asm/spinlock.h:#include <asm/processor.h>
arch/arc/include/asm/spinlock.h:#include <asm/processor.h>
arch/hexagon/include/asm/spinlock.h:#include <asm/processor.h>
arch/parisc/include/asm/spinlock.h:#include <asm/processor.h>
arch/x86/include/asm/spinlock.h:#include <asm/processor.h>
arch/s390/include/asm/spinlock.h:#include <asm/processor.h>
arch/mips/include/asm/spinlock.h:#include <asm/processor.h>
arch/loongarch/include/asm/spinlock.h:#include <asm/processor.h>

> 
> > +void enter_turnstile(struct mm_id *mm_id);
> > +void exit_turnstile(struct mm_id *mm_id);
> 
> We could add __acquires(turnstile) and __releases(turnstile) or
> something, to have sparse check that it's locked/unlocked correctly, but
> not sure it's worth it.

Will do.

> 
> > +int disable_kmalloc[NR_CPUS] = { 0 };
> 
> nit: you can remove the "0".

Will fix all the nits in the next version.

> 
> > +int smp_sigio_handler(struct uml_pt_regs *regs)
> > +{
> > +	int cpu = raw_smp_processor_id();
> > +
> > +	IPI_handler(cpu, regs);
> > +	if (cpu != 0)
> > +		return 1;
> > +	return 0;
> 
> nit: "return cpu != 0;" perhaps
> 
> > +__uml_setup("ncpus=", uml_ncpus_setup,
> > +"ncpus=<# of desired CPUs>\n"
> > +"    This tells UML how many virtual processors to start. The maximum\n"
> > +"    number of supported virtual processors can be obtained by querying\n"
> > +"    the CONFIG_NR_CPUS option using --showconfig.\n\n"
> 
> 
> I feel like probably this should at least for now be mutually exclusive
> with time-travel= parameters, at least if it's not 1? Or perhaps only
> with time-travel=ext?

Currently, the UML_TIME_TRAVEL_SUPPORT option depends on !SMP:

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/um/Kconfig?h=v6.16#n218

so they can't be enabled at the same time during build.

> 
> The timer code is in another patch, will look at that also. I guess
> until then it's more of a gut feeling on "how would this work" :)

Thanks for the review! :)

Regards,
Tiwei


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 9/9] um: Add initial SMP support
  2025-07-28 13:55   ` Johannes Berg
@ 2025-07-28 16:06     ` Tiwei Bie
  0 siblings, 0 replies; 20+ messages in thread
From: Tiwei Bie @ 2025-07-28 16:06 UTC (permalink / raw)
  To: johannes; +Cc: richard, anton.ivanov, linux-um, tiwei.btw, tiwei.bie

On Mon, 28 Jul 2025 15:55:28 +0200, Johannes Berg wrote:
> On Sun, 2025-07-27 at 14:29 +0800, Tiwei Bie wrote:
> > 
> > +static void IPI_handler(int cpu, struct uml_pt_regs *regs)
> > +{
> > +	struct pt_regs *old_regs = set_irq_regs((struct pt_regs *)regs);
> > +	unsigned char c;
> > +	int fd;
> > +
> > +	irq_enter();
> > +
> > +	if (current->mm)
> > +		os_alarm_process(current->mm->context.id.pid);
> > +
> > +	fd = uml_cpu_data[cpu].ipi_pipe[0];
> > +	while (os_read_file(fd, &c, 1) == 1) {
> 
> We were discussing the IPI stuff and started thinking maybe an RT signal
> with sigqueue() passing the value to the si_value in SA_SIGINFO data
> would be possible and have less overhead? That way there's nothing on
> the SIGIO path, and you don't need a read() for the type of IPI?

I like this idea. I will give it a try. Thanks!

Regards,
Tiwei


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 9/9] um: Add initial SMP support
  2025-07-28 16:04     ` Tiwei Bie
@ 2025-07-28 16:27       ` Johannes Berg
  2025-07-29 15:06         ` Tiwei Bie
  0 siblings, 1 reply; 20+ messages in thread
From: Johannes Berg @ 2025-07-28 16:27 UTC (permalink / raw)
  To: Tiwei Bie; +Cc: richard, anton.ivanov, linux-um, linux-arch, tiwei.btw

On Tue, 2025-07-29 at 00:04 +0800, Tiwei Bie wrote:
> > > +++ b/arch/um/include/asm/spinlock.h
> > > @@ -0,0 +1,8 @@
> > > +/* SPDX-License-Identifier: GPL-2.0 */
> > > +#ifndef __ASM_UM_SPINLOCK_H
> > > +#define __ASM_UM_SPINLOCK_H
> > > +
> > > +#include <asm/processor.h>
> > > +#include <asm-generic/spinlock.h>
> > > +
> > > +#endif /* __ASM_UM_SPINLOCK_H */
> > 
> > Do we need this file? Maybe asm-generic should be including the right
> > things it needs?
> 
> I added this file to include asm/processor.h; otherwise, there would be
> a lot of compilation errors. Other architectures seem to do the same:
> 
> $ grep -r asm/processor.h arch/ | grep asm/spinlock.h
> arch/arm/include/asm/spinlock.h:#include <asm/processor.h>
> arch/alpha/include/asm/spinlock.h:#include <asm/processor.h>
> arch/arc/include/asm/spinlock.h:#include <asm/processor.h>
> arch/hexagon/include/asm/spinlock.h:#include <asm/processor.h>
> arch/parisc/include/asm/spinlock.h:#include <asm/processor.h>
> arch/x86/include/asm/spinlock.h:#include <asm/processor.h>
> arch/s390/include/asm/spinlock.h:#include <asm/processor.h>
> arch/mips/include/asm/spinlock.h:#include <asm/processor.h>
> arch/loongarch/include/asm/spinlock.h:#include <asm/processor.h>

Except for loongarch they all do something else too though. Feels to me
um (and loongarch) really shouldn't need that file.

> > I feel like probably this should at least for now be mutually exclusive
> > with time-travel= parameters, at least if it's not 1? Or perhaps only
> > with time-travel=ext?
> 
> Currently, the UML_TIME_TRAVEL_SUPPORT option depends on !SMP:
> 
> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/um/Kconfig?h=v6.16#n218
> 
> so they can't be enabled at the same time during build.

Oops, sorry, missed that. Good. I didn't see anything particularly wrong
in the time code, but I'm sure it won't work there :)

johannes


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 9/9] um: Add initial SMP support
  2025-07-28 16:27       ` Johannes Berg
@ 2025-07-29 15:06         ` Tiwei Bie
  2025-07-29 15:37           ` Johannes Berg
  0 siblings, 1 reply; 20+ messages in thread
From: Tiwei Bie @ 2025-07-29 15:06 UTC (permalink / raw)
  To: johannes
  Cc: richard, anton.ivanov, linux-um, linux-arch, tiwei.btw, tiwei.bie

On Mon, 28 Jul 2025 18:27:53 +0200, Johannes Berg wrote:
> On Tue, 2025-07-29 at 00:04 +0800, Tiwei Bie wrote:
> > > > +++ b/arch/um/include/asm/spinlock.h
> > > > @@ -0,0 +1,8 @@
> > > > +/* SPDX-License-Identifier: GPL-2.0 */
> > > > +#ifndef __ASM_UM_SPINLOCK_H
> > > > +#define __ASM_UM_SPINLOCK_H
> > > > +
> > > > +#include <asm/processor.h>
> > > > +#include <asm-generic/spinlock.h>
> > > > +
> > > > +#endif /* __ASM_UM_SPINLOCK_H */
> > > 
> > > Do we need this file? Maybe asm-generic should be including the right
> > > things it needs?
> > 
> > I added this file to include asm/processor.h; otherwise, there would be
> > a lot of compilation errors. Other architectures seem to do the same:
> > 
> > $ grep -r asm/processor.h arch/ | grep asm/spinlock.h
> > arch/arm/include/asm/spinlock.h:#include <asm/processor.h>
> > arch/alpha/include/asm/spinlock.h:#include <asm/processor.h>
> > arch/arc/include/asm/spinlock.h:#include <asm/processor.h>
> > arch/hexagon/include/asm/spinlock.h:#include <asm/processor.h>
> > arch/parisc/include/asm/spinlock.h:#include <asm/processor.h>
> > arch/x86/include/asm/spinlock.h:#include <asm/processor.h>
> > arch/s390/include/asm/spinlock.h:#include <asm/processor.h>
> > arch/mips/include/asm/spinlock.h:#include <asm/processor.h>
> > arch/loongarch/include/asm/spinlock.h:#include <asm/processor.h>
> 
> Except for loongarch they all do something else too though. Feels to me
> um (and loongarch) really shouldn't need that file.

Sorry for the confusion. My point is that since other architectures
also do this, it seems common practice to include asm/processor.h in
asm/spinlock.h when necessary.

The reason we need to include asm/processor.h in asm/spinlock.h on UML
is because:

ticket_spin_lock() (which is an inline function indirectly provided by
asm-generic/spinlock.h) relies on atomic_cond_read_acquire(), which
is defined as smp_cond_load_acquire().

On UML, smp_cond_load_acquire() is provided by asm-generic/barrier.h,
and it relies on smp_cond_load_relaxed(), which is also provided by
asm-generic/barrier.h on UML. And smp_cond_load_relaxed() is a macro
that relies on cpu_relax(), which is provided by asm/processor.h.

If we don't include asm/processor.h in asm/spinlock.h, ticket_spin_lock()
will fail to build:

./include/asm-generic/ticket_spinlock.h: In function ‘ticket_spin_lock’:
./include/asm-generic/barrier.h:253:17: error: implicit declaration of function ‘cpu_relax’ [-Werror=implicit-function-declaration]
  253 |                 cpu_relax();                                    \
      |                 ^~~~~~~~~
./include/asm-generic/barrier.h:270:16: note: in expansion of macro ‘smp_cond_load_relaxed’
  270 |         _val = smp_cond_load_relaxed(ptr, cond_expr);           \
      |                ^~~~~~~~~~~~~~~~~~~~~
./include/linux/atomic.h:28:40: note: in expansion of macro ‘smp_cond_load_acquire’
   28 | #define atomic_cond_read_acquire(v, c) smp_cond_load_acquire(&(v)->counter, (c))
      |                                        ^~~~~~~~~~~~~~~~~~~~~
./include/asm-generic/ticket_spinlock.h:49:9: note: in expansion of macro ‘atomic_cond_read_acquire’
   49 |         atomic_cond_read_acquire(&lock->val, ticket == (u16)VAL);
      |         ^~~~~~~~~~~~~~~~~~~~~~~~

I can add a comment for it like this:

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/sparc/include/asm/spinlock_32.h?h=v6.16#n14

Regards,
Tiwei


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 9/9] um: Add initial SMP support
  2025-07-29 15:06         ` Tiwei Bie
@ 2025-07-29 15:37           ` Johannes Berg
  2025-07-30  4:18             ` Tiwei Bie
  0 siblings, 1 reply; 20+ messages in thread
From: Johannes Berg @ 2025-07-29 15:37 UTC (permalink / raw)
  To: Tiwei Bie; +Cc: richard, anton.ivanov, linux-um, linux-arch, tiwei.btw

On Tue, 2025-07-29 at 23:06 +0800, Tiwei Bie wrote:
> On Mon, 28 Jul 2025 18:27:53 +0200, Johannes Berg wrote:
> > On Tue, 2025-07-29 at 00:04 +0800, Tiwei Bie wrote:
> > > > > +++ b/arch/um/include/asm/spinlock.h
> > > > > @@ -0,0 +1,8 @@
> > > > > +/* SPDX-License-Identifier: GPL-2.0 */
> > > > > +#ifndef __ASM_UM_SPINLOCK_H
> > > > > +#define __ASM_UM_SPINLOCK_H
> > > > > +
> > > > > +#include <asm/processor.h>
> > > > > +#include <asm-generic/spinlock.h>
> > > > > +
> > > > > +#endif /* __ASM_UM_SPINLOCK_H */
> > > > 
> > > > Do we need this file? Maybe asm-generic should be including the right
> > > > things it needs?
> > > 
> > > I added this file to include asm/processor.h; otherwise, there would be
> > > a lot of compilation errors. Other architectures seem to do the same:
> > > 
> > > $ grep -r asm/processor.h arch/ | grep asm/spinlock.h
> > > arch/arm/include/asm/spinlock.h:#include <asm/processor.h>
> > > arch/alpha/include/asm/spinlock.h:#include <asm/processor.h>
> > > arch/arc/include/asm/spinlock.h:#include <asm/processor.h>
> > > arch/hexagon/include/asm/spinlock.h:#include <asm/processor.h>
> > > arch/parisc/include/asm/spinlock.h:#include <asm/processor.h>
> > > arch/x86/include/asm/spinlock.h:#include <asm/processor.h>
> > > arch/s390/include/asm/spinlock.h:#include <asm/processor.h>
> > > arch/mips/include/asm/spinlock.h:#include <asm/processor.h>
> > > arch/loongarch/include/asm/spinlock.h:#include <asm/processor.h>
> > 
> > Except for loongarch they all do something else too though. Feels to me
> > um (and loongarch) really shouldn't need that file.
> 
> Sorry for the confusion. My point is that since other architectures
> also do this, it seems common practice to include asm/processor.h in
> asm/spinlock.h when necessary.

Yeah, I understand.

> 
> The reason we need to include asm/processor.h in asm/spinlock.h on UML
> is because:
> 
> ticket_spin_lock() (which is an inline function indirectly provided by
> asm-generic/spinlock.h) relies on atomic_cond_read_acquire(), which
> is defined as smp_cond_load_acquire().

Right, but that's not the architecture's "fault".

It seems to me that either spinlock.h should include asm/processor.h for
it, or (at least, but I think less appropriate) asm-generic/spinlock.h
should be doing this.

> On UML, smp_cond_load_acquire() is provided by asm-generic/barrier.h,
> and it relies on smp_cond_load_relaxed(), which is also provided by
> asm-generic/barrier.h on UML. And smp_cond_load_relaxed() is a macro
> that relies on cpu_relax(), which is provided by asm/processor.h.

In general though, there ought to be some definition of which header
file(s) is/are expected to provide smp_cond_load_acquire() and/or
atomic_cond_read_acquire(). And that header file/those header files
should be included by the files that use the functions/macros.


IOW, I think you've stumbled across an inconsistency in the generic
files, and hence we should fix that, rather than having each
architecture paper over it.


johannes


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 9/9] um: Add initial SMP support
  2025-07-29 15:37           ` Johannes Berg
@ 2025-07-30  4:18             ` Tiwei Bie
  2025-08-10  4:33               ` Tiwei Bie
  0 siblings, 1 reply; 20+ messages in thread
From: Tiwei Bie @ 2025-07-30  4:18 UTC (permalink / raw)
  To: johannes
  Cc: richard, anton.ivanov, linux-um, linux-arch, tiwei.btw, tiwei.bie

On Tue, 29 Jul 2025 17:37:24 +0200, Johannes Berg wrote:
> On Tue, 2025-07-29 at 23:06 +0800, Tiwei Bie wrote:
> > On Mon, 28 Jul 2025 18:27:53 +0200, Johannes Berg wrote:
> > > On Tue, 2025-07-29 at 00:04 +0800, Tiwei Bie wrote:
> > > > > > +++ b/arch/um/include/asm/spinlock.h
> > > > > > @@ -0,0 +1,8 @@
> > > > > > +/* SPDX-License-Identifier: GPL-2.0 */
> > > > > > +#ifndef __ASM_UM_SPINLOCK_H
> > > > > > +#define __ASM_UM_SPINLOCK_H
> > > > > > +
> > > > > > +#include <asm/processor.h>
> > > > > > +#include <asm-generic/spinlock.h>
> > > > > > +
> > > > > > +#endif /* __ASM_UM_SPINLOCK_H */
> > > > > 
> > > > > Do we need this file? Maybe asm-generic should be including the right
> > > > > things it needs?
> > > > 
> > > > I added this file to include asm/processor.h; otherwise, there would be
> > > > a lot of compilation errors. Other architectures seem to do the same:
> > > > 
> > > > $ grep -r asm/processor.h arch/ | grep asm/spinlock.h
> > > > arch/arm/include/asm/spinlock.h:#include <asm/processor.h>
> > > > arch/alpha/include/asm/spinlock.h:#include <asm/processor.h>
> > > > arch/arc/include/asm/spinlock.h:#include <asm/processor.h>
> > > > arch/hexagon/include/asm/spinlock.h:#include <asm/processor.h>
> > > > arch/parisc/include/asm/spinlock.h:#include <asm/processor.h>
> > > > arch/x86/include/asm/spinlock.h:#include <asm/processor.h>
> > > > arch/s390/include/asm/spinlock.h:#include <asm/processor.h>
> > > > arch/mips/include/asm/spinlock.h:#include <asm/processor.h>
> > > > arch/loongarch/include/asm/spinlock.h:#include <asm/processor.h>
> > > 
> > > Except for loongarch they all do something else too though. Feels to me
> > > um (and loongarch) really shouldn't need that file.
> > 
> > Sorry for the confusion. My point is that since other architectures
> > also do this, it seems common practice to include asm/processor.h in
> > asm/spinlock.h when necessary.
> 
> Yeah, I understand.
> 
> > 
> > The reason we need to include asm/processor.h in asm/spinlock.h on UML
> > is because:
> > 
> > ticket_spin_lock() (which is an inline function indirectly provided by
> > asm-generic/spinlock.h) relies on atomic_cond_read_acquire(), which
> > is defined as smp_cond_load_acquire().
> 
> Right, but that's not the architecture's "fault".
> 
> It seems to me that either spinlock.h should include asm/processor.h for
> it,

+1

> or (at least, but I think less appropriate) asm-generic/spinlock.h
> should be doing this.
> 
> > On UML, smp_cond_load_acquire() is provided by asm-generic/barrier.h,
> > and it relies on smp_cond_load_relaxed(), which is also provided by
> > asm-generic/barrier.h on UML. And smp_cond_load_relaxed() is a macro
> > that relies on cpu_relax(), which is provided by asm/processor.h.
> 
> In general though, there ought to be some definition of which header
> file(s) is/are expected to provide smp_cond_load_acquire() and/or
> atomic_cond_read_acquire(). And that header file/those header files
> should be included by the files that use the functions/macros.
> 
> 
> IOW, I think you've stumbled across an inconsistency in the generic
> files, and hence we should fix that, rather than having each
> architecture paper over it.

That does make sense. I will prepare a patch for that. Thanks!

Regards,
Tiwei


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 9/9] um: Add initial SMP support
  2025-07-30  4:18             ` Tiwei Bie
@ 2025-08-10  4:33               ` Tiwei Bie
  0 siblings, 0 replies; 20+ messages in thread
From: Tiwei Bie @ 2025-08-10  4:33 UTC (permalink / raw)
  To: johannes
  Cc: richard, anton.ivanov, linux-um, linux-arch, tiwei.btw, tiwei.bie

On Wed, 30 Jul 2025 12:18:38 +0800, Tiwei Bie wrote:
> On Tue, 29 Jul 2025 17:37:24 +0200, Johannes Berg wrote:
[...]
> > 
> > IOW, I think you've stumbled across an inconsistency in the generic
> > files, and hence we should fix that, rather than having each
> > architecture paper over it.
> 
> That does make sense. I will prepare a patch for that. Thanks!

Hmm, this issue might be a bit tricky to resolve.. The root cause
is that smp_cond_load_relaxed() provided by asm/barrier.h relies
on cpu_relax() [1][2], but the corresponding header isn't included.
The reason why it's not included might be that asm/processor.h
includes too many dependencies, which prevents barrier.h from
including it. I haven't come up with an ideal way to address it
yet. Fortunately, smp_cond_load_relaxed() is a macro, so cpu_relax()
is needed only when smp_cond_load_relaxed() is invoked inside a
function (i.e., when it's expanded during preprocessing).

As for this series, I realized that I should implement spinlock in
$SUBARCH (i.e., in a $SUBARCH native way, which won't require the
above workaround anymore), similar to how atomic and barrier are
implemented. I'll take that approach in the next version.

Thanks again for the review!

[1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/asm-generic/barrier.h?h=v6.16#n253
[2] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/um/asm/barrier.h?h=v6.16#n27

Regards,
Tiwei


^ permalink raw reply	[flat|nested] 20+ messages in thread

end of thread, other threads:[~2025-08-10  4:34 UTC | newest]

Thread overview: 20+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-07-27  6:29 [PATCH 0/9] um: Add SMP support Tiwei Bie
2025-07-27  6:29 ` [PATCH 1/9] um: Stop tracking virtual CPUs via mm_cpumask() Tiwei Bie
2025-07-27  6:29 ` [PATCH 2/9] um: Remove unused cpu_data and current_cpu_data macros Tiwei Bie
2025-07-27  6:29 ` [PATCH 3/9] um: vdso: Implement __vdso_getcpu() via syscall Tiwei Bie
2025-07-27  6:29 ` [PATCH 4/9] um: Preserve errno within signal handler Tiwei Bie
2025-07-27  6:29 ` [PATCH 5/9] um: Turn signals_* into thread-local variables Tiwei Bie
2025-07-27  6:29 ` [PATCH 6/9] um: Determine sleep based on need_resched() Tiwei Bie
2025-07-27  6:29 ` [PATCH 7/9] um: Define timers on a per-CPU basis Tiwei Bie
2025-07-27  6:29 ` [PATCH 8/9] um: Support directing IO signals to calling thread Tiwei Bie
2025-07-27  6:29 ` [PATCH 9/9] um: Add initial SMP support Tiwei Bie
2025-07-28 10:47   ` Johannes Berg
2025-07-28 15:28     ` Randy Dunlap
2025-07-28 16:04     ` Tiwei Bie
2025-07-28 16:27       ` Johannes Berg
2025-07-29 15:06         ` Tiwei Bie
2025-07-29 15:37           ` Johannes Berg
2025-07-30  4:18             ` Tiwei Bie
2025-08-10  4:33               ` Tiwei Bie
2025-07-28 13:55   ` Johannes Berg
2025-07-28 16:06     ` Tiwei Bie

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).