@@ -3,6 +3,8 @@
#define _ASM_RISCV_PROTOTYPES_H
#include <linux/ftrace.h>
+#include <linux/riscv_sse.h>
+#include <asm/sse.h>
#include <asm-generic/asm-prototypes.h>
long long __lshrti3(long long a, int b);
@@ -29,5 +31,8 @@ asmlinkage unsigned long get_overflow_stack(void);
asmlinkage void handle_bad_stack(struct pt_regs *regs);
asmlinkage void do_page_fault(struct pt_regs *regs);
asmlinkage void do_irq(struct pt_regs *regs);
+asmlinkage unsigned long do_sse(unsigned long evt,
+ struct sse_interrupted_state *i_state,
+ sse_event_handler *handler, void *arg);
#endif /* _ASM_RISCV_PROTOTYPES_H */
new file mode 100644
@@ -0,0 +1,94 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2023 Rivos Inc.
+ */
+#ifndef __ASM_SSE_H
+#define __ASM_SSE_H
+
+#include <linux/riscv_sse.h>
+
+
+struct sse_interrupted_state {
+ unsigned long pc;
+ unsigned long ra;
+ unsigned long sp;
+ unsigned long gp;
+ unsigned long tp;
+ unsigned long t0;
+ unsigned long t1;
+ unsigned long t2;
+ unsigned long s0;
+ unsigned long s1;
+ unsigned long a0;
+ unsigned long a1;
+ unsigned long a2;
+ unsigned long a3;
+ unsigned long a4;
+ unsigned long a5;
+ unsigned long a6;
+ unsigned long a7;
+ unsigned long s2;
+ unsigned long s3;
+ unsigned long s4;
+ unsigned long s5;
+ unsigned long s6;
+ unsigned long s7;
+ unsigned long s8;
+ unsigned long s9;
+ unsigned long s10;
+ unsigned long s11;
+ unsigned long t3;
+ unsigned long t4;
+ unsigned long t5;
+ unsigned long t6;
+ unsigned long exec_mode;
+};
+
+struct sse_entry_state {
+ unsigned long pc;
+ unsigned long ra;
+ unsigned long sp;
+ unsigned long gp;
+ unsigned long tp;
+ unsigned long t0;
+ unsigned long t1;
+ unsigned long t2;
+ unsigned long s0;
+ unsigned long s1;
+ unsigned long a0;
+ unsigned long a1;
+ unsigned long a2;
+ unsigned long a3;
+ unsigned long a4;
+ unsigned long a5;
+ unsigned long a6;
+ unsigned long a7;
+ unsigned long s2;
+ unsigned long s3;
+ unsigned long s4;
+ unsigned long s5;
+ unsigned long s6;
+ unsigned long s7;
+ unsigned long s8;
+ unsigned long s9;
+ unsigned long s10;
+ unsigned long s11;
+ unsigned long t3;
+ unsigned long t4;
+ unsigned long t5;
+ unsigned long t6;
+};
+
+struct sse_handler_context {
+ struct sse_entry_state e_state;
+ struct sse_interrupted_state i_state;
+};
+
+unsigned long *sse_stack_alloc(unsigned int cpu, unsigned int size);
+void sse_stack_free(unsigned long *stack);
+
+struct sse_handler_context;
+void sse_handler_context_init(struct sse_handler_context *ctx, void *stack,
+ u32 evt, sse_event_handler *handler, void *arg);
+
+#endif
@@ -79,6 +79,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
+obj-$(CONFIG_RISCV_SSE) += sse.o
ifeq ($(CONFIG_RISCV_SBI), y)
obj-$(CONFIG_SMP) += sbi-ipi.o
obj-$(CONFIG_SMP) += cpu_ops_sbi.o
@@ -14,7 +14,10 @@
#include <asm/thread_info.h>
#include <asm/ptrace.h>
#include <asm/cpu_ops_sbi.h>
+#include <asm/sbi.h>
+#include <asm/sse.h>
#include <asm/suspend.h>
+#include <asm/stacktrace.h>
void asm_offsets(void);
@@ -479,4 +482,18 @@ void asm_offsets(void)
OFFSET(KERNEL_MAP_VIRT_ADDR, kernel_mapping, virt_addr);
OFFSET(SBI_HART_BOOT_TASK_PTR_OFFSET, sbi_hart_boot_data, task_ptr);
OFFSET(SBI_HART_BOOT_STACK_PTR_OFFSET, sbi_hart_boot_data, stack_ptr);
+
+ OFFSET(SSE_INTERRUPTED_EXEC_MODE, sse_interrupted_state, exec_mode);
+ OFFSET(SSE_INTERRUPTED_S0, sse_interrupted_state, s0);
+ OFFSET(SSE_INTERRUPTED_TP, sse_interrupted_state, tp);
+ OFFSET(SSE_INTERRUPTED_PC, sse_interrupted_state, pc);
+
+ DEFINE(STACKFRAME_SIZE_ON_STACK, ALIGN(sizeof(struct stackframe),
+ STACK_ALIGN));
+ OFFSET(STACKFRAME_FP, stackframe, fp);
+ OFFSET(STACKFRAME_RA, stackframe, ra);
+ DEFINE(SBI_EXT_SSE, SBI_EXT_SSE);
+ DEFINE(SBI_SSE_EVENT_COMPLETE, SBI_SSE_EVENT_COMPLETE);
+
+ DEFINE(ASM_PAGE_OFFSET, CONFIG_PAGE_OFFSET);
}
@@ -14,17 +14,49 @@
#include <asm/asm-offsets.h>
#include <asm/errata_list.h>
+#ifdef CONFIG_RISCV_SSE
+#define IN_TP 0
+#define IN_SSCRATCH 1
+#define PRV_U_TASK_LOC(loc) (loc << 0)
+#define PRV_S_TASK_LOC(loc) (loc << 1)
+#define TASK_LOC(s_loc, u_loc) (PRV_U_TASK_LOC(u_loc) | PRV_S_TASK_LOC(s_loc))
+#define TASK_LOC_ENTRY_SIZE (RISCV_SZPTR + 1)
+
+/* __SSE_TASK_LOC - Annotate e'xception code with the location of current task
+ * struct when coming from S/U mode
+ *
+ * When entering handle_exception(), the current task struct is located either
+ * in tp or sscratch depending on interrupted mode. Since SSE handlers can be
+ * triggered at any time during the execution of the kernel, we need to be able
+ * to retrieve the current task struct even if in handle_exception. This macro
+ * create an entry into the __task_loc section that holds the location of
+ * the current task struct for the subsequent assembly located after that up to
+ * _ret_from_exception_end or the next __TASK_LOC.
+ */
+#define __SSE_TASK_LOC(s_loc, u_loc) \
+ .pushsection __task_loc,"a"; \
+ RISCV_PTR 99f; \
+ .byte TASK_LOC(s_loc, u_loc); \
+ .popsection; \
+ 99:
+#else
+#define __SSE_TASK_LOC(s_loc, u_loc)
+#endif
+
SYM_CODE_START(handle_exception)
/*
* If coming from userspace, preserve the user thread pointer and load
* the kernel thread pointer. If we came from the kernel, the scratch
* register will contain 0, and we should continue on the current TP.
*/
+__SSE_TASK_LOC(IN_TP, IN_SSCRATCH)
csrrw tp, CSR_SCRATCH, tp
+__SSE_TASK_LOC(IN_SSCRATCH, IN_TP)
bnez tp, _save_context
_restore_kernel_tpsp:
csrr tp, CSR_SCRATCH
+__SSE_TASK_LOC(IN_TP, IN_TP)
REG_S sp, TASK_TI_KERNEL_SP(tp)
#ifdef CONFIG_VMAP_STACK
@@ -151,6 +183,7 @@ SYM_CODE_START_NOALIGN(ret_from_exception)
REG_SC x0, a2, PT_EPC(sp)
csrw CSR_STATUS, a0
+__SSE_TASK_LOC(IN_TP, IN_SSCRATCH)
csrw CSR_EPC, a2
REG_L x1, PT_RA(sp)
@@ -166,6 +199,7 @@ SYM_CODE_START_NOALIGN(ret_from_exception)
#else
sret
#endif
+_ret_from_exception_end:
SYM_CODE_END(ret_from_exception)
#ifdef CONFIG_VMAP_STACK
@@ -268,6 +302,125 @@ SYM_CODE_START(ret_from_fork)
tail syscall_exit_to_user_mode
SYM_CODE_END(ret_from_fork)
+#ifdef CONFIG_RISCV_SSE
+SYM_CODE_START(handle_sse)
+ move s3, a0
+ /*
+ * Depending on where the sse event interrupted the kernel execution,
+ * sscratch content might be 0 or not even if kernel was interrupted.
+ * This might happen if the sse event was triggered while in
+ * handle_exception() right after entry. In that case, sscratch might
+ * contain 0 if coming from kernel. In order to handle that easily,
+ * simply save sscratch content and restore it when returning from sse
+ * event.
+ */
+ csrr s4, CSR_SSCRATCH
+ /*
+ * Save CAUSE and TVAL in case of nested exceptions, EPC/STATUS are
+ * already saved/restored by handle_exception() for nested exceptions.
+ */
+ csrr s5, CSR_SCAUSE
+ csrr s6, CSR_STVAL
+ REG_L t0, SSE_INTERRUPTED_EXEC_MODE(a1)
+ andi t1, t0, 0x1
+
+ /* Userspace was interrupted, simply restore TP from scratch */
+ bnez t1, _sse_from_kernel
+ move tp, s4
+ j _call_do_sse
+
+_sse_from_kernel:
+ REG_L t0, SSE_INTERRUPTED_PC(a1)
+#ifdef CONFIG_FRAME_POINTER
+ /*
+ * Else, kernel was interrupted and we will create a correct stack frame
+ * from interrupted context.
+ */
+ addi sp, sp, -(STACKFRAME_SIZE_ON_STACK)
+ REG_L t2, SSE_INTERRUPTED_S0(a1)
+ REG_S t2, STACKFRAME_FP(sp)
+ REG_S t0, STACKFRAME_RA(sp)
+ addi s0, sp, STACKFRAME_SIZE_ON_STACK
+#endif
+ REG_L tp, SSE_INTERRUPTED_TP(a1)
+
+ /*
+ * If interrupting the kernel during exception handling
+ * (see handle_exception), then, we might have tp either in SSCRATCH or
+ * in tp, this part is non regular and requires some more work to
+ * determine were is located the current task.
+ */
+ la t1, handle_exception
+ la t2, _ret_from_exception_end
+ bltu t0, t1, _call_do_sse
+ bltu t0, t2, _sse_exception_slow_path
+
+_call_do_sse:
+
+ /* TODO: remove this sanity check once sure everything works ! */
+ li t1, ASM_PAGE_OFFSET
+ bgeu tp, t1, _tp_ok
+ la tp, init_task
+ la a0, sse_panic_string
+ la t0, panic
+ jalr t0
+_tp_ok:
+
+ csrw CSR_SCRATCH, x0
+ la ra, ret_from_sse
+ tail do_sse
+
+.global ret_from_sse
+ret_from_sse:
+ /* Restore saved CSRs */
+ csrw CSR_SSCRATCH, s4
+ csrw CSR_SCAUSE, s5
+ csrw CSR_STVAL, s6
+ li a2, 0
+ move a1, a0
+ move a0, s3
+ li a7, SBI_EXT_SSE
+ li a6, SBI_SSE_EVENT_COMPLETE
+ ecall
+
+/*
+ * t0 contains interrupted pc
+ * t1 contains handle_exception address
+ * When called, t0 must be in [handle_exception, _ret_from_exception_end[
+ */
+_sse_exception_slow_path:
+ la t3, __stop___task_loc
+ add t3, t3, -TASK_LOC_ENTRY_SIZE
+ la t4, __start___task_loc
+ /*
+ * Reverse iterate the task location section to find where is located
+ * the task struct
+ */
+1:
+ REG_L t2, 0(t3)
+ bgeu t0, t2, 2f
+ add t3, t3, -TASK_LOC_ENTRY_SIZE
+ bgeu t3, t4, 1b
+
+2:
+ lbu t2, RISCV_SZPTR(t3)
+
+ /* Get the value of SR_SPP */
+ csrr t1, CSR_SSTATUS
+ andi t1, t1, SR_SPP
+ snez t1, t1
+
+ srl t2, t2, t1
+ andi t2, t2, 1
+ beqz t2, _call_do_sse
+
+_restore_tp_from_sscratch:
+ csrr tp, CSR_SCRATCH
+ j _call_do_sse
+
+SYM_CODE_END(handle_sse)
+#endif
+
/*
* Integer register context switch
* The callee-saved registers must be saved and restored.
@@ -351,3 +504,6 @@ SYM_CODE_START(__user_rt_sigreturn)
ecall
SYM_CODE_END(__user_rt_sigreturn)
#endif
+
+sse_panic_string:
+ .ascii "SSE TP is invalid, last state %px"
new file mode 100644
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2023 Rivos Inc.
+ */
+#include <linux/nmi.h>
+#include <linux/bitfield.h>
+#include <linux/riscv_sse.h>
+
+#include <asm/irq_stack.h>
+#include <asm/sbi.h>
+#include <asm/sse.h>
+
+#define SSE_PRIVILEGE_MODE_BIT BIT(0)
+#define SSE_SPIE_BIT BIT(2)
+
+#define sse_privilege_mode(exec_mode) FIELD_GET(SSE_PRIVILEGE_MODE_BIT, exec_mode)
+#define sse_spie(exec_mode) FIELD_GET(SSE_SPIE_BIT, exec_mode)
+
+register unsigned long gp_in_global __asm__("gp");
+
+extern asmlinkage void handle_exception(void);
+extern asmlinkage void handle_sse(unsigned long evt,
+ struct sse_interrupted_state *i_state,
+ sse_event_handler *handler, void *arg);
+
+static void sse_get_pt_regs(struct sse_interrupted_state *i_state,
+ struct pt_regs *regs)
+{
+ unsigned long sstatus = csr_read(CSR_SSTATUS);
+
+ memcpy(regs, i_state, offsetof(struct sse_interrupted_state, exec_mode));
+ regs->status = sstatus & ~SR_SPP;
+ regs->status |= FIELD_PREP(SR_SPP, sse_privilege_mode(i_state->exec_mode));
+}
+
+unsigned long do_sse(unsigned long evt, struct sse_interrupted_state *i_state,
+ sse_event_handler *handler, void *arg)
+{
+ int ret;
+ struct pt_regs regs;
+
+ nmi_enter();
+
+ sse_get_pt_regs(i_state, ®s);
+ ret = handler(evt, arg, ®s);
+ if (ret)
+ pr_warn("event %lx handler failed with error %d\n", evt, ret);
+
+ /* The SSE delivery path does not uses the "standard" exception path and
+ * thus does not process any pending signal/softirqs. Some drivers might
+ * enqueue pending work that needs to be handled as soon as possible.
+ * For that purpose, set the software interrupt pending bit
+ */
+ csr_set(CSR_IP, IE_SIE);
+
+ nmi_exit();
+
+ return ret ? SBI_SSE_HANDLER_FAILED : SBI_SSE_HANDLER_SUCCESS;
+}
+
+#ifdef CONFIG_VMAP_STACK
+unsigned long *sse_stack_alloc(unsigned int cpu, unsigned int size)
+{
+ return arch_alloc_vmap_stack(size, cpu_to_node(cpu));
+}
+
+void sse_stack_free(unsigned long *stack)
+{
+ vfree(stack);
+}
+#else /* CONFIG_VMAP_STACK */
+
+unsigned long *sse_stack_alloc(unsigned int cpu, unsigned int size)
+{
+ return kmalloc(size, GFP_KERNEL);
+}
+
+void sse_stack_free(unsigned long *stack)
+{
+ kfree(stack);
+}
+
+#endif /* CONFIG_VMAP_STACK */
+
+void sse_handler_context_init(struct sse_handler_context *ctx, void *stack,
+ u32 evt, sse_event_handler *handler, void *arg)
+{
+ ctx->e_state.pc = (unsigned long)handle_sse;
+ ctx->e_state.gp = gp_in_global;
+ ctx->e_state.sp = (unsigned long)stack + THREAD_SIZE;
+
+ /* This must match handle_sse expected parameter order */
+ ctx->e_state.a0 = evt;
+ ctx->e_state.a1 = (unsigned long)&ctx->i_state;
+ ctx->e_state.a2 = (unsigned long)handler;
+ ctx->e_state.a3 = (unsigned long)arg;
+}
@@ -17,6 +17,7 @@
#ifdef CONFIG_FRAME_POINTER
extern asmlinkage void ret_from_exception(void);
+extern asmlinkage void ret_from_sse(void);
void notrace walk_stackframe(struct task_struct *task, struct pt_regs *regs,
bool (*fn)(void *, unsigned long), void *arg)
@@ -68,6 +69,18 @@ void notrace walk_stackframe(struct task_struct *task, struct pt_regs *regs,
pc = ((struct pt_regs *)sp)->epc;
fp = ((struct pt_regs *)sp)->s0;
+ } else if (pc == (unsigned long)ret_from_sse) {
+ if (unlikely(!fn(arg, pc)))
+ break;
+ /* We don't have pt_regs when handling SSE
+ * events but we build a custom stackframe,
+ * moreover, the stack changes across boundaries
+ * so update it to avoid failing the checks above
+ */
+ frame = (struct stackframe *)fp - 1;
+ fp = frame->fp;
+ pc = frame->ra;
+ sp = fp - sizeof(struct stackframe);
}
}
@@ -120,6 +120,12 @@ SECTIONS
/* Start of data section */
_sdata = .;
RO_DATA(SECTION_ALIGN)
+
+#ifdef CONFIG_RISCV_SSE
+ __task_loc : {
+ BOUNDED_SECTION_BY(__task_loc, ___task_loc)
+ }
+#endif
.srodata : {
*(.srodata*)
}
@@ -226,6 +226,16 @@ config QCOM_SCM_DOWNLOAD_MODE_DEFAULT
Say Y here to enable "download mode" by default.
+config RISCV_SSE
+ bool
+ depends on RISCV_SBI
+ default y
+ help
+ The Supervisor Software Events support allow the SBI to deliver
+ NMI-like notifications to the supervisor mode software. When enable,
+ this option provides support to register callbacks on specific SSE
+ events.
+
config SYSFB
bool
select BOOT_VESA_SUPPORT
@@ -20,6 +20,7 @@ obj-$(CONFIG_RASPBERRYPI_FIRMWARE) += raspberrypi.o
obj-$(CONFIG_FW_CFG_SYSFS) += qemu_fw_cfg.o
obj-$(CONFIG_QCOM_SCM) += qcom-scm.o
qcom-scm-objs += qcom_scm.o qcom_scm-smc.o qcom_scm-legacy.o
+obj-$(CONFIG_RISCV_SSE) += riscv_sse.o
obj-$(CONFIG_SYSFB) += sysfb.o
obj-$(CONFIG_SYSFB_SIMPLEFB) += sysfb_simplefb.o
obj-$(CONFIG_TI_SCI_PROTOCOL) += ti_sci.o
new file mode 100644
@@ -0,0 +1,496 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2023 Rivos Inc.
+ */
+
+#define pr_fmt(fmt) "sse: " fmt
+
+#include <linux/cpu.h>
+#include <linux/cpuhotplug.h>
+#include <linux/hardirq.h>
+#include <linux/list.h>
+#include <linux/percpu-defs.h>
+#include <linux/riscv_sse.h>
+#include <linux/slab.h>
+
+#include <asm/sbi.h>
+#include <asm/sse.h>
+
+struct sse_registered_event {
+ struct sse_handler_context ctx;
+ unsigned long *stack;
+};
+
+struct sse_event {
+ struct list_head list;
+ u32 evt;
+ u32 priority;
+ unsigned int cpu;
+
+ union {
+ struct sse_registered_event *global;
+ struct sse_registered_event __percpu *local;
+ };
+};
+
+static bool sse_available;
+static DEFINE_SPINLOCK(events_list_lock);
+static LIST_HEAD(events);
+static DEFINE_MUTEX(sse_mutex);
+
+static struct sbiret sbi_sse_ecall(int fid, unsigned long arg0,
+ unsigned long arg1, unsigned long arg2)
+{
+ return sbi_ecall(SBI_EXT_SSE, fid, arg0, arg1, arg2, 0, 0, 0);
+}
+
+static bool sse_event_is_global(u32 evt)
+{
+ return !!(evt & SBI_SSE_EVENT_GLOBAL);
+}
+
+static
+struct sse_event *sse_event_get(u32 evt)
+{
+ struct sse_event *sse_evt = NULL, *tmp;
+
+ spin_lock(&events_list_lock);
+ list_for_each_entry(tmp, &events, list) {
+ if (tmp->evt == evt) {
+ sse_evt = tmp;
+ break;
+ }
+ }
+ spin_unlock(&events_list_lock);
+
+ return sse_evt;
+}
+
+static int sse_event_set_target_cpu_nolock(struct sse_event *event, unsigned int cpu)
+{
+ unsigned int hart_id = cpuid_to_hartid_map(cpu);
+ u32 evt = event->evt;
+ struct sbiret sret;
+
+ if (!sse_event_is_global(evt))
+ return -EINVAL;
+
+ do {
+ sret = sbi_sse_ecall(SBI_SSE_EVENT_ATTR_SET, evt,
+ SBI_SSE_EVENT_ATTR_HART_ID, hart_id);
+ if (sret.error && sret.error != SBI_ERR_BUSY) {
+ pr_err("Failed to set event %x hart id, error %ld\n", evt,
+ sret.error);
+ return sbi_err_map_linux_errno(sret.error);
+ }
+ } while (sret.error);
+
+ event->cpu = cpu;
+
+ return 0;
+}
+
+
+int sse_event_set_target_cpu(struct sse_event *event, unsigned int cpu)
+{
+ int ret;
+
+ mutex_lock(&sse_mutex);
+ ret = sse_event_set_target_cpu_nolock(event, cpu);
+ mutex_unlock(&sse_mutex);
+
+ return ret;
+}
+
+static int sse_event_init_registered(unsigned int cpu, u32 evt,
+ struct sse_registered_event *reg_evt,
+ sse_event_handler *handler, void *arg)
+{
+ unsigned long *stack;
+
+ stack = sse_stack_alloc(cpu, THREAD_SIZE);
+ if (!stack)
+ return -ENOMEM;
+
+ reg_evt->stack = stack;
+
+ sse_handler_context_init(®_evt->ctx, stack, evt, handler, arg);
+
+ return 0;
+}
+
+static struct sse_event *sse_event_alloc(u32 evt,
+ u32 priority,
+ sse_event_handler *handler, void *arg)
+{
+ int err;
+ unsigned int cpu;
+ struct sse_event *event;
+ struct sse_registered_event __percpu *reg_evts;
+ struct sse_registered_event *reg_evt;
+
+ event = kzalloc(sizeof(*event), GFP_KERNEL);
+ if (!event)
+ return ERR_PTR(-ENOMEM);
+
+ event->evt = evt;
+ event->priority = priority;
+
+ if (sse_event_is_global(evt)) {
+ reg_evt = kzalloc(sizeof(*reg_evt), GFP_KERNEL);
+ if (!reg_evt) {
+ err = -ENOMEM;
+ goto err_alloc_reg_evt;
+ }
+
+ event->global = reg_evt;
+ err = sse_event_init_registered(smp_processor_id(), evt,
+ reg_evt, handler, arg);
+ if (err)
+ goto err_alloc_reg_evt;
+
+
+ } else {
+ reg_evts = alloc_percpu(struct sse_registered_event);
+ if (!reg_evts) {
+ err = -ENOMEM;
+ goto err_alloc_reg_evt;
+ }
+
+ event->local = reg_evts;
+
+ for_each_possible_cpu(cpu) {
+ reg_evt = per_cpu_ptr(reg_evts, cpu);
+
+ err = sse_event_init_registered(cpu, evt, reg_evt,
+ handler, arg);
+ if (err)
+ goto err_alloc_reg_evt;
+
+ }
+ }
+
+ return event;
+
+err_alloc_reg_evt:
+ kfree(event);
+
+ return ERR_PTR(err);
+}
+
+static int sse_sbi_register_event(struct sse_event *event,
+ struct sse_registered_event *reg_evt)
+{
+ struct sbiret ret;
+ phys_addr_t phys;
+ u32 evt = event->evt;
+
+ ret = sbi_sse_ecall(SBI_SSE_EVENT_ATTR_SET, evt,
+ SBI_SSE_EVENT_ATTR_PRIORITY, event->priority);
+ if (ret.error) {
+ pr_err("Failed to set event %x priority, error %ld\n", evt,
+ ret.error);
+ return sbi_err_map_linux_errno(ret.error);
+ }
+
+ if (sse_event_is_global(event->evt))
+ phys = virt_to_phys(®_evt->ctx);
+ else
+ phys = per_cpu_ptr_to_phys(®_evt->ctx);
+
+ ret = sbi_sse_ecall(SBI_SSE_EVENT_REGISTER, evt, phys, 0);
+ if (ret.error)
+ pr_err("Failed to register event %d, error %ld\n", evt,
+ ret.error);
+
+ return sbi_err_map_linux_errno(ret.error);
+}
+
+static int sse_sbi_event_func(struct sse_event *event, unsigned long func)
+{
+ struct sbiret ret;
+ u32 evt = event->evt;
+
+ ret = sbi_sse_ecall(func, evt, 0, 0);
+ if (ret.error)
+ pr_err("Failed to execute func %lx, event %d, error %ld\n", func,
+ evt, ret.error);
+
+ return sbi_err_map_linux_errno(ret.error);
+}
+
+static int sse_event_register_local(struct sse_event *event)
+{
+ int ret;
+ struct sse_registered_event *reg_evt = per_cpu_ptr(event->local,
+ smp_processor_id());
+
+ ret = sse_sbi_register_event(event, reg_evt);
+ if (ret)
+ pr_err("Failed to register event %x: err %d\n", event->evt,
+ ret);
+
+ return ret;
+}
+
+static int sse_sbi_disable_event(struct sse_event *event)
+{
+ return sse_sbi_event_func(event, SBI_SSE_EVENT_DISABLE);
+}
+
+static int sse_sbi_enable_event(struct sse_event *event)
+{
+ return sse_sbi_event_func(event, SBI_SSE_EVENT_ENABLE);
+}
+
+static int sse_sbi_unregister_event(struct sse_event *event)
+{
+ return sse_sbi_event_func(event, SBI_SSE_EVENT_UNREGISTER);
+}
+
+struct sse_per_cpu_evt {
+ struct sse_event *event;
+ unsigned long func;
+ int error;
+};
+
+static void sse_event_per_cpu_func(void *info)
+{
+ int ret;
+ struct sse_per_cpu_evt *cpu_evt = info;
+
+ if (cpu_evt->func == SBI_SSE_EVENT_REGISTER)
+ ret = sse_event_register_local(cpu_evt->event);
+ else
+ ret = sse_sbi_event_func(cpu_evt->event, cpu_evt->func);
+
+ if (ret)
+ WRITE_ONCE(cpu_evt->error, 1);
+}
+
+static void sse_event_free(struct sse_event *event)
+{
+ unsigned int cpu;
+ struct sse_registered_event *reg_evt;
+
+ if (sse_event_is_global(event->evt)) {
+ sse_stack_free(event->global->stack);
+ } else {
+ for_each_possible_cpu(cpu) {
+ reg_evt = per_cpu_ptr(event->local, cpu);
+ sse_stack_free(reg_evt->stack);
+ }
+ free_percpu(event->local);
+ }
+
+ kfree(event);
+}
+
+int sse_event_enable(struct sse_event *event)
+{
+ int ret = 0;
+ struct sse_per_cpu_evt cpu_evt;
+
+ mutex_lock(&sse_mutex);
+
+ cpus_read_lock();
+ if (sse_event_is_global(event->evt)) {
+ /* Global events can only be unregister from target hart */
+ ret = sse_event_set_target_cpu_nolock(event, smp_processor_id());
+ if (ret)
+ goto out;
+
+ ret = sse_sbi_enable_event(event);
+ if (ret)
+ goto out;
+
+ } else {
+ cpu_evt.event = event;
+ cpu_evt.error = 0;
+ cpu_evt.func = SBI_SSE_EVENT_ENABLE;
+ on_each_cpu(sse_event_per_cpu_func, &cpu_evt, 1);
+ if (READ_ONCE(cpu_evt.error)) {
+ cpu_evt.func = SBI_SSE_EVENT_DISABLE;
+ on_each_cpu(sse_event_per_cpu_func, &cpu_evt, 1);
+ goto out;
+ }
+ }
+ cpus_read_unlock();
+
+out:
+ mutex_unlock(&sse_mutex);
+
+ return ret;
+}
+
+void sse_event_disable(struct sse_event *event)
+{
+ struct sse_per_cpu_evt cpu_evt;
+
+ mutex_lock(&sse_mutex);
+
+ if (sse_event_is_global(event->evt)) {
+ sse_sbi_disable_event(event);
+ } else {
+ cpu_evt.event = event;
+ cpu_evt.func = SBI_SSE_EVENT_DISABLE;
+ on_each_cpu(sse_event_per_cpu_func, &cpu_evt, 1);
+ }
+
+ mutex_unlock(&sse_mutex);
+}
+
+struct sse_event *sse_event_register(u32 evt, u32 priority,
+ sse_event_handler *handler, void *arg)
+{
+ struct sse_per_cpu_evt cpu_evt;
+ struct sse_event *event;
+ int ret = 0;
+
+ mutex_lock(&sse_mutex);
+ if (sse_event_get(evt)) {
+ pr_err("Event %x already registered\n", evt);
+ ret = -EEXIST;
+ goto out_unlock;
+ }
+
+ event = sse_event_alloc(evt, priority, handler, arg);
+ if (IS_ERR(event)) {
+ ret = PTR_ERR(event);
+ goto out_unlock;
+ }
+
+ cpus_read_lock();
+ if (sse_event_is_global(evt)) {
+ /* SSE spec mandates that the CPU registering the global event be the
+ * one set as the target hart, plus we don't know initial value
+ */
+ ret = sse_event_set_target_cpu_nolock(event, smp_processor_id());
+ if (ret)
+ goto err_event_free;
+
+ ret = sse_sbi_register_event(event, event->global);
+ if (ret)
+ goto err_event_free;
+
+ } else {
+ cpu_evt.event = event;
+ cpu_evt.error = 0;
+ cpu_evt.func = SBI_SSE_EVENT_REGISTER;
+ on_each_cpu(sse_event_per_cpu_func, &cpu_evt, 1);
+ if (READ_ONCE(cpu_evt.error)) {
+ cpu_evt.func = SBI_SSE_EVENT_UNREGISTER;
+ on_each_cpu(sse_event_per_cpu_func, &cpu_evt, 1);
+ goto err_event_free;
+ }
+ }
+ cpus_read_unlock();
+
+ spin_lock(&events_list_lock);
+ list_add(&event->list, &events);
+ spin_unlock(&events_list_lock);
+
+ mutex_unlock(&sse_mutex);
+
+ return event;
+
+err_event_free:
+ cpus_read_unlock();
+ sse_event_free(event);
+out_unlock:
+ mutex_unlock(&sse_mutex);
+
+ return ERR_PTR(ret);
+}
+
+void sse_event_unregister(struct sse_event *event)
+{
+ int ret;
+ struct sse_per_cpu_evt cpu_evt;
+
+ mutex_lock(&sse_mutex);
+
+ if (sse_event_is_global(event->evt)) {
+ /* Global events can only be unregister from target hart */
+ ret = sse_event_set_target_cpu_nolock(event, smp_processor_id());
+ WARN_ON(ret);
+ sse_sbi_unregister_event(event);
+ } else {
+ cpu_evt.event = event;
+ cpu_evt.func = SBI_SSE_EVENT_UNREGISTER;
+ on_each_cpu(sse_event_per_cpu_func, &cpu_evt, 1);
+ }
+
+ spin_lock(&events_list_lock);
+ list_del(&event->list);
+ spin_unlock(&events_list_lock);
+
+ sse_event_free(event);
+
+ mutex_unlock(&sse_mutex);
+}
+
+static int sse_cpu_online(unsigned int cpu)
+{
+ struct sse_event *sse_evt;
+
+ spin_lock(&events_list_lock);
+ list_for_each_entry(sse_evt, &events, list) {
+ if (sse_event_is_global(sse_evt->evt))
+ continue;
+
+ sse_event_register_local(sse_evt);
+ }
+
+ spin_unlock(&events_list_lock);
+
+ return 0;
+}
+
+static int sse_cpu_teardown(unsigned int cpu)
+{
+ unsigned int next_cpu;
+ struct sse_event *sse_evt;
+
+ spin_lock(&events_list_lock);
+ list_for_each_entry(sse_evt, &events, list) {
+ if (!sse_event_is_global(sse_evt->evt)) {
+ sse_sbi_unregister_event(sse_evt);
+ continue;
+ }
+
+ if (sse_evt->cpu != smp_processor_id())
+ continue;
+
+ /* Update destination hart */
+ next_cpu = cpumask_any_but(cpu_online_mask, cpu);
+ sse_event_set_target_cpu(sse_evt, next_cpu);
+ }
+ spin_unlock(&events_list_lock);
+
+ return 0;
+}
+
+static int __init sse_init(void)
+{
+ int cpu, ret;
+
+ if (sbi_probe_extension(SBI_EXT_SSE) <= 0) {
+ pr_err("Missing SBI SSE extension\n");
+ return -EOPNOTSUPP;
+ }
+ pr_info("SBI SSE extension detected\n");
+
+ for_each_possible_cpu(cpu)
+ INIT_LIST_HEAD(&events);
+
+ ret = cpuhp_setup_state_nocalls(CPUHP_AP_ONLINE_DYN, "riscv/sse:online",
+ sse_cpu_online, sse_cpu_teardown);
+ if (ret < 0)
+ return ret;
+
+ sse_available = true;
+
+ return 0;
+}
+device_initcall(sse_init);
new file mode 100644
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2023 Rivos Inc.
+ */
+
+#ifndef __LINUX_RISCV_SSE_H
+#define __LINUX_RISCV_SSE_H
+
+#include <linux/types.h>
+#include <linux/linkage.h>
+
+struct sse_interrupted_state;
+struct sse_event;
+struct pt_regs;
+
+typedef int (sse_event_handler)(u32 event_num, void *arg, struct pt_regs *regs);
+
+#ifdef CONFIG_RISCV_SSE
+
+struct sse_event *sse_event_register(u32 event_num, u32 priority,
+ sse_event_handler *handler, void *arg);
+
+void sse_event_unregister(struct sse_event *evt);
+
+int sse_event_set_target_cpu(struct sse_event *sse_evt, unsigned int cpu);
+
+int sse_event_enable(struct sse_event *sse_evt);
+
+void sse_event_disable(struct sse_event *sse_evt);
+
+#else
+static inline int sse_event_register(struct sse_event *evt, u32 priority,
+ sse_event_handler *handler, void *arg)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline void sse_event_unregister(struct sse_event *evt) {}
+
+static inline int sse_event_set_target_cpu(struct sse_event *sse_evt,
+ unsigned int cpu)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline int sse_event_enable(struct sse_event *sse_evt)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline void sse_event_disable(struct sse_event *sse_evt) {}
+
+
+#endif
+
+#endif /* __LINUX_RISCV_SSE_H */