Re: [PATCH V10 20/22] LoongArch: Add multi-processor (SMP) support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi, Xuerui,

On Sun, May 15, 2022 at 10:16 PM WANG Xuerui <kernel@xxxxxxxxxx> wrote:
>
> Hi,
>
> On 5/14/22 16:04, Huacai Chen wrote:
> > LoongArch-based procesors have 4, 8 or 16 cores per package. This patch
> > adds multi-processor (SMP) support for LoongArch.
> >
> > Signed-off-by: Huacai Chen <chenhuacai@xxxxxxxxxxx>
> > ---
> >   arch/loongarch/Kconfig                  |  44 +-
> >   arch/loongarch/include/asm/atomic.h     |   4 +
> >   arch/loongarch/include/asm/barrier.h    | 108 ++++
> >   arch/loongarch/include/asm/cmpxchg.h    |   1 +
> >   arch/loongarch/include/asm/futex.h      |   1 +
> >   arch/loongarch/include/asm/hardirq.h    |   2 +
> >   arch/loongarch/include/asm/irq.h        |   3 +
> >   arch/loongarch/include/asm/percpu.h     | 194 ++++++
> >   arch/loongarch/include/asm/pgtable.h    |  21 +
> >   arch/loongarch/include/asm/smp.h        | 124 ++++
> >   arch/loongarch/include/asm/stackframe.h |  17 +-
> >   arch/loongarch/include/asm/tlbflush.h   |  13 +
> >   arch/loongarch/include/asm/topology.h   |   7 +-
> >   arch/loongarch/kernel/Makefile          |   2 +
> >   arch/loongarch/kernel/acpi.c            |  70 ++-
> >   arch/loongarch/kernel/asm-offsets.c     |  10 +
> >   arch/loongarch/kernel/head.S            |  30 +
> >   arch/loongarch/kernel/irq.c             |  13 +-
> >   arch/loongarch/kernel/proc.c            |   5 +
> >   arch/loongarch/kernel/process.c         |   7 +
> >   arch/loongarch/kernel/reset.c           |  12 +
> >   arch/loongarch/kernel/setup.c           |  26 +
> >   arch/loongarch/kernel/smp.c             | 744 ++++++++++++++++++++++++
> >   arch/loongarch/kernel/topology.c        |  43 +-
> >   arch/loongarch/kernel/vmlinux.lds.S     |   4 +
> >   arch/loongarch/mm/tlbex.S               |  69 +++
> >   include/linux/cpuhotplug.h              |   1 +
> >   27 files changed, 1564 insertions(+), 11 deletions(-)
> >   create mode 100644 arch/loongarch/include/asm/smp.h
> >   create mode 100644 arch/loongarch/kernel/smp.c
> >
> > diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig
> > index 564bd6d2c2b9..f1053d77f06e 100644
> > --- a/arch/loongarch/Kconfig
> > +++ b/arch/loongarch/Kconfig
> > @@ -65,6 +65,7 @@ config LOONGARCH
> >       select GENERIC_LIB_UCMPDI2
> >       select GENERIC_PCI_IOMAP
> >       select GENERIC_SCHED_CLOCK
> > +     select GENERIC_SMP_IDLE_THREAD
> >       select GENERIC_TIME_VSYSCALL
> >       select GPIOLIB
> >       select HAVE_ARCH_AUDITSYSCALL
> > @@ -95,7 +96,7 @@ config LOONGARCH
> >       select HAVE_RSEQ
> >       select HAVE_SYSCALL_TRACEPOINTS
> >       select HAVE_TIF_NOHZ
> > -     select HAVE_VIRT_CPU_ACCOUNTING_GEN
> > +     select HAVE_VIRT_CPU_ACCOUNTING_GEN if !SMP
> >       select IRQ_FORCED_THREADING
> >       select IRQ_LOONGARCH_CPU
> >       select MODULES_USE_ELF_RELA if MODULES
> > @@ -312,6 +313,47 @@ config EFI_STUB
> >         This kernel feature allows the kernel to be loaded directly by
> >         EFI firmware without the use of a bootloader.
> >
> > +config SMP
> > +     bool "Multi-Processing support"
> > +     help
> > +       This enables support for systems with more than one CPU. If you have
> > +       a system with only one CPU, say N. If you have a system with more
> > +       than one CPU, say Y.
> > +
> > +       If you say N here, the kernel will run on uni- and multiprocessor
> > +       machines, but will use only one CPU of a multiprocessor machine. If
> > +       you say Y here, the kernel will run on many, but not all,
> > +       uniprocessor machines. On a uniprocessor machine, the kernel
> > +       will run faster if you say N here.
> > +
> > +       People using multiprocessor machines who say Y here should also say
> > +       Y to "Enhanced Real Time Clock Support", below.
> This paragraph could be removed as it's not applicable to us. (We don't
> have an "Enhanced RTC support" config option.)
OK, thanks.

> > +
> > +       See also the SMP-HOWTO available at
> > +       <http://www.tldp.org/docs.html#howto>.
> > +
> > +       If you don't know what to do here, say N.
> > +
> > +config HOTPLUG_CPU
> > +     bool "Support for hot-pluggable CPUs"
> > +     depends on SMP
> > +     select GENERIC_IRQ_MIGRATION
> > +     help
> > +       Say Y here to allow turning CPUs off and on. CPUs can be
> > +       controlled through /sys/devices/system/cpu.
> > +       (Note: power management support will enable this option
> > +         automatically on SMP systems. )
> > +       Say N if you want to disable CPU hotplug.
> > +
> > +config NR_CPUS
> > +     int "Maximum number of CPUs (2-256)"
> > +     range 2 256
> > +     depends on SMP
> > +     default "16"
> > +     help
> > +       This allows you to specify the maximum number of CPUs which this
> > +       kernel will support.
> > +
> >   config FORCE_MAX_ZONEORDER
> >       int "Maximum zone order"
> >       range 14 64 if PAGE_SIZE_64KB
> > diff --git a/arch/loongarch/include/asm/atomic.h b/arch/loongarch/include/asm/atomic.h
> > index 932352342b12..979367ad4e2c 100644
> > --- a/arch/loongarch/include/asm/atomic.h
> > +++ b/arch/loongarch/include/asm/atomic.h
> > @@ -162,6 +162,7 @@ static inline int arch_atomic_sub_if_positive(int i, atomic_t *v)
> >               "       sc.w    %1, %2                                  \n"
> >               "       beq     $zero, %1, 1b                           \n"
> >               "2:                                                     \n"
> > +             __WEAK_LLSC_MB
> >               : "=&r" (result), "=&r" (temp),
> >                 "+" GCC_OFF_SMALL_ASM() (v->counter)
> >               : "I" (-i));
> > @@ -174,6 +175,7 @@ static inline int arch_atomic_sub_if_positive(int i, atomic_t *v)
> >               "       sc.w    %1, %2                                  \n"
> >               "       beq     $zero, %1, 1b                           \n"
> >               "2:                                                     \n"
> > +             __WEAK_LLSC_MB
> >               : "=&r" (result), "=&r" (temp),
> >                 "+" GCC_OFF_SMALL_ASM() (v->counter)
> >               : "r" (i));
> > @@ -323,6 +325,7 @@ static inline long arch_atomic64_sub_if_positive(long i, atomic64_t *v)
> >               "       sc.d    %1, %2                                  \n"
> >               "       beq     %1, $zero, 1b                           \n"
> >               "2:                                                     \n"
> > +             __WEAK_LLSC_MB
> >               : "=&r" (result), "=&r" (temp),
> >                 "+" GCC_OFF_SMALL_ASM() (v->counter)
> >               : "I" (-i));
> > @@ -335,6 +338,7 @@ static inline long arch_atomic64_sub_if_positive(long i, atomic64_t *v)
> >               "       sc.d    %1, %2                                  \n"
> >               "       beq     %1, $zero, 1b                           \n"
> >               "2:                                                     \n"
> > +             __WEAK_LLSC_MB
> >               : "=&r" (result), "=&r" (temp),
> >                 "+" GCC_OFF_SMALL_ASM() (v->counter)
> >               : "r" (i));
> > diff --git a/arch/loongarch/include/asm/barrier.h b/arch/loongarch/include/asm/barrier.h
> > index cc6c7e3f5ce6..6c567c750d04 100644
> > --- a/arch/loongarch/include/asm/barrier.h
> > +++ b/arch/loongarch/include/asm/barrier.h
> > @@ -18,6 +18,19 @@
> >   #define mb()                fast_mb()
> >   #define iob()               fast_iob()
> >
> > +#define __smp_mb()   __asm__ __volatile__("dbar 0" : : : "memory")
> > +#define __smp_rmb()  __asm__ __volatile__("dbar 0" : : : "memory")
> > +#define __smp_wmb()  __asm__ __volatile__("dbar 0" : : : "memory")
>
> It's always unfortunate to see full barriers used in place of more
> fine-grained barriers, but that's all we have in LoongArch v1.00... This
> is life, I think, but make sure to tell the hardware engineers, that
> users will be thankful for the additional barriers. ;-)
OK, Loongson-3A6000 will have fine-grained barriers.

>
> > +
> > +#ifdef CONFIG_SMP
> > +#define __WEAK_LLSC_MB               "       dbar 0  \n"
> > +#else
> > +#define __WEAK_LLSC_MB               "               \n"
> > +#endif
> > +
> > +#define __smp_mb__before_atomic()    barrier()
> > +#define __smp_mb__after_atomic()     barrier()
> > +
> >   /**
> >    * array_index_mask_nospec() - generate a ~0 mask when index < size, 0 otherwise
> >    * @index: array element index
> > @@ -46,6 +59,101 @@ static inline unsigned long array_index_mask_nospec(unsigned long index,
> >       return mask;
> >   }
> >
> > +#define __smp_load_acquire(p)                                                        \
> > +({                                                                           \
> > +     union { typeof(*p) __val; char __c[1]; } __u;                           \
> > +     unsigned long __tmp = 0;                                                        \
> > +     compiletime_assert_atomic_type(*p);                                     \
> > +     switch (sizeof(*p)) {                                                   \
> > +     case 1:                                                                 \
> > +             *(__u8 *)__u.__c = *(volatile __u8 *)p;                         \
> > +             __smp_mb();                                                     \
> > +             break;                                                          \
> > +     case 2:                                                                 \
> > +             *(__u16 *)__u.__c = *(volatile __u16 *)p;                       \
> > +             __smp_mb();                                                     \
> > +             break;                                                          \
> > +     case 4:                                                                 \
> > +             __asm__ __volatile__(                                           \
> > +             "amor_db.w %[val], %[tmp], %[mem]       \n"                             \
> > +             : [val] "=&r" (*(__u32 *)__u.__c)                               \
> > +             : [mem] "ZB" (*(u32 *) p), [tmp] "r" (__tmp)                    \
> > +             : "memory");                                                    \
> > +             break;                                                          \
> > +     case 8:                                                                 \
> > +             __asm__ __volatile__(                                           \
> > +             "amor_db.d %[val], %[tmp], %[mem]       \n"                             \
> > +             : [val] "=&r" (*(__u64 *)__u.__c)                               \
> > +             : [mem] "ZB" (*(u64 *) p), [tmp] "r" (__tmp)                    \
> > +             : "memory");                                                    \
> > +             break;                                                          \
> This is a little bit scary; while it's working right now it's perhaps
> better to, if possible, rewrite these with generic helpers such as
> READ_ONCE/WRITE_ONCE, like what arch/riscv has done. Cleanups can always
> come later...
> > +     }                                                                       \
> > +     (typeof(*p))__u.__val;                                                          \
> > +})
> > +
> > +#define __smp_store_release(p, v)                                            \
> > +do {                                                                         \
> > +     union { typeof(*p) __val; char __c[1]; } __u =                          \
> > +             { .__val = (__force typeof(*p)) (v) };                          \
> > +     unsigned long __tmp;                                                    \
> > +     compiletime_assert_atomic_type(*p);                                     \
> > +     switch (sizeof(*p)) {                                                   \
> > +     case 1:                                                                 \
> > +             __smp_mb();                                                     \
> > +             *(volatile __u8 *)p = *(__u8 *)__u.__c;                         \
> > +             break;                                                          \
> > +     case 2:                                                                 \
> > +             __smp_mb();                                                     \
> > +             *(volatile __u16 *)p = *(__u16 *)__u.__c;                       \
> > +             break;                                                          \
> > +     case 4:                                                                 \
> > +             __asm__ __volatile__(                                           \
> > +             "amswap_db.w %[tmp], %[val], %[mem]     \n"                     \
> > +             : [mem] "+ZB" (*(u32 *)p), [tmp] "=&r" (__tmp)                  \
> > +             : [val] "r" (*(__u32 *)__u.__c)                                 \
> > +             : );                                                            \
> > +             break;                                                          \
> > +     case 8:                                                                 \
> > +             __asm__ __volatile__(                                           \
> > +             "amswap_db.d %[tmp], %[val], %[mem]     \n"                     \
> > +             : [mem] "+ZB" (*(u64 *)p), [tmp] "=&r" (__tmp)                  \
> > +             : [val] "r" (*(__u64 *)__u.__c)                                 \
> > +             : );                                                            \
> > +             break;                                                          \
> > +     }                                                                       \
> > +} while (0)
> > +
> > +#define __smp_store_mb(p, v)                                                 \
> > +do {                                                                         \
> > +     union { typeof(p) __val; char __c[1]; } __u =                           \
> > +             { .__val = (__force typeof(p)) (v) };                           \
> > +     unsigned long __tmp;                                                    \
> > +     switch (sizeof(p)) {                                                    \
> > +     case 1:                                                                 \
> > +             *(volatile __u8 *)&p = *(__u8 *)__u.__c;                        \
> > +             __smp_mb();                                                     \
> > +             break;                                                          \
> > +     case 2:                                                                 \
> > +             *(volatile __u16 *)&p = *(__u16 *)__u.__c;                      \
> > +             __smp_mb();                                                     \
> > +             break;                                                          \
> > +     case 4:                                                                 \
> > +             __asm__ __volatile__(                                           \
> > +             "amswap_db.w %[tmp], %[val], %[mem]     \n"                     \
> > +             : [mem] "+ZB" (*(u32 *)&p), [tmp] "=&r" (__tmp)                 \
> > +             : [val] "r" (*(__u32 *)__u.__c)                                 \
> > +             : );                                                            \
> > +             break;                                                          \
> > +     case 8:                                                                 \
> > +             __asm__ __volatile__(                                           \
> > +             "amswap_db.d %[tmp], %[val], %[mem]     \n"                     \
> > +             : [mem] "+ZB" (*(u64 *)&p), [tmp] "=&r" (__tmp)                 \
> > +             : [val] "r" (*(__u64 *)__u.__c)                                 \
> > +             : );                                                            \
> > +             break;                                                          \
> > +     }                                                                       \
> > +} while (0)
> > +
> >   #include <asm-generic/barrier.h>
> >
> >   #endif /* __ASM_BARRIER_H */
> > diff --git a/arch/loongarch/include/asm/cmpxchg.h b/arch/loongarch/include/asm/cmpxchg.h
> > index 699925b8ff27..7a880651161b 100644
> > --- a/arch/loongarch/include/asm/cmpxchg.h
> > +++ b/arch/loongarch/include/asm/cmpxchg.h
> > @@ -58,6 +58,7 @@ static inline unsigned long __xchg(volatile void *ptr, unsigned long x,
> >       "       " st "  $t0, %1                         \n"             \
> >       "       beq     $zero, $t0, 1b                  \n"             \
> >       "2:                                             \n"             \
> > +     __WEAK_LLSC_MB                                                  \
> >       : "=&r" (__ret), "=ZB"(*m)                                      \
> >       : "ZB"(*m), "Jr" (old), "Jr" (new)                              \
> >       : "t0", "memory");                                              \
> > diff --git a/arch/loongarch/include/asm/futex.h b/arch/loongarch/include/asm/futex.h
> > index b27d55f92db7..9de8231694ec 100644
> > --- a/arch/loongarch/include/asm/futex.h
> > +++ b/arch/loongarch/include/asm/futex.h
> > @@ -86,6 +86,7 @@ futex_atomic_cmpxchg_inatomic(u32 *uval, u32 __user *uaddr, u32 oldval, u32 newv
> >       "2:     sc.w    $t0, %2                                 \n"
> >       "       beq     $zero, $t0, 1b                          \n"
> >       "3:                                                     \n"
> > +     __WEAK_LLSC_MB
> >       "       .section .fixup,\"ax\"                          \n"
> >       "4:     li.d    %0, %6                                  \n"
> >       "       b       3b                                      \n"
> > diff --git a/arch/loongarch/include/asm/hardirq.h b/arch/loongarch/include/asm/hardirq.h
> > index d32f83938880..befe8184aa08 100644
> > --- a/arch/loongarch/include/asm/hardirq.h
> > +++ b/arch/loongarch/include/asm/hardirq.h
> > @@ -21,4 +21,6 @@ typedef struct {
> >
> >   DECLARE_PER_CPU_ALIGNED(irq_cpustat_t, irq_stat);
> >
> > +#define __ARCH_IRQ_STAT
> > +
> >   #endif /* _ASM_HARDIRQ_H */
> > diff --git a/arch/loongarch/include/asm/irq.h b/arch/loongarch/include/asm/irq.h
> > index cd95d0d4e10f..ca3a68767b54 100644
> > --- a/arch/loongarch/include/asm/irq.h
> > +++ b/arch/loongarch/include/asm/irq.h
> > @@ -125,6 +125,9 @@ extern struct irq_domain *pch_lpc_domain;
> >   extern struct irq_domain *pch_msi_domain[MAX_IO_PICS];
> >   extern struct irq_domain *pch_pic_domain[MAX_IO_PICS];
> >
> > +extern void fixup_irqs(void);
> > +extern irqreturn_t loongson3_ipi_interrupt(int irq, void *dev);
> > +
> >   #include <asm-generic/irq.h>
> >
> >   #endif /* _ASM_IRQ_H */
> > diff --git a/arch/loongarch/include/asm/percpu.h b/arch/loongarch/include/asm/percpu.h
> > index 7d5b22ebd834..6f0d6e6fde10 100644
> > --- a/arch/loongarch/include/asm/percpu.h
> > +++ b/arch/loongarch/include/asm/percpu.h
> > @@ -5,6 +5,8 @@
> >   #ifndef __ASM_PERCPU_H
> >   #define __ASM_PERCPU_H
> >
> > +#include <asm/cmpxchg.h>
> > +
> >   /* Use r21 for fast access */
> >   register unsigned long __my_cpu_offset __asm__("$r21");
> >
> > @@ -15,6 +17,198 @@ static inline void set_my_cpu_offset(unsigned long off)
> >   }
> >   #define __my_cpu_offset __my_cpu_offset
> >
> > +#define PERCPU_OP(op, asm_op, c_op)                                  \
> > +static inline unsigned long __percpu_##op(void *ptr,                 \
> > +                     unsigned long val, int size)                    \
> > +{                                                                    \
> > +     unsigned long ret;                                              \
> > +                                                                     \
> > +     switch (size) {                                                 \
> > +     case 4:                                                         \
> > +             __asm__ __volatile__(                                   \
> > +             "am"#asm_op".w" " %[ret], %[val], %[ptr]        \n"             \
> > +             : [ret] "=&r" (ret), [ptr] "+ZB"(*(u32 *)ptr)           \
> > +             : [val] "r" (val));                                     \
> > +             break;                                                  \
> > +     case 8:                                                         \
> > +             __asm__ __volatile__(                                   \
> > +             "am"#asm_op".d" " %[ret], %[val], %[ptr]        \n"             \
> > +             : [ret] "=&r" (ret), [ptr] "+ZB"(*(u64 *)ptr)           \
> > +             : [val] "r" (val));                                     \
> > +             break;                                                  \
> > +     default:                                                        \
> > +             ret = 0;                                                \
> > +             BUILD_BUG();                                            \
> > +     }                                                               \
> > +                                                                     \
> > +     return ret c_op val;                                            \
> > +}
> > +
> > +PERCPU_OP(add, add, +)
> > +PERCPU_OP(and, and, &)
> > +PERCPU_OP(or, or, |)
> > +#undef PERCPU_OP
> > +
> > +static inline unsigned long __percpu_read(void *ptr, int size)
> > +{
> > +     unsigned long ret;
> > +
> > +     switch (size) {
> > +     case 1:
> > +             __asm__ __volatile__ ("ldx.b %[ret], $r21, %[ptr]       \n"
> > +             : [ret] "=&r"(ret)
> > +             : [ptr] "r"(ptr)
> > +             : "memory");
> > +             break;
> > +     case 2:
> > +             __asm__ __volatile__ ("ldx.h %[ret], $r21, %[ptr]       \n"
> > +             : [ret] "=&r"(ret)
> > +             : [ptr] "r"(ptr)
> > +             : "memory");
> > +             break;
> > +     case 4:
> > +             __asm__ __volatile__ ("ldx.w %[ret], $r21, %[ptr]       \n"
> > +             : [ret] "=&r"(ret)
> > +             : [ptr] "r"(ptr)
> > +             : "memory");
> > +             break;
> > +     case 8:
> > +             __asm__ __volatile__ ("ldx.d %[ret], $r21, %[ptr]       \n"
> > +             : [ret] "=&r"(ret)
> > +             : [ptr] "r"(ptr)
> > +             : "memory");
> > +             break;
> > +     default:
> > +             ret = 0;
> > +             BUILD_BUG();
> > +     }
> > +
> > +     return ret;
> > +}
> > +
> > +static inline void __percpu_write(void *ptr, unsigned long val, int size)
> > +{
> > +     switch (size) {
> > +     case 1:
> > +             __asm__ __volatile__("stx.b %[val], $r21, %[ptr]        \n"
> > +             :
> > +             : [val] "r" (val), [ptr] "r" (ptr)
> > +             : "memory");
> > +             break;
> > +     case 2:
> > +             __asm__ __volatile__("stx.h %[val], $r21, %[ptr]        \n"
> > +             :
> > +             : [val] "r" (val), [ptr] "r" (ptr)
> > +             : "memory");
> > +             break;
> > +     case 4:
> > +             __asm__ __volatile__("stx.w %[val], $r21, %[ptr]        \n"
> > +             :
> > +             : [val] "r" (val), [ptr] "r" (ptr)
> > +             : "memory");
> > +             break;
> > +     case 8:
> > +             __asm__ __volatile__("stx.d %[val], $r21, %[ptr]        \n"
> > +             :
> > +             : [val] "r" (val), [ptr] "r" (ptr)
> > +             : "memory");
> > +             break;
> > +     default:
> > +             BUILD_BUG();
> > +     }
> > +}
> > +
> > +static inline unsigned long __percpu_xchg(void *ptr, unsigned long val,
> > +                                             int size)
> > +{
> > +     switch (size) {
> > +     case 4:
> > +             return __xchg_asm("amswap.w", (volatile u32 *)ptr, (u32)val);
> > +
> > +     case 8:
> > +             return __xchg_asm("amswap.d", (volatile u64 *)ptr, (u64)val);
> > +
> > +     default:
> > +             BUILD_BUG();
> > +     }
> > +
> > +     return 0;
> > +}
> > +
> > +/* this_cpu_cmpxchg */
> > +#define _protect_cmpxchg_local(pcp, o, n)                    \
> > +({                                                           \
> > +     typeof(*raw_cpu_ptr(&(pcp))) __ret;                     \
> > +     preempt_disable_notrace();                              \
> > +     __ret = cmpxchg_local(raw_cpu_ptr(&(pcp)), o, n);       \
> > +     preempt_enable_notrace();                               \
> > +     __ret;                                                  \
> > +})
> > +
> > +#define _percpu_read(pcp)                                            \
> > +({                                                                   \
> > +     typeof(pcp) __retval;                                           \
> > +     __retval = (typeof(pcp))__percpu_read(&(pcp), sizeof(pcp));     \
> > +     __retval;                                                       \
> > +})
> > +
> > +#define _percpu_write(pcp, val)                                              \
> > +do {                                                                 \
> > +     __percpu_write(&(pcp), (unsigned long)(val), sizeof(pcp));      \
> > +} while (0)                                                          \
> > +
> > +#define _pcp_protect(operation, pcp, val)                    \
> > +({                                                           \
> > +     typeof(pcp) __retval;                                   \
> > +     preempt_disable_notrace();                              \
> > +     __retval = (typeof(pcp))operation(raw_cpu_ptr(&(pcp)),  \
> > +                                       (val), sizeof(pcp));  \
> > +     preempt_enable_notrace();                               \
> > +     __retval;                                               \
> > +})
> > +
> > +#define _percpu_add(pcp, val) \
> > +     _pcp_protect(__percpu_add, pcp, val)
> > +
> > +#define _percpu_add_return(pcp, val) _percpu_add(pcp, val)
> > +
> > +#define _percpu_and(pcp, val) \
> > +     _pcp_protect(__percpu_and, pcp, val)
> > +
> > +#define _percpu_or(pcp, val) \
> > +     _pcp_protect(__percpu_or, pcp, val)
> > +
> > +#define _percpu_xchg(pcp, val) ((typeof(pcp)) \
> > +     _pcp_protect(__percpu_xchg, pcp, (unsigned long)(val)))
> > +
> > +#define this_cpu_add_4(pcp, val) _percpu_add(pcp, val)
> > +#define this_cpu_add_8(pcp, val) _percpu_add(pcp, val)
> > +
> > +#define this_cpu_add_return_4(pcp, val) _percpu_add_return(pcp, val)
> > +#define this_cpu_add_return_8(pcp, val) _percpu_add_return(pcp, val)
> > +
> > +#define this_cpu_and_4(pcp, val) _percpu_and(pcp, val)
> > +#define this_cpu_and_8(pcp, val) _percpu_and(pcp, val)
> > +
> > +#define this_cpu_or_4(pcp, val) _percpu_or(pcp, val)
> > +#define this_cpu_or_8(pcp, val) _percpu_or(pcp, val)
> > +
> > +#define this_cpu_read_1(pcp) _percpu_read(pcp)
> > +#define this_cpu_read_2(pcp) _percpu_read(pcp)
> > +#define this_cpu_read_4(pcp) _percpu_read(pcp)
> > +#define this_cpu_read_8(pcp) _percpu_read(pcp)
> > +
> > +#define this_cpu_write_1(pcp, val) _percpu_write(pcp, val)
> > +#define this_cpu_write_2(pcp, val) _percpu_write(pcp, val)
> > +#define this_cpu_write_4(pcp, val) _percpu_write(pcp, val)
> > +#define this_cpu_write_8(pcp, val) _percpu_write(pcp, val)
> > +
> > +#define this_cpu_xchg_4(pcp, val) _percpu_xchg(pcp, val)
> > +#define this_cpu_xchg_8(pcp, val) _percpu_xchg(pcp, val)
> > +
> > +#define this_cpu_cmpxchg_4(ptr, o, n) _protect_cmpxchg_local(ptr, o, n)
> > +#define this_cpu_cmpxchg_8(ptr, o, n) _protect_cmpxchg_local(ptr, o, n)
> > +
> >   #include <asm-generic/percpu.h>
> >
> >   #endif /* __ASM_PERCPU_H */
> > diff --git a/arch/loongarch/include/asm/pgtable.h b/arch/loongarch/include/asm/pgtable.h
> > index 8920dd8b297b..5e33987d0a13 100644
> > --- a/arch/loongarch/include/asm/pgtable.h
> > +++ b/arch/loongarch/include/asm/pgtable.h
> > @@ -279,8 +279,29 @@ static inline void set_pte(pte_t *ptep, pte_t pteval)
> >                * Make sure the buddy is global too (if it's !none,
> >                * it better already be global)
> >                */
> > +#ifdef CONFIG_SMP
> > +             /*
> > +              * For SMP, multiple CPUs can race, so we need to do
> > +              * this atomically.
> > +              */
> > +             unsigned long page_global = _PAGE_GLOBAL;
> > +             unsigned long tmp;
> > +
> > +             __asm__ __volatile__ (
> > +             "1:"    __LL    "%[tmp], %[buddy]               \n"
> > +             "       bnez    %[tmp], 2f                      \n"
> > +             "        or     %[tmp], %[tmp], %[global]       \n"
> > +                     __SC    "%[tmp], %[buddy]               \n"
> > +             "       beqz    %[tmp], 1b                      \n"
> > +             "       nop                                     \n"
> > +             "2:                                             \n"
> > +             __WEAK_LLSC_MB
> > +             : [buddy] "+m" (buddy->pte), [tmp] "=&r" (tmp)
> > +             : [global] "r" (page_global));
> > +#else /* !CONFIG_SMP */
> >               if (pte_none(*buddy))
> >                       pte_val(*buddy) = pte_val(*buddy) | _PAGE_GLOBAL;
> > +#endif /* CONFIG_SMP */
> >       }
> >   }
> >
> > diff --git a/arch/loongarch/include/asm/smp.h b/arch/loongarch/include/asm/smp.h
> > new file mode 100644
> > index 000000000000..551e1f37c705
> > --- /dev/null
> > +++ b/arch/loongarch/include/asm/smp.h
> > @@ -0,0 +1,124 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/*
> > + * Author: Huacai Chen <chenhuacai@xxxxxxxxxxx>
> > + * Copyright (C) 2020-2022 Loongson Technology Corporation Limited
> > + */
> > +#ifndef __ASM_SMP_H
> > +#define __ASM_SMP_H
> > +
> > +#include <linux/atomic.h>
> > +#include <linux/bitops.h>
> > +#include <linux/linkage.h>
> > +#include <linux/smp.h>
> > +#include <linux/threads.h>
> > +#include <linux/cpumask.h>
> > +
> > +void loongson3_smp_setup(void);
> > +void loongson3_prepare_cpus(unsigned int max_cpus);
> > +void loongson3_boot_secondary(int cpu, struct task_struct *idle);
> > +void loongson3_init_secondary(void);
> > +void loongson3_smp_finish(void);
> > +void loongson3_send_ipi_single(int cpu, unsigned int action);
> > +void loongson3_send_ipi_mask(const struct cpumask *mask, unsigned int action);
> > +#ifdef CONFIG_HOTPLUG_CPU
> > +int loongson3_cpu_disable(void);
> > +void loongson3_cpu_die(unsigned int cpu);
> > +#endif
> > +
> > +#ifdef CONFIG_SMP
> > +
> > +static inline void plat_smp_setup(void)
> > +{
> > +     loongson3_smp_setup();
> > +}
> > +
> > +#else /* !CONFIG_SMP */
> > +
> > +static inline void plat_smp_setup(void) { }
> > +
> > +#endif /* !CONFIG_SMP */
> > +
> > +extern int smp_num_siblings;
> > +extern int num_processors;
> > +extern int disabled_cpus;
> > +extern cpumask_t cpu_sibling_map[];
> > +extern cpumask_t cpu_core_map[];
> > +extern cpumask_t cpu_foreign_map[];
> > +
> > +static inline int raw_smp_processor_id(void)
> > +{
> > +#if defined(__VDSO__)
> > +     extern int vdso_smp_processor_id(void)
> > +             __compiletime_error("VDSO should not call smp_processor_id()");
> > +     return vdso_smp_processor_id();
> > +#else
> > +     return current_thread_info()->cpu;
> > +#endif
> > +}
> > +#define raw_smp_processor_id raw_smp_processor_id
> > +
> > +/* Map from cpu id to sequential logical cpu number.  This will only
> > + * not be idempotent when cpus failed to come on-line.       */
> > +extern int __cpu_number_map[NR_CPUS];
> > +#define cpu_number_map(cpu)  __cpu_number_map[cpu]
> > +
> > +/* The reverse map from sequential logical cpu number to cpu id.  */
> > +extern int __cpu_logical_map[NR_CPUS];
> > +#define cpu_logical_map(cpu)  __cpu_logical_map[cpu]
> > +
> > +#define cpu_physical_id(cpu) cpu_logical_map(cpu)
> > +
> > +#define SMP_BOOT_CPU         0x1
> > +#define SMP_RESCHEDULE               0x2
> > +#define SMP_CALL_FUNCTION    0x4
> > +
> > +struct secondary_data {
> > +     unsigned long stack;
> > +     unsigned long thread_info;
> > +};
> > +extern struct secondary_data cpuboot_data;
> > +
> > +extern asmlinkage void smpboot_entry(void);
> > +
> > +extern void calculate_cpu_foreign_map(void);
> > +
> > +/*
> > + * Generate IPI list text
> > + */
> > +extern void show_ipi_list(struct seq_file *p, int prec);
> > +
> > +/*
> > + * This function sends a 'reschedule' IPI to another CPU.
> > + * it goes straight through and wastes no time serializing
> > + * anything. Worst case is that we lose a reschedule ...
> > + */
> > +static inline void smp_send_reschedule(int cpu)
> > +{
> > +     loongson3_send_ipi_single(cpu, SMP_RESCHEDULE);
> > +}
> > +
> > +static inline void arch_send_call_function_single_ipi(int cpu)
> > +{
> > +     loongson3_send_ipi_single(cpu, SMP_CALL_FUNCTION);
> > +}
> > +
> > +static inline void arch_send_call_function_ipi_mask(const struct cpumask *mask)
> > +{
> > +     loongson3_send_ipi_mask(mask, SMP_CALL_FUNCTION);
> > +}
> > +
> > +#ifdef CONFIG_HOTPLUG_CPU
> > +static inline int __cpu_disable(void)
> > +{
> > +     return loongson3_cpu_disable();
> > +}
> > +
> > +static inline void __cpu_die(unsigned int cpu)
> > +{
> > +     loongson3_cpu_die(cpu);
> > +}
> > +
> > +extern void play_dead(void);
> > +#endif
> > +
> > +#endif /* __ASM_SMP_H */
> > diff --git a/arch/loongarch/include/asm/stackframe.h b/arch/loongarch/include/asm/stackframe.h
> > index 44151b878d00..4ca953062b5b 100644
> > --- a/arch/loongarch/include/asm/stackframe.h
> > +++ b/arch/loongarch/include/asm/stackframe.h
> > @@ -77,17 +77,24 @@
> >    * new value in sp.
> >    */
> >       .macro  get_saved_sp docfi=0
> > -     la.abs  t1, kernelsp
> > -     move    t0, sp
> > +     la.abs    t1, kernelsp
> > +#ifdef CONFIG_SMP
> > +     csrrd     t0, PERCPU_BASE_KS
> > +     LONG_ADD  t1, t1, t0
> > +#endif
> > +     move      t0, sp
> >       .if \docfi
> >       .cfi_register sp, t0
> >       .endif
> > -     LONG_L  sp, t1, 0
> > +     LONG_L    sp, t1, 0
> >       .endm
> >
> >       .macro  set_saved_sp stackp temp temp2
> > -     la.abs  \temp, kernelsp
> > -     LONG_S  \stackp, \temp, 0
> > +     la.abs    \temp, kernelsp
> > +#ifdef CONFIG_SMP
> > +     LONG_ADD  \temp, \temp, u0
> > +#endif
> > +     LONG_S    \stackp, \temp, 0
> >       .endm
> >
> >       .macro  SAVE_SOME docfi=0
> > diff --git a/arch/loongarch/include/asm/tlbflush.h b/arch/loongarch/include/asm/tlbflush.h
> > index 36bd6d11dc2d..a0785e590681 100644
> > --- a/arch/loongarch/include/asm/tlbflush.h
> > +++ b/arch/loongarch/include/asm/tlbflush.h
> > @@ -25,6 +25,17 @@ extern void local_flush_tlb_kernel_range(unsigned long start, unsigned long end)
> >   extern void local_flush_tlb_page(struct vm_area_struct *vma, unsigned long page);
> >   extern void local_flush_tlb_one(unsigned long vaddr);
> >
> > +#ifdef CONFIG_SMP
> > +
> > +extern void flush_tlb_all(void);
> > +extern void flush_tlb_mm(struct mm_struct *);
> > +extern void flush_tlb_range(struct vm_area_struct *vma, unsigned long, unsigned long);
> > +extern void flush_tlb_kernel_range(unsigned long, unsigned long);
> > +extern void flush_tlb_page(struct vm_area_struct *, unsigned long);
> > +extern void flush_tlb_one(unsigned long vaddr);
> > +
> > +#else /* CONFIG_SMP */
> > +
> >   #define flush_tlb_all()                     local_flush_tlb_all()
> >   #define flush_tlb_mm(mm)            local_flush_tlb_mm(mm)
> >   #define flush_tlb_range(vma, vmaddr, end)   local_flush_tlb_range(vma, vmaddr, end)
> > @@ -32,4 +43,6 @@ extern void local_flush_tlb_one(unsigned long vaddr);
> >   #define flush_tlb_page(vma, page)   local_flush_tlb_page(vma, page)
> >   #define flush_tlb_one(vaddr)                local_flush_tlb_one(vaddr)
> >
> > +#endif /* CONFIG_SMP */
> > +
> >   #endif /* __ASM_TLBFLUSH_H */
> > diff --git a/arch/loongarch/include/asm/topology.h b/arch/loongarch/include/asm/topology.h
> > index 9ac71a25207a..da135841e5b1 100644
> > --- a/arch/loongarch/include/asm/topology.h
> > +++ b/arch/loongarch/include/asm/topology.h
> > @@ -7,7 +7,12 @@
> >
> >   #include <linux/smp.h>
> >
> > -#define cpu_logical_map(cpu)  0
> > +#ifdef CONFIG_SMP
> > +#define topology_physical_package_id(cpu)    (cpu_data[cpu].package)
> > +#define topology_core_id(cpu)                        (cpu_data[cpu].core)
> > +#define topology_core_cpumask(cpu)           (&cpu_core_map[cpu])
> > +#define topology_sibling_cpumask(cpu)                (&cpu_sibling_map[cpu])
> > +#endif
> >
> >   #include <asm-generic/topology.h>
> >
> > diff --git a/arch/loongarch/kernel/Makefile b/arch/loongarch/kernel/Makefile
> > index 211f12b3781c..08ddd8c90eae 100644
> > --- a/arch/loongarch/kernel/Makefile
> > +++ b/arch/loongarch/kernel/Makefile
> > @@ -19,4 +19,6 @@ obj-$(CONFIG_MODULES)               += module.o module-sections.o
> >
> >   obj-$(CONFIG_PROC_FS)               += proc.o
> >
> > +obj-$(CONFIG_SMP)            += smp.o
> > +
> >   CPPFLAGS_vmlinux.lds                := $(KBUILD_CFLAGS)
> > diff --git a/arch/loongarch/kernel/acpi.c b/arch/loongarch/kernel/acpi.c
> > index 506ab9912c51..0c7f2d1077a1 100644
> > --- a/arch/loongarch/kernel/acpi.c
> > +++ b/arch/loongarch/kernel/acpi.c
> > @@ -139,6 +139,35 @@ void __init acpi_boot_table_init(void)
> >       }
> >   }
> >
> > +static int set_processor_mask(u32 id, u32 flags)
> > +{
> > +
> > +     int cpu, cpuid = id;
> > +
> > +     if (num_processors >= nr_cpu_ids) {
> > +             pr_warn("acpi: nr_cpus/possible_cpus limit of %i reached."
> pr_warn(PREFIX "nr_cpus ..."); -- make use of the common PREFIX declaration.
> > +                     " processor 0x%x ignored.\n", nr_cpu_ids, cpuid);
> > +
> > +             return -ENODEV;
> > +
> > +     }
> > +     if (cpuid == loongson_sysconf.boot_cpu_id)
> > +             cpu = 0;
> > +     else
> > +             cpu = cpumask_next_zero(-1, cpu_present_mask);
> > +
> > +     if (flags & ACPI_MADT_ENABLED) {
> > +             num_processors++;
> > +             set_cpu_possible(cpu, true);
> > +             set_cpu_present(cpu, true);
> > +             __cpu_number_map[cpuid] = cpu;
> > +             __cpu_logical_map[cpu] = cpuid;
> > +     } else
> > +             disabled_cpus++;
> > +
> > +     return cpu;
> > +}
> > +
> >   static int __init
> >   acpi_parse_cpuintc(union acpi_subtable_headers *header, const unsigned long end)
> >   {
> > @@ -149,6 +178,7 @@ acpi_parse_cpuintc(union acpi_subtable_headers *header, const unsigned long end)
> >               return -EINVAL;
> >
> >       acpi_table_print_madt_entry(&header->common);
> > +     set_processor_mask(processor->core_id, processor->flags);
> >
> >       return 0;
> >   }
> > @@ -250,7 +280,12 @@ acpi_parse_pch_lpc(union acpi_subtable_headers *header, const unsigned long end)
> >
> >   static void __init acpi_process_madt(void)
> >   {
> > -     int error;
> > +     int i, error;
> > +
> > +     for (i = 0; i < NR_CPUS; i++) {
> > +             __cpu_number_map[i] = -1;
> > +             __cpu_logical_map[i] = -1;
> > +     }
> >
> >       /* Parse MADT CPUINTC entries */
> >       error = acpi_table_parse_madt(ACPI_MADT_TYPE_CORE_PIC, acpi_parse_cpuintc, MAX_CORE_PIC);
> > @@ -336,3 +371,36 @@ void __init arch_reserve_mem_area(acpi_physical_address addr, size_t size)
> >   {
> >       memblock_reserve(addr, size);
> >   }
> > +
> > +#ifdef CONFIG_ACPI_HOTPLUG_CPU
> > +
> > +#include <acpi/processor.h>
> > +
> > +int acpi_map_cpu(acpi_handle handle, phys_cpuid_t physid, u32 acpi_id, int *pcpu)
> > +{
> > +     int cpu;
> > +
> > +     cpu = set_processor_mask(physid, ACPI_MADT_ENABLED);
> > +     if (cpu < 0) {
> > +             pr_info(PREFIX "Unable to map lapic to logical cpu number\n");
> > +             return cpu;
> > +     }
> > +
> > +     *pcpu = cpu;
> > +
> > +     return 0;
> > +}
> > +EXPORT_SYMBOL(acpi_map_cpu);
> > +
> > +int acpi_unmap_cpu(int cpu)
> > +{
> > +     set_cpu_present(cpu, false);
> > +     num_processors--;
> > +
> > +     pr_info("cpu%d hot remove!\n", cpu);
> > +
> > +     return 0;
> > +}
> > +EXPORT_SYMBOL(acpi_unmap_cpu);
> > +
> > +#endif /* CONFIG_ACPI_HOTPLUG_CPU */
> > diff --git a/arch/loongarch/kernel/asm-offsets.c b/arch/loongarch/kernel/asm-offsets.c
> > index 3531e3c60a6e..bfb65eb2844f 100644
> > --- a/arch/loongarch/kernel/asm-offsets.c
> > +++ b/arch/loongarch/kernel/asm-offsets.c
> > @@ -252,3 +252,13 @@ void output_signal_defines(void)
> >       DEFINE(_SIGXFSZ, SIGXFSZ);
> >       BLANK();
> >   }
> > +
> > +#ifdef CONFIG_SMP
> > +void output_smpboot_defines(void)
> > +{
> > +     COMMENT("Linux smp cpu boot offsets.");
> > +     OFFSET(CPU_BOOT_STACK, secondary_data, stack);
> > +     OFFSET(CPU_BOOT_TINFO, secondary_data, thread_info);
> > +     BLANK();
> > +}
> > +#endif
> > diff --git a/arch/loongarch/kernel/head.S b/arch/loongarch/kernel/head.S
> > index f0b3e76bb762..9f6c9f3182a5 100644
> > --- a/arch/loongarch/kernel/head.S
> > +++ b/arch/loongarch/kernel/head.S
> > @@ -94,4 +94,34 @@ SYM_CODE_START(kernel_entry)                       # kernel entry point
> >
> >   SYM_CODE_END(kernel_entry)
> >
> > +#ifdef CONFIG_SMP
> > +
> > +/*
> > + * SMP slave cpus entry point.       Board specific code for bootstrap calls this
> > + * function after setting up the stack and tp registers.
> > + */
> > +SYM_CODE_START(smpboot_entry)
> > +     li.d            t0, CSR_DMW0_INIT       # UC, PLV0
> > +     csrwr           t0, LOONGARCH_CSR_DMWIN0
> > +     li.d            t0, CSR_DMW1_INIT       # CA, PLV0
> > +     csrwr           t0, LOONGARCH_CSR_DMWIN1
> > +     li.w            t0, 0xb0                # PLV=0, IE=0, PG=1
> > +     csrwr           t0, LOONGARCH_CSR_CRMD
> > +     li.w            t0, 0x04                # PLV=0, PIE=1, PWE=0
> > +     csrwr           t0, LOONGARCH_CSR_PRMD
> > +     li.w            t0, 0x00                # FPE=0, SXE=0, ASXE=0, BTE=0
> > +     csrwr           t0, LOONGARCH_CSR_EUEN
> > +
> > +     la.abs          t0, cpuboot_data
> > +     ld.d            sp, t0, CPU_BOOT_STACK
> > +     ld.d            tp, t0, CPU_BOOT_TINFO
> > +
> > +     la.abs  t0, 0f
> > +     jirl    zero, t0, 0
> > +0:
> > +     bl              start_secondary
> > +SYM_CODE_END(smpboot_entry)
> > +
> > +#endif /* CONFIG_SMP */
> > +
> >   SYM_ENTRY(kernel_entry_end, SYM_L_GLOBAL, SYM_A_NONE)
> > diff --git a/arch/loongarch/kernel/irq.c b/arch/loongarch/kernel/irq.c
> > index 48032ffd9331..fbb7ad1d1ac0 100644
> > --- a/arch/loongarch/kernel/irq.c
> > +++ b/arch/loongarch/kernel/irq.c
> > @@ -73,6 +73,9 @@ asmlinkage void spurious_interrupt(void)
> >
> >   int arch_show_interrupts(struct seq_file *p, int prec)
> >   {
> > +#ifdef CONFIG_SMP
> > +     show_ipi_list(p, prec);
> > +#endif
> >       seq_printf(p, "%*s: %10u\n", prec, "ERR", atomic_read(&irq_err_count));
> >       return 0;
> >   }
> > @@ -108,13 +111,21 @@ void __init setup_IRQ(void)
> >
> >   void __init init_IRQ(void)
> >   {
> > -     int i;
> > +     int i, r, ipi_irq;
> > +     static int ipi_dummy_dev;
> >       unsigned int order = get_order(IRQ_STACK_SIZE);
> >
> >       clear_csr_ecfg(ECFG0_IM);
> >       clear_csr_estat(ESTATF_IP);
> >
> >       setup_IRQ();
> > +#ifdef CONFIG_SMP
> > +     ipi_irq = get_ipi_irq();
> > +     irq_set_percpu_devid(ipi_irq);
> > +     r = request_percpu_irq(ipi_irq, loongson3_ipi_interrupt, "IPI", &ipi_dummy_dev);
> > +     if (r < 0)
> > +             panic("IPI IRQ request failed\n");
> > +#endif
> >
> >       for (i = 0; i < NR_IRQS; i++)
> >               irq_set_noprobe(i);
> > diff --git a/arch/loongarch/kernel/proc.c b/arch/loongarch/kernel/proc.c
> > index d1f36b510297..e5772b65f956 100644
> > --- a/arch/loongarch/kernel/proc.c
> > +++ b/arch/loongarch/kernel/proc.c
> > @@ -35,6 +35,11 @@ static int show_cpuinfo(struct seq_file *m, void *v)
> >       unsigned int fp_version = cpu_data[n].fpu_vers;
> >       struct proc_cpuinfo_notifier_args proc_cpuinfo_notifier_args;
> >
> > +#ifdef CONFIG_SMP
> > +     if (!cpu_online(n))
> > +             return 0;
> > +#endif
> > +
> >       /*
> >        * For the first processor also print the system type
> >        */
> > diff --git a/arch/loongarch/kernel/process.c b/arch/loongarch/kernel/process.c
> > index 667b365d909d..8f74985f8782 100644
> > --- a/arch/loongarch/kernel/process.c
> > +++ b/arch/loongarch/kernel/process.c
> > @@ -53,6 +53,13 @@
> >   unsigned long boot_option_idle_override = IDLE_NO_OVERRIDE;
> >   EXPORT_SYMBOL(boot_option_idle_override);
> >
> > +#ifdef CONFIG_HOTPLUG_CPU
> > +void arch_cpu_idle_dead(void)
> > +{
> > +     play_dead();
> > +}
> > +#endif
> > +
> >   asmlinkage void ret_from_fork(void);
> >   asmlinkage void ret_from_kernel_thread(void);
> >
> > diff --git a/arch/loongarch/kernel/reset.c b/arch/loongarch/kernel/reset.c
> > index ef484ce43c5c..2b86469e4718 100644
> > --- a/arch/loongarch/kernel/reset.c
> > +++ b/arch/loongarch/kernel/reset.c
> > @@ -65,16 +65,28 @@ EXPORT_SYMBOL(pm_power_off);
> >
> >   void machine_halt(void)
> >   {
> > +#ifdef CONFIG_SMP
> > +     preempt_disable();
> > +     smp_send_stop();
> > +#endif
> >       default_halt();
> >   }
> >
> >   void machine_power_off(void)
> >   {
> > +#ifdef CONFIG_SMP
> > +     preempt_disable();
> > +     smp_send_stop();
> > +#endif
> >       pm_power_off();
> >   }
> >
> >   void machine_restart(char *command)
> >   {
> > +#ifdef CONFIG_SMP
> > +     preempt_disable();
> > +     smp_send_stop();
> > +#endif
> >       do_kernel_restart(command);
> >       pm_restart();
> >   }
> > diff --git a/arch/loongarch/kernel/setup.c b/arch/loongarch/kernel/setup.c
> > index 827d07204e62..c314c22cdb22 100644
> > --- a/arch/loongarch/kernel/setup.c
> > +++ b/arch/loongarch/kernel/setup.c
> > @@ -38,6 +38,7 @@
> >   #include <asm/pgalloc.h>
> >   #include <asm/sections.h>
> >   #include <asm/setup.h>
> > +#include <asm/smp.h>
> >   #include <asm/time.h>
> >
> >   #define SMBIOS_BIOSSIZE_OFFSET              0x09
> > @@ -317,6 +318,29 @@ static int __init reserve_memblock_reserved_regions(void)
> >   }
> >   arch_initcall(reserve_memblock_reserved_regions);
> >
> > +#ifdef CONFIG_SMP
> > +static void __init prefill_possible_map(void)
> > +{
> > +     int i, possible;
> > +
> > +     possible = num_processors + disabled_cpus;
> > +     if (possible > nr_cpu_ids)
> > +             possible = nr_cpu_ids;
> > +
> > +     pr_info("SMP: Allowing %d CPUs, %d hotplug CPUs\n",
> > +                     possible, max((possible - num_processors), 0));
> > +
> > +     for (i = 0; i < possible; i++)
> > +             set_cpu_possible(i, true);
> > +     for (; i < NR_CPUS; i++)
> > +             set_cpu_possible(i, false);
> > +
> > +     nr_cpu_ids = possible;
> > +}
> > +#else
> > +static inline void prefill_possible_map(void) {}
> > +#endif
> > +
> >   void __init setup_arch(char **cmdline_p)
> >   {
> >       cpu_probe();
> > @@ -331,6 +355,8 @@ void __init setup_arch(char **cmdline_p)
> >       arch_mem_init(cmdline_p);
> >
> >       resource_init();
> > +     plat_smp_setup();
> > +     prefill_possible_map();
> >
> >       paging_init();
> >   }
> > diff --git a/arch/loongarch/kernel/smp.c b/arch/loongarch/kernel/smp.c
> > new file mode 100644
> > index 000000000000..4211025c79d3
> > --- /dev/null
> > +++ b/arch/loongarch/kernel/smp.c
> > @@ -0,0 +1,744 @@
> > +// SPDX-License-Identifier: GPL-2.0-or-later
> > +/*
> > + * Copyright (C) 2020-2022 Loongson Technology Corporation Limited
> > + *
> > + * Derived from MIPS:
> > + * Copyright (C) 2000, 2001 Kanoj Sarcar
> > + * Copyright (C) 2000, 2001 Ralf Baechle
> > + * Copyright (C) 2000, 2001 Silicon Graphics, Inc.
> > + * Copyright (C) 2000, 2001, 2003 Broadcom Corporation
> > + */
> > +#include <linux/cpu.h>
> > +#include <linux/cpumask.h>
> > +#include <linux/init.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/seq_file.h>
> > +#include <linux/smp.h>
> > +#include <linux/threads.h>
> > +#include <linux/export.h>
> > +#include <linux/time.h>
> > +#include <linux/tracepoint.h>
> > +#include <linux/sched/hotplug.h>
> > +#include <linux/sched/task_stack.h>
> > +
> > +#include <asm/cpu.h>
> > +#include <asm/idle.h>
> > +#include <asm/loongson.h>
> > +#include <asm/mmu_context.h>
> > +#include <asm/processor.h>
> > +#include <asm/setup.h>
> > +#include <asm/time.h>
> > +
> > +int __cpu_number_map[NR_CPUS];   /* Map physical to logical */
> > +EXPORT_SYMBOL(__cpu_number_map);
> > +
> > +int __cpu_logical_map[NR_CPUS];              /* Map logical to physical */
> > +EXPORT_SYMBOL(__cpu_logical_map);
> > +
> > +/* Number of threads (siblings) per CPU core */
> > +int smp_num_siblings = 1;
> > +EXPORT_SYMBOL(smp_num_siblings);
> > +
> > +/* Representing the threads (siblings) of each logical CPU */
> > +cpumask_t cpu_sibling_map[NR_CPUS] __read_mostly;
> > +EXPORT_SYMBOL(cpu_sibling_map);
> > +
> > +/* Representing the core map of multi-core chips of each logical CPU */
> > +cpumask_t cpu_core_map[NR_CPUS] __read_mostly;
> > +EXPORT_SYMBOL(cpu_core_map);
> > +
> > +static DECLARE_COMPLETION(cpu_starting);
> > +static DECLARE_COMPLETION(cpu_running);
> > +
> > +/*
> > + * A logcal cpu mask containing only one VPE per core to
> > + * reduce the number of IPIs on large MT systems.
> > + */
> > +cpumask_t cpu_foreign_map[NR_CPUS] __read_mostly;
> > +EXPORT_SYMBOL(cpu_foreign_map);
> > +
> > +/* representing cpus for which sibling maps can be computed */
> > +static cpumask_t cpu_sibling_setup_map;
> > +
> > +/* representing cpus for which core maps can be computed */
> > +static cpumask_t cpu_core_setup_map;
> > +
> > +struct secondary_data cpuboot_data;
> > +static DEFINE_PER_CPU(int, cpu_state);
> > +DEFINE_PER_CPU_SHARED_ALIGNED(irq_cpustat_t, irq_stat);
> > +EXPORT_PER_CPU_SYMBOL(irq_stat);
> > +
> > +enum ipi_msg_type {
> > +     IPI_RESCHEDULE,
> > +     IPI_CALL_FUNCTION,
> > +};
> > +
> > +static const char *ipi_types[NR_IPI] __tracepoint_string = {
> > +     [IPI_RESCHEDULE] = "Rescheduling interrupts",
> > +     [IPI_CALL_FUNCTION] = "Call Function interrupts",
> "Function call interrupts" would sound more natural. Plus it's what all
> other arches call it.
OK, thanks.

Huacai
> > +};
> > +
> > +void show_ipi_list(struct seq_file *p, int prec)
> > +{
> > +     unsigned int cpu, i;
> > +
> > +     for (i = 0; i < NR_IPI; i++) {
> > +             seq_printf(p, "%*s%u:%s", prec - 1, "IPI", i, prec >= 4 ? " " : "");
> > +             for_each_online_cpu(cpu)
> > +                     seq_printf(p, "%10u ", per_cpu(irq_stat, cpu).ipi_irqs[i]);
> > +             seq_printf(p, " LoongArch  %d  %s\n", i + 1, ipi_types[i]);
> > +     }
> > +}
> > +
> > +/* Send mailbox buffer via Mail_Send */
> > +static void csr_mail_send(uint64_t data, int cpu, int mailbox)
> > +{
> > +     uint64_t val;
> > +
> > +     /* Send high 32 bits */
> > +     val = IOCSR_MBUF_SEND_BLOCKING;
> > +     val |= (IOCSR_MBUF_SEND_BOX_HI(mailbox) << IOCSR_MBUF_SEND_BOX_SHIFT);
> > +     val |= (cpu << IOCSR_MBUF_SEND_CPU_SHIFT);
> > +     val |= (data & IOCSR_MBUF_SEND_H32_MASK);
> > +     iocsr_writeq(val, LOONGARCH_IOCSR_MBUF_SEND);
> > +
> > +     /* Send low 32 bits */
> > +     val = IOCSR_MBUF_SEND_BLOCKING;
> > +     val |= (IOCSR_MBUF_SEND_BOX_LO(mailbox) << IOCSR_MBUF_SEND_BOX_SHIFT);
> > +     val |= (cpu << IOCSR_MBUF_SEND_CPU_SHIFT);
> > +     val |= (data << IOCSR_MBUF_SEND_BUF_SHIFT);
> > +     iocsr_writeq(val, LOONGARCH_IOCSR_MBUF_SEND);
> > +};
> > +
> > +static u32 ipi_read_clear(int cpu)
> > +{
> > +     u32 action;
> > +
> > +     /* Load the ipi register to figure out what we're supposed to do */
> > +     action = iocsr_readl(LOONGARCH_IOCSR_IPI_STATUS);
> > +     /* Clear the ipi register to clear the interrupt */
> > +     iocsr_writel(action, LOONGARCH_IOCSR_IPI_CLEAR);
> > +     smp_mb();
> > +
> > +     return action;
> > +}
> > +
> > +static void ipi_write_action(int cpu, u32 action)
> > +{
> > +     unsigned int irq = 0;
> > +
> > +     while ((irq = ffs(action))) {
> > +             uint32_t val = IOCSR_IPI_SEND_BLOCKING;
> > +
> > +             val |= (irq - 1);
> > +             val |= (cpu << IOCSR_IPI_SEND_CPU_SHIFT);
> > +             iocsr_writel(val, LOONGARCH_IOCSR_IPI_SEND);
> > +             action &= ~BIT(irq - 1);
> > +     }
> > +}
> > +
> > +void loongson3_send_ipi_single(int cpu, unsigned int action)
> > +{
> > +     ipi_write_action(cpu_logical_map(cpu), (u32)action);
> > +}
> > +
> > +void loongson3_send_ipi_mask(const struct cpumask *mask, unsigned int action)
> > +{
> > +     unsigned int i;
> > +
> > +     for_each_cpu(i, mask)
> > +             ipi_write_action(cpu_logical_map(i), (u32)action);
> > +}
> > +
> > +irqreturn_t loongson3_ipi_interrupt(int irq, void *dev)
> > +{
> > +     unsigned int action;
> > +     unsigned int cpu = smp_processor_id();
> > +
> > +     action = ipi_read_clear(cpu_logical_map(cpu));
> > +
> > +     if (action & SMP_RESCHEDULE) {
> > +             scheduler_ipi();
> > +             per_cpu(irq_stat, cpu).ipi_irqs[IPI_RESCHEDULE]++;
> > +     }
> > +
> > +     if (action & SMP_CALL_FUNCTION) {
> > +             generic_smp_call_function_interrupt();
> > +             per_cpu(irq_stat, cpu).ipi_irqs[IPI_CALL_FUNCTION]++;
> > +     }
> > +
> > +     return IRQ_HANDLED;
> > +}
> > +
> > +void __init loongson3_smp_setup(void)
> > +{
> > +     cpu_data[0].core = cpu_logical_map(0) % loongson_sysconf.cores_per_package;
> > +     cpu_data[0].package = cpu_logical_map(0) / loongson_sysconf.cores_per_package;
> > +
> > +     iocsr_writel(0xffffffff, LOONGARCH_IOCSR_IPI_EN);
> > +     pr_info("Detected %i available CPU(s)\n", loongson_sysconf.nr_cpus);
> > +}
> > +
> > +void __init loongson3_prepare_cpus(unsigned int max_cpus)
> > +{
> > +     int i = 0;
> > +
> > +     for (i = 0; i < loongson_sysconf.nr_cpus; i++) {
> > +             set_cpu_present(i, true);
> > +             csr_mail_send(0, __cpu_logical_map[i], 0);
> > +     }
> > +
> > +     per_cpu(cpu_state, smp_processor_id()) = CPU_ONLINE;
> > +}
> > +
> > +/*
> > + * Setup the PC, SP, and TP of a secondary processor and start it running!
> > + */
> > +void loongson3_boot_secondary(int cpu, struct task_struct *idle)
> > +{
> > +     unsigned long entry;
> > +
> > +     pr_info("Booting CPU#%d...\n", cpu);
> > +
> > +     entry = __pa_symbol((unsigned long)&smpboot_entry);
> > +     cpuboot_data.stack = (unsigned long)__KSTK_TOS(idle);
> > +     cpuboot_data.thread_info = (unsigned long)task_thread_info(idle);
> > +
> > +     csr_mail_send(entry, cpu_logical_map(cpu), 0);
> > +
> > +     loongson3_send_ipi_single(cpu, SMP_BOOT_CPU);
> > +}
> > +
> > +/*
> > + * SMP init and finish on secondary CPUs
> > + */
> > +void loongson3_init_secondary(void)
> > +{
> > +     unsigned int cpu = smp_processor_id();
> > +     unsigned int imask = ECFGF_IP0 | ECFGF_IP1 | ECFGF_IP2 |
> > +                          ECFGF_IPI | ECFGF_PMC | ECFGF_TIMER;
> > +
> > +     change_csr_ecfg(ECFG0_IM, imask);
> > +
> > +     iocsr_writel(0xffffffff, LOONGARCH_IOCSR_IPI_EN);
> > +
> > +     per_cpu(cpu_state, cpu) = CPU_ONLINE;
> > +     cpu_data[cpu].core =
> > +                  cpu_logical_map(cpu) % loongson_sysconf.cores_per_package;
> > +     cpu_data[cpu].package =
> > +                  cpu_logical_map(cpu) / loongson_sysconf.cores_per_package;
> > +}
> > +
> > +void loongson3_smp_finish(void)
> > +{
> > +     local_irq_enable();
> > +     iocsr_writeq(0, LOONGARCH_IOCSR_MBUF0);
> > +     pr_info("CPU#%d finished\n", smp_processor_id());
> > +}
> > +
> > +#ifdef CONFIG_HOTPLUG_CPU
> > +
> > +static bool io_master(int cpu)
> > +{
> > +     int i, node, master;
> > +
> > +     if (cpu == 0)
> > +             return true;
> > +
> > +     for (i = 1; i < loongson_sysconf.nr_io_pics; i++) {
> > +             node = eiointc_get_node(i);
> > +             master = cpu_number_map(node * CORES_PER_EIO_NODE);
> > +             if (cpu == master)
> > +                     return true;
> > +     }
> > +
> > +     return false;
> > +}
> > +
> > +int loongson3_cpu_disable(void)
> > +{
> > +     unsigned long flags;
> > +     unsigned int cpu = smp_processor_id();
> > +
> > +     if (io_master(cpu))
> > +             return -EBUSY;
> > +
> > +     set_cpu_online(cpu, false);
> > +     calculate_cpu_foreign_map();
> > +     local_irq_save(flags);
> > +     irq_migrate_all_off_this_cpu();
> > +     clear_csr_ecfg(ECFG0_IM);
> > +     local_irq_restore(flags);
> > +     local_flush_tlb_all();
> > +
> > +     return 0;
> > +}
> > +
> > +void loongson3_cpu_die(unsigned int cpu)
> > +{
> > +     while (per_cpu(cpu_state, cpu) != CPU_DEAD)
> > +             cpu_relax();
> > +
> > +     mb();
> > +}
> > +
> > +/*
> > + * The target CPU should go to XKPRANGE (uncached area) and flush
> > + * ICache/DCache/VCache before the control CPU can safely disable its clock.
> > + */
> > +static void loongson3_play_dead(int *state_addr)
> > +{
> > +     register int val;
> > +     register void *addr;
> > +     register void (*init_fn)(void);
> > +
> > +     __asm__ __volatile__(
> > +             "   li.d %[addr], 0x8000000000000000\n"
> > +             "1: cacop 0x8, %[addr], 0           \n" /* flush ICache */
> > +             "   cacop 0x8, %[addr], 1           \n"
> > +             "   cacop 0x8, %[addr], 2           \n"
> > +             "   cacop 0x8, %[addr], 3           \n"
> > +             "   cacop 0x9, %[addr], 0           \n" /* flush DCache */
> > +             "   cacop 0x9, %[addr], 1           \n"
> > +             "   cacop 0x9, %[addr], 2           \n"
> > +             "   cacop 0x9, %[addr], 3           \n"
> > +             "   addi.w %[sets], %[sets], -1     \n"
> > +             "   addi.d %[addr], %[addr], 0x40   \n"
> > +             "   bnez %[sets], 1b                \n"
> > +             "   li.d %[addr], 0x8000000000000000\n"
> > +             "2: cacop 0xa, %[addr], 0           \n" /* flush VCache */
> > +             "   cacop 0xa, %[addr], 1           \n"
> > +             "   cacop 0xa, %[addr], 2           \n"
> > +             "   cacop 0xa, %[addr], 3           \n"
> > +             "   cacop 0xa, %[addr], 4           \n"
> > +             "   cacop 0xa, %[addr], 5           \n"
> > +             "   cacop 0xa, %[addr], 6           \n"
> > +             "   cacop 0xa, %[addr], 7           \n"
> > +             "   cacop 0xa, %[addr], 8           \n"
> > +             "   cacop 0xa, %[addr], 9           \n"
> > +             "   cacop 0xa, %[addr], 10          \n"
> > +             "   cacop 0xa, %[addr], 11          \n"
> > +             "   cacop 0xa, %[addr], 12          \n"
> > +             "   cacop 0xa, %[addr], 13          \n"
> > +             "   cacop 0xa, %[addr], 14          \n"
> > +             "   cacop 0xa, %[addr], 15          \n"
> > +             "   addi.w %[vsets], %[vsets], -1   \n"
> > +             "   addi.d %[addr], %[addr], 0x40   \n"
> > +             "   bnez   %[vsets], 2b             \n"
> > +             "   li.w   %[val], 0x7              \n" /* *state_addr = CPU_DEAD; */
> > +             "   st.w   %[val], %[state_addr], 0 \n"
> > +             "   dbar 0                          \n"
> > +             "   cacop 0x11, %[state_addr], 0    \n" /* flush entry of *state_addr */
> > +             : [addr] "=&r" (addr), [val] "=&r" (val)
> > +             : [state_addr] "r" (state_addr),
> > +               [sets] "r" (cpu_data[smp_processor_id()].dcache.sets),
> > +               [vsets] "r" (cpu_data[smp_processor_id()].vcache.sets));
> > +
> > +     local_irq_enable();
> > +     change_csr_ecfg(ECFG0_IM, ECFGF_IPI);
> > +
> > +     __asm__ __volatile__(
> > +             "   idle      0                     \n"
> > +             "   li.w      $t0, 0x1020           \n"
> > +             "   iocsrrd.d %[init_fn], $t0       \n" /* Get init PC */
> > +             : [init_fn] "=&r" (addr)
> > +             : /* No Input */
> > +             : "a0");
> > +     init_fn = __va(addr);
> > +
> > +     init_fn();
> > +     unreachable();
> > +}
> > +
> > +void play_dead(void)
> > +{
> > +     int *state_addr;
> > +     unsigned int cpu = smp_processor_id();
> > +     void (*play_dead_uncached)(int *s);
> > +
> > +     idle_task_exit();
> > +     play_dead_uncached = (void *)TO_UNCACHE(__pa((unsigned long)loongson3_play_dead));
> > +     state_addr = &per_cpu(cpu_state, cpu);
> > +     mb();
> > +     play_dead_uncached(state_addr);
> > +}
> > +
> > +static int loongson3_enable_clock(unsigned int cpu)
> > +{
> > +     uint64_t core_id = cpu_data[cpu].core;
> > +     uint64_t package_id = cpu_data[cpu].package;
> > +
> > +     LOONGSON_FREQCTRL(package_id) |= 1 << (core_id * 4 + 3);
> > +
> > +     return 0;
> > +}
> > +
> > +static int loongson3_disable_clock(unsigned int cpu)
> > +{
> > +     uint64_t core_id = cpu_data[cpu].core;
> > +     uint64_t package_id = cpu_data[cpu].package;
> > +
> > +     LOONGSON_FREQCTRL(package_id) &= ~(1 << (core_id * 4 + 3));
> > +
> > +     return 0;
> > +}
> > +
> > +static int register_loongson3_notifier(void)
> > +{
> > +     return cpuhp_setup_state_nocalls(CPUHP_LOONGARCH_SOC_PREPARE,
> > +                                      "loongarch/loongson:prepare",
> > +                                      loongson3_enable_clock,
> > +                                      loongson3_disable_clock);
> > +}
> > +early_initcall(register_loongson3_notifier);
> > +
> > +#endif
> > +
> > +/*
> > + * Power management
> > + */
> > +#ifdef CONFIG_PM
> > +
> > +static int loongson3_ipi_suspend(void)
> > +{
> > +     return 0;
> > +}
> > +
> > +static void loongson3_ipi_resume(void)
> > +{
> > +     iocsr_writel(0xffffffff, LOONGARCH_IOCSR_IPI_EN);
> > +}
> > +
> > +static struct syscore_ops loongson3_ipi_syscore_ops = {
> > +     .resume         = loongson3_ipi_resume,
> > +     .suspend        = loongson3_ipi_suspend,
> > +};
> > +
> > +/*
> > + * Enable boot cpu ipi before enabling nonboot cpus
> > + * during syscore_resume.
> > + */
> > +static int __init ipi_pm_init(void)
> > +{
> > +     register_syscore_ops(&loongson3_ipi_syscore_ops);
> > +     return 0;
> > +}
> > +
> > +core_initcall(ipi_pm_init);
> > +#endif
> > +
> > +static inline void set_cpu_sibling_map(int cpu)
> > +{
> > +     int i;
> > +
> > +     cpumask_set_cpu(cpu, &cpu_sibling_setup_map);
> > +
> > +     if (smp_num_siblings <= 1)
> > +             cpumask_set_cpu(cpu, &cpu_sibling_map[cpu]);
> > +     else {
> > +             for_each_cpu(i, &cpu_sibling_setup_map) {
> > +                     if (cpus_are_siblings(cpu, i)) {
> > +                             cpumask_set_cpu(i, &cpu_sibling_map[cpu]);
> > +                             cpumask_set_cpu(cpu, &cpu_sibling_map[i]);
> > +                     }
> > +             }
> > +     }
> > +}
> > +
> > +static inline void set_cpu_core_map(int cpu)
> > +{
> > +     int i;
> > +
> > +     cpumask_set_cpu(cpu, &cpu_core_setup_map);
> > +
> > +     for_each_cpu(i, &cpu_core_setup_map) {
> > +             if (cpu_data[cpu].package == cpu_data[i].package) {
> > +                     cpumask_set_cpu(i, &cpu_core_map[cpu]);
> > +                     cpumask_set_cpu(cpu, &cpu_core_map[i]);
> > +             }
> > +     }
> > +}
> > +
> > +/*
> > + * Calculate a new cpu_foreign_map mask whenever a
> > + * new cpu appears or disappears.
> > + */
> > +void calculate_cpu_foreign_map(void)
> > +{
> > +     int i, k, core_present;
> > +     cpumask_t temp_foreign_map;
> > +
> > +     /* Re-calculate the mask */
> > +     cpumask_clear(&temp_foreign_map);
> > +     for_each_online_cpu(i) {
> > +             core_present = 0;
> > +             for_each_cpu(k, &temp_foreign_map)
> > +                     if (cpus_are_siblings(i, k))
> > +                             core_present = 1;
> > +             if (!core_present)
> > +                     cpumask_set_cpu(i, &temp_foreign_map);
> > +     }
> > +
> > +     for_each_online_cpu(i)
> > +             cpumask_andnot(&cpu_foreign_map[i],
> > +                            &temp_foreign_map, &cpu_sibling_map[i]);
> > +}
> > +
> > +/* Preload SMP state for boot cpu */
> > +void smp_prepare_boot_cpu(void)
> > +{
> > +     unsigned int cpu;
> > +
> > +     set_cpu_possible(0, true);
> > +     set_cpu_online(0, true);
> > +     set_my_cpu_offset(per_cpu_offset(0));
> > +
> > +     for_each_possible_cpu(cpu)
> > +             set_cpu_numa_node(cpu, 0);
> > +}
> > +
> > +/* called from main before smp_init() */
> > +void __init smp_prepare_cpus(unsigned int max_cpus)
> > +{
> > +     init_new_context(current, &init_mm);
> > +     current_thread_info()->cpu = 0;
> > +     loongson3_prepare_cpus(max_cpus);
> > +     set_cpu_sibling_map(0);
> > +     set_cpu_core_map(0);
> > +     calculate_cpu_foreign_map();
> > +#ifndef CONFIG_HOTPLUG_CPU
> > +     init_cpu_present(cpu_possible_mask);
> > +#endif
> > +}
> > +
> > +int __cpu_up(unsigned int cpu, struct task_struct *tidle)
> > +{
> > +     loongson3_boot_secondary(cpu, tidle);
> > +
> > +     /* Wait for CPU to start and be ready to sync counters */
> > +     if (!wait_for_completion_timeout(&cpu_starting,
> > +                                      msecs_to_jiffies(5000))) {
> > +             pr_crit("CPU%u: failed to start\n", cpu);
> > +             return -EIO;
> > +     }
> > +
> > +     /* Wait for CPU to finish startup & mark itself online before return */
> > +     wait_for_completion(&cpu_running);
> > +
> > +     return 0;
> > +}
> > +
> > +/*
> > + * First C code run on the secondary CPUs after being started up by
> > + * the master.
> > + */
> > +asmlinkage void start_secondary(void)
> > +{
> > +     unsigned int cpu;
> > +
> > +     sync_counter();
> > +     cpu = smp_processor_id();
> > +     set_my_cpu_offset(per_cpu_offset(cpu));
> > +
> > +     cpu_probe();
> > +     constant_clockevent_init();
> > +     loongson3_init_secondary();
> > +
> > +     set_cpu_sibling_map(cpu);
> > +     set_cpu_core_map(cpu);
> > +
> > +     notify_cpu_starting(cpu);
> > +
> > +     /* Notify boot CPU that we're starting */
> > +     complete(&cpu_starting);
> > +
> > +     /* The CPU is running, now mark it online */
> > +     set_cpu_online(cpu, true);
> > +
> > +     calculate_cpu_foreign_map();
> > +
> > +     /*
> > +      * Notify boot CPU that we're up & online and it can safely return
> > +      * from __cpu_up()
> > +      */
> > +     complete(&cpu_running);
> > +
> > +     /*
> > +      * irq will be enabled in loongson3_smp_finish(), enabling it too
> > +      * early is dangerous.
> > +      */
> > +     WARN_ON_ONCE(!irqs_disabled());
> > +     loongson3_smp_finish();
> > +
> > +     cpu_startup_entry(CPUHP_AP_ONLINE_IDLE);
> > +}
> > +
> > +void __init smp_cpus_done(unsigned int max_cpus)
> > +{
> > +}
> > +
> > +static void stop_this_cpu(void *dummy)
> > +{
> > +     set_cpu_online(smp_processor_id(), false);
> > +     calculate_cpu_foreign_map();
> > +     local_irq_disable();
> > +     while (true);
> > +}
> > +
> > +void smp_send_stop(void)
> > +{
> > +     smp_call_function(stop_this_cpu, NULL, 0);
> > +}
> > +
> > +int setup_profiling_timer(unsigned int multiplier)
> > +{
> > +     return 0;
> > +}
> > +
> > +static void flush_tlb_all_ipi(void *info)
> > +{
> > +     local_flush_tlb_all();
> > +}
> > +
> > +void flush_tlb_all(void)
> > +{
> > +     on_each_cpu(flush_tlb_all_ipi, NULL, 1);
> > +}
> > +
> > +static void flush_tlb_mm_ipi(void *mm)
> > +{
> > +     local_flush_tlb_mm((struct mm_struct *)mm);
> > +}
> > +
> > +void flush_tlb_mm(struct mm_struct *mm)
> > +{
> > +     if (atomic_read(&mm->mm_users) == 0)
> > +             return;         /* happens as a result of exit_mmap() */
> > +
> > +     preempt_disable();
> > +
> > +     if ((atomic_read(&mm->mm_users) != 1) || (current->mm != mm)) {
> > +             on_each_cpu_mask(mm_cpumask(mm), flush_tlb_mm_ipi, mm, 1);
> > +     } else {
> > +             unsigned int cpu;
> > +
> > +             for_each_online_cpu(cpu) {
> > +                     if (cpu != smp_processor_id() && cpu_context(cpu, mm))
> > +                             cpu_context(cpu, mm) = 0;
> > +             }
> > +             local_flush_tlb_mm(mm);
> > +     }
> > +
> > +     preempt_enable();
> > +}
> > +
> > +struct flush_tlb_data {
> > +     struct vm_area_struct *vma;
> > +     unsigned long addr1;
> > +     unsigned long addr2;
> > +};
> > +
> > +static void flush_tlb_range_ipi(void *info)
> > +{
> > +     struct flush_tlb_data *fd = info;
> > +
> > +     local_flush_tlb_range(fd->vma, fd->addr1, fd->addr2);
> > +}
> > +
> > +void flush_tlb_range(struct vm_area_struct *vma, unsigned long start, unsigned long end)
> > +{
> > +     struct mm_struct *mm = vma->vm_mm;
> > +
> > +     preempt_disable();
> > +     if ((atomic_read(&mm->mm_users) != 1) || (current->mm != mm)) {
> > +             struct flush_tlb_data fd = {
> > +                     .vma = vma,
> > +                     .addr1 = start,
> > +                     .addr2 = end,
> > +             };
> > +
> > +             on_each_cpu_mask(mm_cpumask(mm), flush_tlb_range_ipi, &fd, 1);
> > +     } else {
> > +             unsigned int cpu;
> > +             int exec = vma->vm_flags & VM_EXEC;
> > +
> > +             for_each_online_cpu(cpu) {
> > +                     /*
> > +                      * flush_cache_range() will only fully flush icache if
> > +                      * the VMA is executable, otherwise we must invalidate
> > +                      * ASID without it appearing to has_valid_asid() as if
> > +                      * mm has been completely unused by that CPU.
> > +                      */
> > +                     if (cpu != smp_processor_id() && cpu_context(cpu, mm))
> > +                             cpu_context(cpu, mm) = !exec;
> > +             }
> > +             local_flush_tlb_range(vma, start, end);
> > +     }
> > +     preempt_enable();
> > +}
> > +
> > +static void flush_tlb_kernel_range_ipi(void *info)
> > +{
> > +     struct flush_tlb_data *fd = info;
> > +
> > +     local_flush_tlb_kernel_range(fd->addr1, fd->addr2);
> > +}
> > +
> > +void flush_tlb_kernel_range(unsigned long start, unsigned long end)
> > +{
> > +     struct flush_tlb_data fd = {
> > +             .addr1 = start,
> > +             .addr2 = end,
> > +     };
> > +
> > +     on_each_cpu(flush_tlb_kernel_range_ipi, &fd, 1);
> > +}
> > +
> > +static void flush_tlb_page_ipi(void *info)
> > +{
> > +     struct flush_tlb_data *fd = info;
> > +
> > +     local_flush_tlb_page(fd->vma, fd->addr1);
> > +}
> > +
> > +void flush_tlb_page(struct vm_area_struct *vma, unsigned long page)
> > +{
> > +     preempt_disable();
> > +     if ((atomic_read(&vma->vm_mm->mm_users) != 1) || (current->mm != vma->vm_mm)) {
> > +             struct flush_tlb_data fd = {
> > +                     .vma = vma,
> > +                     .addr1 = page,
> > +             };
> > +
> > +             on_each_cpu_mask(mm_cpumask(vma->vm_mm), flush_tlb_page_ipi, &fd, 1);
> > +     } else {
> > +             unsigned int cpu;
> > +
> > +             for_each_online_cpu(cpu) {
> > +                     /*
> > +                      * flush_cache_page() only does partial flushes, so
> > +                      * invalidate ASID without it appearing to
> > +                      * has_valid_asid() as if mm has been completely unused
> > +                      * by that CPU.
> > +                      */
> > +                     if (cpu != smp_processor_id() && cpu_context(cpu, vma->vm_mm))
> > +                             cpu_context(cpu, vma->vm_mm) = 1;
> > +             }
> > +             local_flush_tlb_page(vma, page);
> > +     }
> > +     preempt_enable();
> > +}
> > +EXPORT_SYMBOL(flush_tlb_page);
> > +
> > +static void flush_tlb_one_ipi(void *info)
> > +{
> > +     unsigned long vaddr = (unsigned long) info;
> > +
> > +     local_flush_tlb_one(vaddr);
> > +}
> > +
> > +void flush_tlb_one(unsigned long vaddr)
> > +{
> > +     on_each_cpu(flush_tlb_one_ipi, (void *)vaddr, 1);
> > +}
> > +EXPORT_SYMBOL(flush_tlb_one);
> > diff --git a/arch/loongarch/kernel/topology.c b/arch/loongarch/kernel/topology.c
> > index 3b2cbb95875b..ab1a75c0b5a6 100644
> > --- a/arch/loongarch/kernel/topology.c
> > +++ b/arch/loongarch/kernel/topology.c
> > @@ -1,13 +1,52 @@
> >   // SPDX-License-Identifier: GPL-2.0
> >   #include <linux/cpu.h>
> > +#include <linux/cpumask.h>
> >   #include <linux/init.h>
> > +#include <linux/node.h>
> > +#include <linux/nodemask.h>
> >   #include <linux/percpu.h>
> >
> > -static struct cpu cpu_device;
> > +static DEFINE_PER_CPU(struct cpu, cpu_devices);
> > +
> > +#ifdef CONFIG_HOTPLUG_CPU
> > +int arch_register_cpu(int cpu)
> > +{
> > +     int ret;
> > +     struct cpu *c = &per_cpu(cpu_devices, cpu);
> > +
> > +     c->hotpluggable = 1;
> > +     ret = register_cpu(c, cpu);
> > +     if (ret < 0)
> > +             pr_warn("register_cpu %d failed (%d)\n", cpu, ret);
> > +
> > +     return ret;
> > +}
> > +EXPORT_SYMBOL(arch_register_cpu);
> > +
> > +void arch_unregister_cpu(int cpu)
> > +{
> > +     struct cpu *c = &per_cpu(cpu_devices, cpu);
> > +
> > +     c->hotpluggable = 0;
> > +     unregister_cpu(c);
> > +}
> > +EXPORT_SYMBOL(arch_unregister_cpu);
> > +#endif
> >
> >   static int __init topology_init(void)
> >   {
> > -     return register_cpu(&cpu_device, 0);
> > +     int i, ret;
> > +
> > +     for_each_present_cpu(i) {
> > +             struct cpu *c = &per_cpu(cpu_devices, i);
> > +
> > +             c->hotpluggable = !!i;
> > +             ret = register_cpu(c, i);
> > +             if (ret < 0)
> > +                     pr_warn("topology_init: register_cpu %d failed (%d)\n", i, ret);
> > +     }
> > +
> > +     return 0;
> >   }
> >
> >   subsys_initcall(topology_init);
> > diff --git a/arch/loongarch/kernel/vmlinux.lds.S b/arch/loongarch/kernel/vmlinux.lds.S
> > index 7da4c4d7c50d..006cbb1bd5c6 100644
> > --- a/arch/loongarch/kernel/vmlinux.lds.S
> > +++ b/arch/loongarch/kernel/vmlinux.lds.S
> > @@ -73,6 +73,10 @@ SECTIONS
> >               EXIT_DATA
> >       }
> >
> > +#ifdef CONFIG_SMP
> > +     PERCPU_SECTION(1 << CONFIG_L1_CACHE_SHIFT)
> > +#endif
> > +
> >       .init.bss : {
> >               *(.init.bss)
> >       }
> > diff --git a/arch/loongarch/mm/tlbex.S b/arch/loongarch/mm/tlbex.S
> > index a4ca4e507ee8..606b7800edc6 100644
> > --- a/arch/loongarch/mm/tlbex.S
> > +++ b/arch/loongarch/mm/tlbex.S
> > @@ -88,7 +88,14 @@ vmalloc_done_load:
> >       slli.d  t0, t0, _PTE_T_LOG2
> >       add.d   t1, ra, t0
> >
> > +#ifdef CONFIG_SMP
> > +smp_pgtable_change_load:
> > +#endif
> > +#ifdef CONFIG_SMP
> > +     ll.d    t0, t1, 0
> > +#else
> >       ld.d    t0, t1, 0
> > +#endif
> >       tlbsrch
> >
> >       srli.d  ra, t0, _PAGE_PRESENT_SHIFT
> > @@ -96,7 +103,12 @@ vmalloc_done_load:
> >       beq     ra, $r0, nopage_tlb_load
> >
> >       ori     t0, t0, _PAGE_VALID
> > +#ifdef CONFIG_SMP
> > +     sc.d    t0, t1, 0
> > +     beq     t0, $r0, smp_pgtable_change_load
> > +#else
> >       st.d    t0, t1, 0
> > +#endif
> >       ori     t1, t1, 8
> >       xori    t1, t1, 8
> >       ld.d    t0, t1, 0
> > @@ -120,14 +132,24 @@ vmalloc_load:
> >        * spots a huge page.
> >        */
> >   tlb_huge_update_load:
> > +#ifdef CONFIG_SMP
> > +     ll.d    t0, t1, 0
> > +#else
> >       ld.d    t0, t1, 0
> > +#endif
> >       srli.d  ra, t0, _PAGE_PRESENT_SHIFT
> >       andi    ra, ra, 1
> >       beq     ra, $r0, nopage_tlb_load
> >       tlbsrch
> >
> >       ori     t0, t0, _PAGE_VALID
> > +#ifdef CONFIG_SMP
> > +     sc.d    t0, t1, 0
> > +     beq     t0, $r0, tlb_huge_update_load
> > +     ld.d    t0, t1, 0
> > +#else
> >       st.d    t0, t1, 0
> > +#endif
> >       addu16i.d       t1, $r0, -(CSR_TLBIDX_EHINV >> 16)
> >       addi.d  ra, t1, 0
> >       csrxchg ra, t1, LOONGARCH_CSR_TLBIDX
> > @@ -173,6 +195,7 @@ tlb_huge_update_load:
> >       csrxchg         t1, t0, LOONGARCH_CSR_TLBIDX
> >
> >   nopage_tlb_load:
> > +     dbar    0
> >       csrrd   ra, EXCEPTION_KS2
> >       la.abs  t0, tlb_do_page_fault_0
> >       jirl    $r0, t0, 0
> > @@ -229,7 +252,14 @@ vmalloc_done_store:
> >       slli.d  t0, t0, _PTE_T_LOG2
> >       add.d   t1, ra, t0
> >
> > +#ifdef CONFIG_SMP
> > +smp_pgtable_change_store:
> > +#endif
> > +#ifdef CONFIG_SMP
> > +     ll.d    t0, t1, 0
> > +#else
> >       ld.d    t0, t1, 0
> > +#endif
> >       tlbsrch
> >
> >       srli.d  ra, t0, _PAGE_PRESENT_SHIFT
> > @@ -238,7 +268,12 @@ vmalloc_done_store:
> >       bne     ra, $r0, nopage_tlb_store
> >
> >       ori     t0, t0, (_PAGE_VALID | _PAGE_DIRTY | _PAGE_MODIFIED)
> > +#ifdef CONFIG_SMP
> > +     sc.d    t0, t1, 0
> > +     beq     t0, $r0, smp_pgtable_change_store
> > +#else
> >       st.d    t0, t1, 0
> > +#endif
> >
> >       ori     t1, t1, 8
> >       xori    t1, t1, 8
> > @@ -263,7 +298,11 @@ vmalloc_store:
> >        * spots a huge page.
> >        */
> >   tlb_huge_update_store:
> > +#ifdef CONFIG_SMP
> > +     ll.d    t0, t1, 0
> > +#else
> >       ld.d    t0, t1, 0
> > +#endif
> >       srli.d  ra, t0, _PAGE_PRESENT_SHIFT
> >       andi    ra, ra, ((_PAGE_PRESENT | _PAGE_WRITE) >> _PAGE_PRESENT_SHIFT)
> >       xori    ra, ra, ((_PAGE_PRESENT | _PAGE_WRITE) >> _PAGE_PRESENT_SHIFT)
> > @@ -272,7 +311,13 @@ tlb_huge_update_store:
> >       tlbsrch
> >       ori     t0, t0, (_PAGE_VALID | _PAGE_DIRTY | _PAGE_MODIFIED)
> >
> > +#ifdef CONFIG_SMP
> > +     sc.d    t0, t1, 0
> > +     beq     t0, $r0, tlb_huge_update_store
> > +     ld.d    t0, t1, 0
> > +#else
> >       st.d    t0, t1, 0
> > +#endif
> >       addu16i.d       t1, $r0, -(CSR_TLBIDX_EHINV >> 16)
> >       addi.d  ra, t1, 0
> >       csrxchg ra, t1, LOONGARCH_CSR_TLBIDX
> > @@ -318,6 +363,7 @@ tlb_huge_update_store:
> >       csrxchg         t1, t0, LOONGARCH_CSR_TLBIDX
> >
> >   nopage_tlb_store:
> > +     dbar    0
> >       csrrd   ra, EXCEPTION_KS2
> >       la.abs  t0, tlb_do_page_fault_1
> >       jirl    $r0, t0, 0
> > @@ -373,7 +419,14 @@ vmalloc_done_modify:
> >       slli.d  t0, t0, _PTE_T_LOG2
> >       add.d   t1, ra, t0
> >
> > +#ifdef CONFIG_SMP
> > +smp_pgtable_change_modify:
> > +#endif
> > +#ifdef CONFIG_SMP
> > +     ll.d    t0, t1, 0
> > +#else
> >       ld.d    t0, t1, 0
> > +#endif
> >       tlbsrch
> >
> >       srli.d  ra, t0, _PAGE_WRITE_SHIFT
> > @@ -381,7 +434,12 @@ vmalloc_done_modify:
> >       beq     ra, $r0, nopage_tlb_modify
> >
> >       ori     t0, t0, (_PAGE_VALID | _PAGE_DIRTY | _PAGE_MODIFIED)
> > +#ifdef CONFIG_SMP
> > +     sc.d    t0, t1, 0
> > +     beq     t0, $r0, smp_pgtable_change_modify
> > +#else
> >       st.d    t0, t1, 0
> > +#endif
> >       ori     t1, t1, 8
> >       xori    t1, t1, 8
> >       ld.d    t0, t1, 0
> > @@ -405,7 +463,11 @@ vmalloc_modify:
> >        * build_tlbchange_handler_head spots a huge page.
> >        */
> >   tlb_huge_update_modify:
> > +#ifdef CONFIG_SMP
> > +     ll.d    t0, t1, 0
> > +#else
> >       ld.d    t0, t1, 0
> > +#endif
> >
> >       srli.d  ra, t0, _PAGE_WRITE_SHIFT
> >       andi    ra, ra, 1
> > @@ -414,7 +476,13 @@ tlb_huge_update_modify:
> >       tlbsrch
> >       ori     t0, t0, (_PAGE_VALID | _PAGE_DIRTY | _PAGE_MODIFIED)
> >
> > +#ifdef CONFIG_SMP
> > +     sc.d    t0, t1, 0
> > +     beq     t0, $r0, tlb_huge_update_modify
> > +     ld.d    t0, t1, 0
> > +#else
> >       st.d    t0, t1, 0
> > +#endif
> >       /*
> >        * A huge PTE describes an area the size of the
> >        * configured huge page size. This is twice the
> > @@ -454,6 +522,7 @@ tlb_huge_update_modify:
> >       csrxchg t1, t0, LOONGARCH_CSR_TLBIDX
> >
> >   nopage_tlb_modify:
> > +     dbar    0
> >       csrrd   ra, EXCEPTION_KS2
> >       la.abs  t0, tlb_do_page_fault_1
> >       jirl    $r0, t0, 0
> > diff --git a/include/linux/cpuhotplug.h b/include/linux/cpuhotplug.h
> > index 2af7c6587875..8abd28c4f32b 100644
> > --- a/include/linux/cpuhotplug.h
> > +++ b/include/linux/cpuhotplug.h
> > @@ -130,6 +130,7 @@ enum cpuhp_state {
> >       CPUHP_ZCOMP_PREPARE,
> >       CPUHP_TIMERS_PREPARE,
> >       CPUHP_MIPS_SOC_PREPARE,
> > +     CPUHP_LOONGARCH_SOC_PREPARE,
> >       CPUHP_BP_PREPARE_DYN,
> >       CPUHP_BP_PREPARE_DYN_END                = CPUHP_BP_PREPARE_DYN + 20,
> >       CPUHP_BRINGUP_CPU,
>
> All of this looks reasonable, not that I write kernel code day and
> night, but maybe I just read too much arch/mips code in the past years
> and the changes here look natural to me. And the names and code flows
> are sensible enough that people moderately familiar with the kernel
> (such as me) could easily follow.
>
> So, like with some earlier patches, a rather weak:
>
> Reviewed-by: WANG Xuerui <git@xxxxxxxxxx>
>



[Index of Archives]     [Linux Kernel]     [Kernel Newbies]     [x86 Platform Driver]     [Netdev]     [Linux Wireless]     [Netfilter]     [Bugtraq]     [Linux Filesystems]     [Yosemite Discussion]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Samba]     [Device Mapper]

  Powered by Linux