Hi Himanshu,

On 10/14/26 11:55 AM, Himanshu Chauhan wrote:
> Hi Zhanpeng,
>
> On Mon, Sep 21, 2026 at 07:14:59PM +0800, Zhanpeng Zhang wrote:
>> From: Clément Léger <[email protected]>
>>
>> The SBI SSE extension allows firmware to notify supervisor software of
>> events that must be delivered independently of normal S-mode interrupts.
>> Firmware saves the minimal state required to enter the supervisor handler,
>> and Linux builds the synthetic handler context around it.
>>
>> SSE can arrive while Linux is already in an exception entry path. At that
>> point sscratch and tp may be in the middle of the normal trap-entry
>> exchange, so they cannot always identify current. Store current in a
>> per-CPU slot and use the hart ID passed by firmware to recover it.
>>
>> Give each event, including each CPU instance of a local event, a dedicated
>> stack and shadow call stack. Synchronize vmapped stack ranges before
>> unmasking events so that the handler cannot take a vmalloc fault while
>> running in an NMI-like context.
>>
>> The handler is a synthetic supervisor episode, but completion must resume
>> the context interrupted by the SSE. Preserve stvec and, when the hypervisor
>> extension is present, hstatus across the handler. Read the interrupted a6
>> and a7 values from the SSE attributes, construct pt_regs for the
>> interrupted context, and write back any changes made by the handler.
>>
>> Nested exceptions on the SSE event stack temporarily replace both
>> TASK_TI_KERNEL_SP and TASK_TI_USER_SP. Preserve their original values
>> across the handler and restore them before completing the event, so a
>> nested exception cannot leave the interrupted task referring to the event
>> stack.
>>
>> Keep an explicit EVENT_REGISTER not-supported result distinct from other
>> firmware failures. This lets clients select another delivery mechanism
>> only when firmware has positively rejected the requested event.
>>
>> Signed-off-by: Clément Léger <[email protected]>
>> Co-developed-by: Himanshu Chauhan <[email protected]>
>> Signed-off-by: Himanshu Chauhan <[email protected]>
>> Co-developed-by: Zhanpeng Zhang <[email protected]>
>> Signed-off-by: Zhanpeng Zhang <[email protected]>
>> ---
>>   MAINTAINERS                          |  12 ++
>>   arch/riscv/include/asm/asm.h         |  14 +-
>>   arch/riscv/include/asm/scs.h         |   7 +
>>   arch/riscv/include/asm/sse.h         |  82 +++++++++
>>   arch/riscv/include/asm/thread_info.h |   1 +
>>   arch/riscv/kernel/Makefile           |   1 +
>>   arch/riscv/kernel/asm-offsets.c      |  14 ++
>>   arch/riscv/kernel/entry.S            |  14 ++
>>   arch/riscv/kernel/sbi_sse.c          | 246 +++++++++++++++++++++++++++
>>   arch/riscv/kernel/sbi_sse_entry.S    | 226 ++++++++++++++++++++++++
>>   10 files changed, 614 insertions(+), 3 deletions(-)
>>   create mode 100644 arch/riscv/include/asm/sse.h
>>   create mode 100644 arch/riscv/kernel/sbi_sse.c
>>   create mode 100644 arch/riscv/kernel/sbi_sse_entry.S
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index cc3cae2e378b..7a6af19f7e30 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -23595,6 +23595,18 @@ F:  arch/riscv/boot/dts/spacemit/
>>   N: spacemit
>>   K: spacemit
>>
>> +RISC-V SUPERVISOR SOFTWARE EVENTS
>> +M:  Zhanpeng Zhang <[email protected]>
>> +M:  Himanshu Chauhan <[email protected]>
>> +R:  Yunhui Cui <[email protected]>
>> +L:  [email protected]
>> +S:  Maintained
>> +F:  arch/riscv/include/asm/sse.h
>> +F:  arch/riscv/kernel/sbi_sse.c
>> +F:  arch/riscv/kernel/sbi_sse_entry.S
>> +F:  drivers/firmware/riscv/riscv_sbi_sse.c
>> +F:  include/linux/riscv_sbi_sse.h
>> +
> I would suggest that you make changes in maintainers as a separate patch.
>
Agreed. I'll move the MAINTAINERS changes into a separate patch in v11.

>>   RISC-V TENSTORRENT SoC SUPPORT
>>   M: Drew Fustini <[email protected]>
>>   M: Joel Stanley <[email protected]>
>> diff --git a/arch/riscv/include/asm/asm.h b/arch/riscv/include/asm/asm.h
>> index b8bf842d4c13..e1196caea02d 100644
>> --- a/arch/riscv/include/asm/asm.h
>> +++ b/arch/riscv/include/asm/asm.h
>> @@ -91,16 +91,24 @@
>>   .endm
>>
>>   #ifdef CONFIG_SMP
>> -.macro asm_per_cpu dst sym tmp
>> -    lw    \tmp, TASK_TI_CPU_NUM(tp)
>> -    slli  \tmp, \tmp, RISCV_LGPTR
>> +.macro asm_per_cpu_with_cpu dst sym tmp cpu
>> +    slli  \tmp, \cpu, RISCV_LGPTR
>>      la    \dst, __per_cpu_offset
>>      add   \dst, \dst, \tmp
>>      REG_L \tmp, 0(\dst)
>>      la    \dst, \sym
>>      add   \dst, \dst, \tmp
>>   .endm
>> +
>> +.macro asm_per_cpu dst sym tmp
>> +    lw \tmp, TASK_TI_CPU_NUM(tp)
>> +    asm_per_cpu_with_cpu \dst \sym \tmp \tmp
>> +.endm
>>   #else /* CONFIG_SMP */
>> +.macro asm_per_cpu_with_cpu dst sym tmp cpu
>> +    la    \dst, \sym
>> +.endm
>> +
>>   .macro asm_per_cpu dst sym tmp
>>      la    \dst, \sym
>>   .endm
>> diff --git a/arch/riscv/include/asm/scs.h b/arch/riscv/include/asm/scs.h
>> index 023a412fe38d..0d70a35bc01a 100644
>> --- a/arch/riscv/include/asm/scs.h
>> +++ b/arch/riscv/include/asm/scs.h
>> @@ -17,6 +17,11 @@
>>      load_per_cpu gp, irq_shadow_call_stack_ptr, \tmp
>>   .endm
>>
>> +/* Load the per-CPU IRQ shadow call stack to gp. */
>> +.macro scs_load_sse_stack reg_evt
>> +    REG_L gp, SSE_REG_EVT_SHADOW_STACK(\reg_evt)
>> +.endm
>> +
>>   /* Load task_scs_sp(current) to gp. */
>>   .macro scs_load_current
>>      REG_L   gp, TASK_TI_SCS_SP(tp)
>> @@ -40,6 +45,8 @@
>>   .endm
>>   .macro scs_load_irq_stack tmp
>>   .endm
>> +.macro scs_load_sse_stack reg_evt
>> +.endm
>>   .macro scs_load_current
>>   .endm
>>   .macro scs_load_current_if_task_changed prev
>> diff --git a/arch/riscv/include/asm/sse.h b/arch/riscv/include/asm/sse.h
>> new file mode 100644
>> index 000000000000..cbd8618c6e00
>> --- /dev/null
>> +++ b/arch/riscv/include/asm/sse.h
>> @@ -0,0 +1,82 @@
>> +/* SPDX-License-Identifier: GPL-2.0-only */
>> +/*
>> + * Copyright (C) 2024 Rivos Inc.
>> + */
>> +#ifndef __ASM_SSE_H
>> +#define __ASM_SSE_H
>> +
>> +#include <linux/printk.h>
>> +#include <linux/types.h>
>> +
>> +#include <asm/sbi.h>
>> +
>> +static inline bool riscv_sse_available(void)
>> +{
>> +#ifdef CONFIG_RISCV_SBI
>> +    return sbi_probe_extension(SBI_EXT_SSE) > 0;
>> +#else
>> +    return false;
>> +#endif
>> +}
>> +
>> +static inline void riscv_sse_mask_current_hart(void)
>> +{
>> +#ifdef CONFIG_RISCV_SBI
>> +    struct sbiret ret;
>> +
>> +    if (!riscv_sse_available())
>> +            return;
>> +
>> +    ret = sbi_ecall(SBI_EXT_SSE, SBI_SSE_HART_MASK, 0, 0, 0, 0, 0, 0);
>> +    if (ret.error && ret.error != SBI_ERR_ALREADY_STOPPED)
>> +            pr_emerg("SSE hart mask failed: %ld\n", ret.error);
>> +#endif
>> +}
>> +
>> +#ifdef CONFIG_RISCV_SBI_SSE
>> +
>> +struct sse_event_interrupted_state {
>> +    unsigned long a6;
>> +    unsigned long a7;
>> +};
>> +
>> +struct sse_event_arch_data {
>> +    void *stack;
>> +    void *shadow_stack;
>> +    unsigned long tmp;
>> +    struct sse_event_interrupted_state *interrupted;
>> +    phys_addr_t interrupted_phys;
>> +    u32 evt_id;
>> +    unsigned long hart_id;
>> +    unsigned int cpu_id;
>> +};
>> +
>> +struct riscv_sse_interrupted_context {
>> +    struct pt_regs *regs;
>> +    unsigned long hstatus;
>> +};
>> +
>> +static inline bool sse_event_is_global(u32 evt)
>> +{
>> +    return !!(evt & SBI_SSE_EVENT_GLOBAL);
>> +}
>> +
>> +void arch_sse_event_update_cpu(struct sse_event_arch_data *arch_evt, int 
>> cpu);
>> +int arch_sse_init_event(struct sse_event_arch_data *arch_evt, u32 evt_id,
>> +                    int cpu);
>> +void arch_sse_free_event(struct sse_event_arch_data *arch_evt);
>> +int arch_sse_register_event(struct sse_event_arch_data *arch_evt);
>> +void arch_sse_init_cpu(void);
>> +
>> +void sse_handle_event(struct sse_event_arch_data *arch_evt,
>> +                  struct pt_regs *regs);
>> +asmlinkage void handle_sse(void);
>> +asmlinkage void noinstr do_sse(struct sse_event_arch_data *arch_evt,
>> +                           struct pt_regs *regs, unsigned long hstatus);
>> +
>> +const struct riscv_sse_interrupted_context *
>> +riscv_sse_get_interrupted_context(void);
>> +
>> +#endif
>> +
>> +#endif
>> diff --git a/arch/riscv/include/asm/thread_info.h 
>> b/arch/riscv/include/asm/thread_info.h
>> index 55019fdfa9ec..d14b45610c73 100644
>> --- a/arch/riscv/include/asm/thread_info.h
>> +++ b/arch/riscv/include/asm/thread_info.h
>> @@ -36,6 +36,7 @@
>>   #define OVERFLOW_STACK_SIZE     SZ_4K
>>
>>   #define IRQ_STACK_SIZE             THREAD_SIZE
>> +#define SSE_STACK_SIZE              THREAD_SIZE
>>
>>   #ifndef __ASSEMBLER__
>>
>> diff --git a/arch/riscv/kernel/Makefile b/arch/riscv/kernel/Makefile
>> index ebe1c3588177..1e34f87e97c0 100644
>> --- a/arch/riscv/kernel/Makefile
>> +++ b/arch/riscv/kernel/Makefile
>> @@ -101,6 +101,7 @@ obj-$(CONFIG_DYNAMIC_FTRACE)     += mcount-dyn.o
>>   obj-$(CONFIG_PERF_EVENTS)  += perf_callchain.o
>>   obj-$(CONFIG_HAVE_PERF_REGS)       += perf_regs.o
>>   obj-$(CONFIG_RISCV_SBI)            += sbi.o sbi_ecall.o
>> +obj-$(CONFIG_RISCV_SBI_SSE) += sbi_sse.o sbi_sse_entry.o
>>   ifeq ($(CONFIG_RISCV_SBI), y)
>>   obj-$(CONFIG_SMP)          += sbi-ipi.o
>>   obj-$(CONFIG_SMP) += cpu_ops_sbi.o
>> diff --git a/arch/riscv/kernel/asm-offsets.c 
>> b/arch/riscv/kernel/asm-offsets.c
>> index a75f0cfea1e9..15363703cdd6 100644
>> --- a/arch/riscv/kernel/asm-offsets.c
>> +++ b/arch/riscv/kernel/asm-offsets.c
>> @@ -15,6 +15,8 @@
>>   #include <asm/ptrace.h>
>>   #include <asm/cpu_ops_sbi.h>
>>   #include <asm/stacktrace.h>
>> +#include <asm/sbi.h>
>> +#include <asm/sse.h>
>>   #include <asm/suspend.h>
>>
>>   void asm_offsets(void);
>> @@ -533,6 +535,18 @@ void asm_offsets(void)
>>      DEFINE(FREGS_A6,            offsetof(struct __arch_ftrace_regs, a6));
>>      DEFINE(FREGS_A7,            offsetof(struct __arch_ftrace_regs, a7));
>>   #endif
>> +
>> +#ifdef CONFIG_RISCV_SBI_SSE
>> +    OFFSET(SSE_REG_EVT_STACK, sse_event_arch_data, stack);
>> +    OFFSET(SSE_REG_EVT_SHADOW_STACK, sse_event_arch_data, shadow_stack);
>> +    OFFSET(SSE_REG_EVT_TMP, sse_event_arch_data, tmp);
>> +    OFFSET(SSE_REG_HART_ID, sse_event_arch_data, hart_id);
>> +    OFFSET(SSE_REG_CPU_ID, sse_event_arch_data, cpu_id);
>> +
>> +    DEFINE(SBI_EXT_SSE, SBI_EXT_SSE);
>> +    DEFINE(SBI_SSE_EVENT_COMPLETE, SBI_SSE_EVENT_COMPLETE);
>> +    DEFINE(ASM_NR_CPUS, CONFIG_NR_CPUS);
>> +#endif
>>   #ifdef CONFIG_RISCV_SBI
>>      DEFINE(SBI_EXT_FWFT, SBI_EXT_FWFT);
>>      DEFINE(SBI_EXT_FWFT_SET, SBI_EXT_FWFT_SET);
>> diff --git a/arch/riscv/kernel/entry.S b/arch/riscv/kernel/entry.S
>> index d799c4e56f80..0b79fa7241ea 100644
>> --- a/arch/riscv/kernel/entry.S
>> +++ b/arch/riscv/kernel/entry.S
>> @@ -424,6 +424,15 @@ SYM_FUNC_END(call_on_irq_stack)
>>    * arguments are passed to schedule_tail.
>>    */
>>   SYM_FUNC_START(__switch_to)
>> +#ifdef CONFIG_RISCV_SBI_SSE
>> +    /*
>> +     * Mark the interval where tp changes from prev to next. SSE entry uses
>> +     * the interrupted tp while this per-CPU pointer is NULL.
>> +     */
>> +    asm_per_cpu t0, __sbi_sse_entry_task, t1
>> +    REG_S zero, 0(t0)
>> +#endif
>> +
>>      /* Save context into prev->thread */
>>      li    a4,  TASK_THREAD_RA
>>      add   a3, a0, a4
>> @@ -470,6 +479,11 @@ SYM_FUNC_START(__switch_to)
>>      REG_L s11, TASK_THREAD_S11_RA(a4)
>>      /* The offset of thread_info in task_struct is zero. */
>>      move tp, a1
>> +#ifdef CONFIG_RISCV_SBI_SSE
>> +    /* Publish next only after tp contains its task_struct pointer. */
>> +    asm_per_cpu t0, __sbi_sse_entry_task, t1
>> +    REG_S tp, 0(t0)
>> +#endif
>>      /* Switch to the next shadow call stack */
>>      scs_load_current
>>      ret
>> diff --git a/arch/riscv/kernel/sbi_sse.c b/arch/riscv/kernel/sbi_sse.c
>> new file mode 100644
>> index 000000000000..7dd496e2bdb0
>> --- /dev/null
>> +++ b/arch/riscv/kernel/sbi_sse.c
>> @@ -0,0 +1,246 @@
>> +// SPDX-License-Identifier: GPL-2.0-or-later
>> +/*
>> + * Copyright (C) 2025 Rivos Inc.
>> + */
>> +#include <linux/nmi.h>
>> +#include <linux/sched.h>
>> +#include <linux/scs.h>
>> +#include <linux/bitfield.h>
>> +#include <linux/percpu-defs.h>
>> +#include <linux/string.h>
>> +
>> +#include <asm/asm-prototypes.h>
>> +#include <asm/switch_to.h>
>> +#include <asm/irq_stack.h>
>> +#include <asm/sbi.h>
>> +#include <asm/sse.h>
>> +#include <asm/tlbflush.h>
>> +
>> +DEFINE_PER_CPU(struct task_struct *, __sbi_sse_entry_task);
>> +static DEFINE_PER_CPU(struct riscv_sse_interrupted_context *,
>> +                          riscv_sse_interrupted_context);
>> +
>> +const struct riscv_sse_interrupted_context *
>> +riscv_sse_get_interrupted_context(void)
>> +{
>> +    return this_cpu_read(riscv_sse_interrupted_context);
>> +}
>> +
>> +void __weak sse_handle_event(struct sse_event_arch_data *arch_evt, struct 
>> pt_regs *regs)
>> +{
>> +}
>> +
>> +void noinstr do_sse(struct sse_event_arch_data *arch_evt,
>> +                struct pt_regs *regs, unsigned long hstatus)
>> +{
>> +    struct riscv_sse_interrupted_context context = { regs, hstatus };
>> +    struct riscv_sse_interrupted_context *previous;
>> +    struct sbiret sret;
>> +
>> +    nmi_enter();
>> +    instrumentation_begin();
>> +
>> +    /* Retrieve missing GPRs from SBI */
>> +    sret = sbi_ecall(SBI_EXT_SSE, SBI_SSE_EVENT_ATTR_READ, arch_evt->evt_id,
>> +                     SBI_SSE_ATTR_INTERRUPTED_A6,
>> +                     (SBI_SSE_ATTR_INTERRUPTED_A7 -
>> +                      SBI_SSE_ATTR_INTERRUPTED_A6) + 1,
>> +                     (unsigned long)arch_evt->interrupted_phys, 0, 0);
>> +    if (sret.error) {
>> +            pr_warn("Failed to read interrupted registers for event %x: 
>> %ld\n",
>> +                    arch_evt->evt_id, sret.error);
>> +            /* Let the client quiesce its source without using incomplete 
>> regs. */
>> +            sse_handle_event(arch_evt, NULL);
>> +            goto out;
>> +    }
>> +
>> +    memcpy(&regs->a6, arch_evt->interrupted,
>> +           sizeof(*arch_evt->interrupted));
>> +
>> +    /* Make the interrupted frame visible while clients handle this event. 
>> */
>> +    previous = this_cpu_read(riscv_sse_interrupted_context);
>> +    this_cpu_write(riscv_sse_interrupted_context, &context);
>> +    sse_handle_event(arch_evt, regs);
>> +    this_cpu_write(riscv_sse_interrupted_context, previous);
>> +
>> +    if (memcmp(&regs->a6, arch_evt->interrupted,
>> +               sizeof(*arch_evt->interrupted))) {
>> +            memcpy(arch_evt->interrupted, &regs->a6,
>> +                   sizeof(*arch_evt->interrupted));
>> +            sret = sbi_ecall(SBI_EXT_SSE, SBI_SSE_EVENT_ATTR_WRITE,
>> +                             arch_evt->evt_id, SBI_SSE_ATTR_INTERRUPTED_A6,
>> +                             (SBI_SSE_ATTR_INTERRUPTED_A7 -
>> +                              SBI_SSE_ATTR_INTERRUPTED_A6) + 1,
>> +                             (unsigned long)arch_evt->interrupted_phys, 0, 
>> 0);
>> +            /*
>> +             * If writeback fails, COMPLETE resumes with firmware's original
>> +             * a6/a7 rather than treating the shared buffer as committed.
>> +             */
>> +            if (sret.error)
>> +                    pr_warn("Failed to write interrupted registers for 
>> event %x: %ld\n",
>> +                            arch_evt->evt_id, sret.error);
>> +    }
>> +
>> +out:
>> +    instrumentation_end();
>> +    nmi_exit();
>> +}
>> +
>> +static void *alloc_to_stack_pointer(void *alloc)
>> +{
>> +    return alloc ? alloc + SSE_STACK_SIZE : NULL;
>> +}
>> +
>> +static void *stack_pointer_to_alloc(void *stack)
>> +{
>> +    return stack ? stack - SSE_STACK_SIZE : NULL;
>> +}
>> +
>> +static void arch_sse_flush_tlb_range(struct sse_event_arch_data *arch_evt,
>> +                                 unsigned long start, unsigned long size)
>> +{
>> +    unsigned long end = start + size;
>> +
>> +    if (sse_event_is_global(arch_evt->evt_id))
>> +            flush_tlb_kernel_range(start, end);
>> +    else
>> +            local_flush_tlb_kernel_range(start, end);
>> +}
>> +
>> +static void arch_sse_shadow_stack_cpu_sync(struct sse_event_arch_data 
>> *arch_evt)
>> +{
>> +#ifdef CONFIG_SHADOW_CALL_STACK
>> +    if (arch_evt->shadow_stack)
>> +            arch_sse_flush_tlb_range(arch_evt,
>> +                                     (unsigned long)arch_evt->shadow_stack,
>> +                                     SCS_SIZE);
>> +#endif
>> +}
>> +
>> +#ifdef CONFIG_VMAP_STACK
>> +static void *sse_stack_alloc(unsigned int cpu)
>> +{
>> +    void *stack = arch_alloc_vmap_stack(SSE_STACK_SIZE, cpu_to_node(cpu));
>> +
>> +    return alloc_to_stack_pointer(stack);
>> +}
>> +
>> +static void sse_stack_free(void *stack)
>> +{
>> +    vfree(stack_pointer_to_alloc(stack));
>> +}
>> +
>> +static void arch_sse_stack_cpu_sync(struct sse_event_arch_data *arch_evt)
>> +{
>> +    void *p_stack = arch_evt->stack;
>> +    unsigned long stack = (unsigned long)stack_pointer_to_alloc(p_stack);
>> +
>> +    /*
>> +     * Flush the tlb to avoid taking any exception when accessing the
>> +     * vmapped stack inside the SSE handler
>> +     */
>> +    arch_sse_flush_tlb_range(arch_evt, stack, SSE_STACK_SIZE);
>> +
>> +    arch_sse_shadow_stack_cpu_sync(arch_evt);
>> +}
>> +#else /* CONFIG_VMAP_STACK */
>> +static void *sse_stack_alloc(unsigned int cpu)
>> +{
>> +    void *stack = kmalloc(SSE_STACK_SIZE, GFP_KERNEL);
>> +
>> +    return alloc_to_stack_pointer(stack);
>> +}
>> +
>> +static void sse_stack_free(void *stack)
>> +{
>> +    kfree(stack_pointer_to_alloc(stack));
>> +}
>> +
>> +static void arch_sse_stack_cpu_sync(struct sse_event_arch_data *arch_evt)
>> +{
>> +    arch_sse_shadow_stack_cpu_sync(arch_evt);
>> +}
>> +#endif /* CONFIG_VMAP_STACK */
>> +
>> +static int sse_init_scs(int cpu, struct sse_event_arch_data *arch_evt)
>> +{
>> +    void *stack;
>> +
>> +    if (!scs_is_enabled())
>> +            return 0;
>> +
>> +    stack = scs_alloc(cpu_to_node(cpu));
>> +    if (!stack)
>> +            return -ENOMEM;
>> +
>> +    arch_evt->shadow_stack = stack;
>> +
>> +    return 0;
>> +}
>> +
>> +void arch_sse_event_update_cpu(struct sse_event_arch_data *arch_evt, int 
>> cpu)
>> +{
>> +    arch_evt->cpu_id = cpu;
>> +    arch_evt->hart_id = cpuid_to_hartid_map(cpu);
>> +}
>> +
>> +void arch_sse_init_cpu(void)
>> +{
>> +    __this_cpu_write(__sbi_sse_entry_task, current);
>> +}
>> +
> This is called from sse_cpu_online() in the cpuhp callback, which runs 
> preemptible, and the
> boot CPU instance runs from an initcall. With DEBUG_PREEMPT you can get a 
> __this_cpu_write() in
> preemptible warning. Can you check? Moreover, if the task migrates between 
> computing the per-CPU
> address and the store, the wrong CPU's slot is written. I would suggest use 
> this_cpu_write()
> under preempt_disable().
>
Agreed. The existing preemption guards do not cover arch_sse_init_cpu().
I'll add preemption protection around the per-CPU update.

>> +int arch_sse_init_event(struct sse_event_arch_data *arch_evt, u32 evt_id,
>> +                    int cpu)
>> +{
>> +    void *stack;
>> +
>> +    arch_evt->interrupted = kmalloc_obj(*arch_evt->interrupted, GFP_KERNEL);
>> +    if (!arch_evt->interrupted)
>> +            return -ENOMEM;
>> +
>> +    arch_evt->evt_id = evt_id;
>> +    stack = sse_stack_alloc(cpu);
>> +    if (!stack)
>> +            goto err_free_interrupted;
>> +
>> +    arch_evt->stack = stack;
>> +
>> +    if (sse_init_scs(cpu, arch_evt)) {
>> +            sse_stack_free(arch_evt->stack);
>> +            goto err_free_interrupted;
>> +    }
>> +
>> +    /* kmalloc keeps the two adjacent SBI attribute words contiguous. */
>> +    arch_evt->interrupted_phys = virt_to_phys(arch_evt->interrupted);
>> +
>> +    arch_sse_event_update_cpu(arch_evt, cpu);
>> +
>> +    return 0;
>> +
>> +err_free_interrupted:
>> +    kfree(arch_evt->interrupted);
>> +    arch_evt->interrupted = NULL;
>> +    return -ENOMEM;
>> +}
>> +
>> +void arch_sse_free_event(struct sse_event_arch_data *arch_evt)
>> +{
>> +    scs_free(arch_evt->shadow_stack);
>> +    sse_stack_free(arch_evt->stack);
>> +    kfree(arch_evt->interrupted);
>> +}
>> +
>> +int arch_sse_register_event(struct sse_event_arch_data *arch_evt)
>> +{
>> +    struct sbiret sret;
>> +
>> +    arch_sse_stack_cpu_sync(arch_evt);
>> +
>> +    sret = sbi_ecall(SBI_EXT_SSE, SBI_SSE_EVENT_REGISTER, arch_evt->evt_id,
>> +                     (unsigned long)handle_sse, (unsigned long)arch_evt, 0,
>> +                     0, 0);
>> +    if (sret.error == SBI_ERR_NOT_SUPPORTED)
>> +            return -EOPNOTSUPP;
>> +
>> +    return sbi_err_map_linux_errno(sret.error);
>> +}
>> diff --git a/arch/riscv/kernel/sbi_sse_entry.S 
>> b/arch/riscv/kernel/sbi_sse_entry.S
>> new file mode 100644
>> index 000000000000..e0e8efba12dd
>> --- /dev/null
>> +++ b/arch/riscv/kernel/sbi_sse_entry.S
>> @@ -0,0 +1,226 @@
>> +/* SPDX-License-Identifier: GPL-2.0-or-later */
>> +/*
>> + * Copyright (C) 2025 Rivos Inc.
>> + */
>> +
>> +#include <linux/init.h>
>> +#include <linux/linkage.h>
>> +
>> +#include <asm/alternative-macros.h>
>> +#include <asm/asm.h>
>> +#include <asm/csr.h>
>> +#include <asm/hwcap.h>
>> +#include <asm/scs.h>
>> +
>> +/* When entering handle_sse, the following registers are set:
>> + * a6: contains the hartid
>> + * a7: contains a sse_event_arch_data struct pointer
>> + */
>> +SYM_CODE_START(handle_sse)
>> +    /* Save stack temporarily */
>> +    REG_S sp, SSE_REG_EVT_TMP(a7)
>> +    /* Set entry stack */
>> +    REG_L sp, SSE_REG_EVT_STACK(a7)
>> +
>> +    addi sp, sp, -(PT_SIZE_ON_STACK)
>> +    REG_S ra, PT_RA(sp)
>> +    REG_S s0, PT_S0(sp)
>> +    REG_S s1, PT_S1(sp)
>> +    REG_S s2, PT_S2(sp)
>> +    REG_S s3, PT_S3(sp)
>> +    REG_S s4, PT_S4(sp)
>> +    REG_S s5, PT_S5(sp)
>> +    REG_S s6, PT_S6(sp)
>> +    REG_S s7, PT_S7(sp)
>> +    REG_S s8, PT_S8(sp)
>> +    REG_S s9, PT_S9(sp)
>> +    REG_S s10, PT_S10(sp)
>> +    REG_S s11, PT_S11(sp)
>> +    REG_S tp, PT_TP(sp)
>> +    REG_S t0, PT_T0(sp)
>> +    REG_S t1, PT_T1(sp)
>> +    REG_S t2, PT_T2(sp)
>> +    REG_S t3, PT_T3(sp)
>> +    REG_S t4, PT_T4(sp)
>> +    REG_S t5, PT_T5(sp)
>> +    REG_S t6, PT_T6(sp)
>> +    REG_S gp, PT_GP(sp)
>> +    REG_S a0, PT_A0(sp)
>> +    REG_S a1, PT_A1(sp)
>> +    REG_S a2, PT_A2(sp)
>> +    REG_S a3, PT_A3(sp)
>> +    REG_S a4, PT_A4(sp)
>> +    REG_S a5, PT_A5(sp)
>> +
>> +    /* Retrieve entry sp */
>> +    REG_L a4, SSE_REG_EVT_TMP(a7)
>> +    /* Save CSRs */
>> +    csrr a0, CSR_EPC
>> +    csrr a1, CSR_SSTATUS
>> +    csrr a2, CSR_STVAL
>> +    csrr a3, CSR_SCAUSE
>> +
>> +    REG_S a0, PT_EPC(sp)
>> +    REG_S a1, PT_STATUS(sp)
>> +    REG_S a2, PT_BADADDR(sp)
>> +    REG_S a3, PT_CAUSE(sp)
>> +    REG_S a4, PT_SP(sp)
>> +
>> +    /* Disable user memory access and floating/vector computing */
>> +    li t0, SR_SUM | SR_FS_VS
>> +    csrc CSR_STATUS, t0
>> +
>> +    load_global_pointer
>> +    scs_load_sse_stack a7
>> +
>> +#ifdef CONFIG_SMP
>> +    REG_L t4, SSE_REG_HART_ID(a7)
>> +    lw t3, SSE_REG_CPU_ID(a7)
>> +
>> +    bne t4, a6, .Lfind_hart_id_slowpath
>> +
>> +.Lcpu_id_found:
>> +#else
>> +    mv t3, zero
>> +#endif
>> +
>> +    asm_per_cpu_with_cpu t2 __sbi_sse_entry_task t1 t3
>> +    REG_L tp, 0(t2)
>> +    bnez tp, .Lcurrent_task_found
>> +
>> +    /* __switch_to() marks its transition window with a NULL entry task. */
>> +    REG_L tp, PT_TP(sp)
>> +
>> +.Lcurrent_task_found:
>> +    /* Nested exceptions temporarily replace these with the SSE stack. */
>> +    REG_L s6, TASK_TI_KERNEL_SP(tp)
>> +    REG_L s7, TASK_TI_USER_SP(tp)
>
> Only KERNEL_SP and USER_SP are preserved. handle_exception's 
> new_valid_map_cpus_check macro
> parks a0/a1/a2 in TASK_TI_A0/A1/A2(tp) and reloads them a few instructions 
> later, for any
> page fault on a kernel address on 64-bit MMU. If an SSE arrives inside that 
> window and the
> handler takes a vmalloc/kfence fault of its own, the nested handle_exception 
> runs the same
> macro on the same tp and overwrites those three slots. When the SSE 
> completes, the interrupted
> macro reloads a0/a1/a2 with the handler's values and they end up in the saved 
> context,
> i.e. a corrupted syscall argument / user a0-a2. The window is small, but 
> "nested exceptions
> are transparent" is then false. Please save and restore those three slots the 
> same way as
> s6/s7.

Good catch! I independently noticed this omission as well and have added
save/restore of TASK_TI_A0/A1/A2 in my local v11 tree, using s8–s10,
which are already saved in the SSE register frame.
>
> I am wondering if this is the reason for the "repeated nested faults corrupt 
> the interrupted context"
> that your 6/9 patch refers to;

It could explain context corruption in that window, but I cannot yet
confirm that it caused the earlier crashes. I'm still testing this and
will avoid attributing those failures to this omission without further
evidence. The fix belongs in the SSE entry code, as you pointed out.

> if so it should be fixed here, not worked around in perf. Check if its
> indeed true.
>

Sure, as mentioned in the resend cover letter, the perf GUP approach was
an interim workaround. v11 will replace it with an x86 way:
pagefault-disabled inatomic copy using exception-table fixup.

>> +
>> +    mv a1, sp /* pt_regs on stack */
>> +
>> +    /*
>> +     * Run the SSE handler with the normal exception vector, but restore the
>> +     * interrupted stvec before completing the event. SSE can arrive while
>> +     * the kernel is using a temporary trap vector in a sensitive entry 
>> path.
>> +     */
>> +    csrr s3, CSR_STVEC
>> +    la t0, handle_exception
>> +    csrw CSR_STVEC, t0
>> +
>> +    /*
>> +     * Preserve the full HS-mode virtualization state across the handler.
>> +     * hstatus is live supervisor state rather than an SSE interrupted
>> +     * attribute, and OpenSBI consumes hstatus.SPV during event completion.
>> +     * Saving the whole CSR keeps the handler episode transparent to KVM and
>> +     * avoids having to infer which hstatus bits may matter to a guest 
>> resume.
>> +     */
>> +    li s5, 0
>> +    ALTERNATIVE("nop", "csrr s5, hstatus", 0, RISCV_ISA_EXT_H, 1)
>> +
>> +    /*
>> +     * Save sscratch for restoration since we might have interrupted the
>> +     * kernel in early exception path and thus, we don't know the content of
>> +     * sscratch.
>> +     */
>> +    csrrw s4, CSR_SSCRATCH, x0
>> +
>> +    mv a0, a7
>> +    mv a2, s5
>> +
>> +    call do_sse
>> +
>> +    /* Leave no reference to the event stack in the interrupted task. */
>> +    REG_S s7, TASK_TI_USER_SP(tp)
>> +    REG_S s6, TASK_TI_KERNEL_SP(tp)
>> +
>> +    csrw CSR_SSCRATCH, s4
>> +    ALTERNATIVE("nop", "csrw hstatus, s5", 0, RISCV_ISA_EXT_H, 1)
>> +    csrw CSR_STVEC, s3
>> +
>> +    REG_L a0, PT_STATUS(sp)
>> +    REG_L a1, PT_EPC(sp)
>> +    REG_L a2, PT_BADADDR(sp)
>> +    REG_L a3, PT_CAUSE(sp)
>> +    csrw CSR_SSTATUS, a0
>> +    csrw CSR_EPC, a1
>> +    csrw CSR_STVAL, a2
>> +    csrw CSR_SCAUSE, a3
>> +
>> +    REG_L ra, PT_RA(sp)
>> +    REG_L s0, PT_S0(sp)
>> +    REG_L s1, PT_S1(sp)
>> +    REG_L s2, PT_S2(sp)
>> +    REG_L s3, PT_S3(sp)
>> +    REG_L s4, PT_S4(sp)
>> +    REG_L s5, PT_S5(sp)
>> +    REG_L s6, PT_S6(sp)
>> +    REG_L s7, PT_S7(sp)
>> +    REG_L s8, PT_S8(sp)
>> +    REG_L s9, PT_S9(sp)
>> +    REG_L s10, PT_S10(sp)
>> +    REG_L s11, PT_S11(sp)
>> +    REG_L tp, PT_TP(sp)
>> +    REG_L t0, PT_T0(sp)
>> +    REG_L t1, PT_T1(sp)
>> +    REG_L t2, PT_T2(sp)
>> +    REG_L t3, PT_T3(sp)
>> +    REG_L t4, PT_T4(sp)
>> +    REG_L t5, PT_T5(sp)
>> +    REG_L t6, PT_T6(sp)
>> +    REG_L gp, PT_GP(sp)
>> +    REG_L a0, PT_A0(sp)
>> +    REG_L a1, PT_A1(sp)
>> +    REG_L a2, PT_A2(sp)
>> +    REG_L a3, PT_A3(sp)
>> +    REG_L a4, PT_A4(sp)
>> +    REG_L a5, PT_A5(sp)
>> +
>> +    REG_L sp, PT_SP(sp)
>> +
>> +    li a7, SBI_EXT_SSE
>> +    li a6, SBI_SSE_EVENT_COMPLETE
>> +    ecall
>> +
>> +    /*
>> +     * COMPLETE must resume the interrupted context and never return. Trap
>> +     * through the normal vector instead of falling into adjacent assembly.
>> +     */
>> +    la t0, handle_exception
>> +    csrw CSR_STVEC, t0
>> +    ebreak
>> +    /* The fatal trap must not return; execution should never reach here. */
>> +
>> +#ifdef CONFIG_SMP
>> +.Lfind_hart_id_slowpath:
>> +
>> +    /* Restore current task struct from __sbi_sse_entry_task */
>> +    li t1, ASM_NR_CPUS
>> +    /* Slowpath to find the CPU id associated to the hart id */
>> +    la t0, __cpuid_to_hartid_map
>> +    li t3, 0
>> +
>> +.Lhart_id_loop:
>> +    REG_L t2, 0(t0)
>> +    beq t2, a6, .Lcpu_id_found
>> +
>> +    /* Increment pointer and CPU number */
>> +    addi t3, t3, 1
>> +    addi t0, t0, RISCV_SZPTR
>> +    bltu t3, t1, .Lhart_id_loop
>> +
>> +    /*
>> +     * This should never happen since we expect the hart_id to match one
>> +     * of our CPU, but better be safe than sorry
>> +     */
>> +    la tp, init_task
>> +    la a0, sse_hart_id_panic_string
>> +    la t0, panic
>> +    jalr t0
>> +#endif
>> +
>> +SYM_CODE_END(handle_sse)
>> +ASM_NOKPROBE(handle_sse)
>> +
>> +SYM_DATA_START_LOCAL(sse_hart_id_panic_string)
>> +    .ascii "Unable to match hart_id with cpu\0"
>> +SYM_DATA_END(sse_hart_id_panic_string)
>> --
>> 2.50.1 (Apple Git-155)
>>

Thanks again for your detailed review!

Regards,
Zhanpeng

Reply via email to