From nobody Tue Oct 7 03:50:34 2025 Received: from mail-pf1-f170.google.com (mail-pf1-f170.google.com [209.85.210.170]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D0BE12517AA for ; Tue, 15 Jul 2025 16:03:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.170 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752595419; cv=none; b=XWYZXHhbtzNiMYMjAmE653AIw1CpaCPD20cxJ9AgN/Ia7HS0ooLzx45TaunLDybCbCBjSSSbB7PyrIb6Jl9RrWjKOA+/SdSyF4gvFLLmqlBBs71Xcr2YJdmQHs3eLXUYHEVjvaFLLbsiEmzdBA2f3tIHYitos6OgShVEot+U5ws= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752595419; c=relaxed/simple; bh=PEmwBq7E7NsptXN2FvvghC0RT7NWo/EZv/l33smMReM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=lWri8a65/ku1s8TLZm/bt6DVsGakEYE7pJwGwEGXPb9Xbvxuh3gJiGoYOwpzOgA7B+EoRBxXWklWVtvNE8GL0ZX2yzlsu7cMtqRQd3xPcRAzlkprynXwdUGMPiId2GcHRFL7ZlPGdRUmYiTtJLcVlmzuZ1//K8Rxdml8FTnAaE8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=rivosinc.com; spf=pass smtp.mailfrom=rivosinc.com; dkim=pass (2048-bit key) header.d=rivosinc-com.20230601.gappssmtp.com header.i=@rivosinc-com.20230601.gappssmtp.com header.b=X7v+fInb; arc=none smtp.client-ip=209.85.210.170 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=rivosinc.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=rivosinc.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=rivosinc-com.20230601.gappssmtp.com header.i=@rivosinc-com.20230601.gappssmtp.com header.b="X7v+fInb" Received: by mail-pf1-f170.google.com with SMTP id d2e1a72fcca58-7494999de5cso3570895b3a.3 for ; Tue, 15 Jul 2025 09:03:36 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=rivosinc-com.20230601.gappssmtp.com; s=20230601; t=1752595416; x=1753200216; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=tkuXx1/t1MJ6Gba/i6Lc81ZjyWxKPmn26QeW5YWnstE=; b=X7v+fInbDjlJGwg8u6LaMwvcT9II6+QyHKWZeIl/I8YSgih/u63eLQDgA8ABEFXuEO fB4r0c3MbT81myrQW0zmdn2DWDpZunxck67NRmdegOXjAY/P53LxhaLdlwCMWTwr3y7f 9jArTTc/y4dQZCFyzqTzkRTOWZubgXEJnHof3UCRJgiewVkUr3vhxNQBmgCo7QepweQE 4RAK2jyRV74GxKN/bBLc4I3M3zyP7Py+g1OO/vFEJ3lFnF0ARR3vijmvJFFGrwVPRt7w taCcYb6pjB+XC1HSNNgOHuc2/PtJWffxVcr8fKYrHkFVh9DxeoS3oS69IyZa7rkeNAzP MBWA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1752595416; x=1753200216; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=tkuXx1/t1MJ6Gba/i6Lc81ZjyWxKPmn26QeW5YWnstE=; b=lv+Su7RIDTT/uFoeM77J7ToyiFlZIyH04nBcrVIs0UbMKqFJoF3P05ixE5fMbCE1vt nb72fZg71jQDmtvwEQPsmnaoHdU6cQ5hUEYQO5eOZZbkG6bf/LLycVD3D6/tLg2cxEvz H7TkoI6t/b1Yp9KP7+bbvct4vG9d0M72XkzsXjGOzxwrv/Q6cSq3VwH2qiQHJZ0gzcPx smnzfgfrmk/93UmPHq/fEor1QKLBIfScoohsUYcHiTAKANTfF+QXC4f1A9uCTJcbCWsU LGhSL+7OIev3O/wx8VR7ytoThP71SdGhmx7r9X0T8Kaq6TtCCs9f2TVb3Qdn1utrkTD+ T6xw== X-Forwarded-Encrypted: i=1; AJvYcCUuMH4QAOjaFRMKOVUGfFNvsa7nXJBOkrc6SXAPqcCdwgZ2RmIqokXJEbOcR33ckU9IEDzV7wHLK5FqeU8=@vger.kernel.org X-Gm-Message-State: AOJu0YyEwP9fcSR7JjQVGbEV5AWvz4wuw7eyXXpPNyetDWLUCxGVxgVm b+9CZZfO7nTmgqopxyoqncsV5DKuzoYJpRBthzw0e2GLzLNPQSJ/LIgSVfLOP1PXfr+kb4ni+A7 Iuu2X X-Gm-Gg: ASbGncuxrZ0/k/rjwDqiTzniZhwrNJTChDZvhOFJSVNrJiWzhHCfGui/+p+LU/vGyOB 9SAtGv/AZCgqDFxGBvRAm2+WKWJKA5vppG1MmD7/xP7/fw0DnEYdlP6pM3Xutr7k4Kg3qvd/Nj1 lEcCVIpLbgozytINNWeUd4N4ZUB1KsQ4Zhz07079UAcCMAxH+LfzchwRPYN1SH5axsEcDhdvJZA pPtqB+L7RxtT+sogpFRvEbwc3M9pks7L4E7CkaFcqKt4mMfgr8f20D/3r/nhiBblolzIBBo9w/y PT/A9rXEqxC6POP/QdaXZCjiwzg6GnV4qxemqAyZD/9TUipcjm2yunmZve4OPiIPOqc37gU/pNx cppK1kS+S0/1LUw== X-Google-Smtp-Source: AGHT+IFRXjNct+XkxzDlh5f0L82u5E5C8XNctwm2aYTsnLFVBO/j/JCW2id6mZlqhzblE1VuiUIVkg== X-Received: by 2002:a05:6a00:22d0:b0:73e:2d7a:8fc0 with SMTP id d2e1a72fcca58-755b24dad18mr5018729b3a.1.1752595415826; Tue, 15 Jul 2025 09:03:35 -0700 (PDT) Received: from cleger.eu.int ([2001:41d0:420:f300::]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-74eb9f856fdsm12546532b3a.144.2025.07.15.09.03.28 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 15 Jul 2025 09:03:35 -0700 (PDT) From: =?UTF-8?q?Cl=C3=A9ment=20L=C3=A9ger?= To: Paul Walmsley , Palmer Dabbelt , linux-riscv@lists.infradead.org, linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org Cc: =?UTF-8?q?Cl=C3=A9ment=20L=C3=A9ger?= , Himanshu Chauhan , Anup Patel , Xu Lu , Atish Patra , =?UTF-8?q?Bj=C3=B6rn=20T=C3=B6pel?= , Conor Dooley Subject: [PATCH v5 3/5] drivers: firmware: add riscv SSE support Date: Tue, 15 Jul 2025 16:02:31 +0000 Message-ID: <20250715160234.454848-4-cleger@rivosinc.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20250715160234.454848-1-cleger@rivosinc.com> References: <20250715160234.454848-1-cleger@rivosinc.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Add driver level interface to use RISC-V SSE arch support. This interface allows registering SSE handlers, and receive them. This will be used by PMU and GHES driver. Signed-off-by: Himanshu Chauhan Co-developed-by: Himanshu Chauhan Signed-off-by: Cl=C3=A9ment L=C3=A9ger Acked-by: Conor Dooley --- MAINTAINERS | 15 + drivers/firmware/Kconfig | 1 + drivers/firmware/Makefile | 1 + drivers/firmware/riscv/Kconfig | 15 + drivers/firmware/riscv/Makefile | 3 + drivers/firmware/riscv/riscv_sse.c | 672 +++++++++++++++++++++++++++++ include/linux/riscv_sse.h | 56 +++ 7 files changed, 763 insertions(+) create mode 100644 drivers/firmware/riscv/Kconfig create mode 100644 drivers/firmware/riscv/Makefile create mode 100644 drivers/firmware/riscv/riscv_sse.c create mode 100644 include/linux/riscv_sse.h diff --git a/MAINTAINERS b/MAINTAINERS index 60bba48f5479..4c3420f3f7c6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -21342,6 +21342,13 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/g= it/iommu/linux.git F: Documentation/devicetree/bindings/iommu/riscv,iommu.yaml F: drivers/iommu/riscv/ =20 +RISC-V FIRMWARE DRIVERS +M: Conor Dooley +L: linux-riscv@lists.infradead.org +S: Maintained +T: git git://git.kernel.org/pub/scm/linux/kernel/git/conor/linux.git +F: drivers/firmware/riscv/* + RISC-V MICROCHIP FPGA SUPPORT M: Conor Dooley M: Daire McNamara @@ -21406,6 +21413,14 @@ F: arch/riscv/boot/dts/spacemit/ N: spacemit K: spacemit =20 +RISC-V SSE DRIVER +M: Cl=C3=A9ment L=C3=A9ger +R: Himanshu Chauhan +L: linux-riscv@lists.infradead.org +S: Maintained +F: drivers/firmware/riscv/riscv_sse.c +F: include/linux/riscv_sse.h + RISC-V THEAD SoC SUPPORT M: Drew Fustini M: Guo Ren diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index bbd2155d8483..1894df87b08e 100644 --- a/drivers/firmware/Kconfig +++ b/drivers/firmware/Kconfig @@ -294,6 +294,7 @@ source "drivers/firmware/meson/Kconfig" source "drivers/firmware/microchip/Kconfig" source "drivers/firmware/psci/Kconfig" source "drivers/firmware/qcom/Kconfig" +source "drivers/firmware/riscv/Kconfig" source "drivers/firmware/samsung/Kconfig" source "drivers/firmware/smccc/Kconfig" source "drivers/firmware/tegra/Kconfig" diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile index 4ddec2820c96..6cdd84570ea7 100644 --- a/drivers/firmware/Makefile +++ b/drivers/firmware/Makefile @@ -34,6 +34,7 @@ obj-y +=3D efi/ obj-y +=3D imx/ obj-y +=3D psci/ obj-y +=3D qcom/ +obj-y +=3D riscv/ obj-y +=3D samsung/ obj-y +=3D smccc/ obj-y +=3D tegra/ diff --git a/drivers/firmware/riscv/Kconfig b/drivers/firmware/riscv/Kconfig new file mode 100644 index 000000000000..8056ed3262d9 --- /dev/null +++ b/drivers/firmware/riscv/Kconfig @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-only +menu "Risc-V Specific firmware drivers" +depends on RISCV + +config RISCV_SSE + bool "Enable SBI Supervisor Software Events support" + 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. + +endmenu diff --git a/drivers/firmware/riscv/Makefile b/drivers/firmware/riscv/Makef= ile new file mode 100644 index 000000000000..4ccfcbbc28ea --- /dev/null +++ b/drivers/firmware/riscv/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_RISCV_SSE) +=3D riscv_sse.o diff --git a/drivers/firmware/riscv/riscv_sse.c b/drivers/firmware/riscv/ri= scv_sse.c new file mode 100644 index 000000000000..3311b0b6c3b6 --- /dev/null +++ b/drivers/firmware/riscv/riscv_sse.c @@ -0,0 +1,672 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2024 Rivos Inc. + */ + +#define pr_fmt(fmt) "sse: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +struct sse_event { + struct list_head list; + u32 evt_id; + u32 priority; + sse_event_handler *handler; + void *handler_arg; + /* Only valid for global events */ + unsigned int cpu; + + union { + struct sse_registered_event *global; + struct sse_registered_event __percpu *local; + }; +}; + +static int sse_hp_state; +static bool sse_available __ro_after_init; +static DEFINE_SPINLOCK(events_list_lock); +static LIST_HEAD(events); +static DEFINE_MUTEX(sse_mutex); + +struct sse_registered_event { + struct sse_event_arch_data arch; + struct sse_event *event; + unsigned long attr; + bool is_enabled; +}; + +void sse_handle_event(struct sse_event_arch_data *arch_event, + struct pt_regs *regs) +{ + int ret; + struct sse_registered_event *reg_evt =3D + container_of(arch_event, struct sse_registered_event, arch); + struct sse_event *evt =3D reg_evt->event; + + ret =3D evt->handler(evt->evt_id, evt->handler_arg, regs); + if (ret) + pr_warn("event %x handler failed with error %d\n", evt->evt_id, ret); +} + +static struct sse_event *sse_event_get(u32 evt) +{ + struct sse_event *event =3D NULL; + + scoped_guard(spinlock, &events_list_lock) { + list_for_each_entry(event, &events, list) { + if (event->evt_id =3D=3D evt) + return event; + } + } + + return NULL; +} + +static phys_addr_t sse_event_get_attr_phys(struct sse_registered_event *re= g_evt) +{ + phys_addr_t phys; + void *addr =3D ®_evt->attr; + + if (sse_event_is_global(reg_evt->event->evt_id)) + phys =3D virt_to_phys(addr); + else + phys =3D per_cpu_ptr_to_phys(addr); + + return phys; +} + +static struct sse_registered_event *sse_get_reg_evt(struct sse_event *even= t) +{ + if (sse_event_is_global(event->evt_id)) + return event->global; + else + return per_cpu_ptr(event->local, smp_processor_id()); +} + +static int sse_sbi_event_func(struct sse_event *event, unsigned long func) +{ + struct sbiret ret; + u32 evt =3D event->evt_id; + struct sse_registered_event *reg_evt =3D sse_get_reg_evt(event); + + ret =3D sbi_ecall(SBI_EXT_SSE, func, evt, 0, 0, 0, 0, 0); + if (ret.error) { + pr_warn("Failed to execute func %lx, event %x, error %ld\n", func, evt, = ret.error); + return sbi_err_map_linux_errno(ret.error); + } + + if (func =3D=3D SBI_SSE_EVENT_DISABLE) + reg_evt->is_enabled =3D false; + else if (func =3D=3D SBI_SSE_EVENT_ENABLE) + reg_evt->is_enabled =3D true; + + return 0; +} + +int sse_event_disable_local(struct sse_event *event) +{ + return sse_sbi_event_func(event, SBI_SSE_EVENT_DISABLE); +} +EXPORT_SYMBOL_GPL(sse_event_disable_local); + +int sse_event_enable_local(struct sse_event *event) +{ + return sse_sbi_event_func(event, SBI_SSE_EVENT_ENABLE); +} +EXPORT_SYMBOL_GPL(sse_event_enable_local); + +static int sse_event_attr_get_no_lock(struct sse_registered_event *reg_evt, + unsigned long attr_id, unsigned long *val) +{ + struct sbiret sret; + u32 evt =3D reg_evt->event->evt_id; + unsigned long phys; + + phys =3D sse_event_get_attr_phys(reg_evt); + + sret =3D sbi_ecall(SBI_EXT_SSE, SBI_SSE_EVENT_ATTR_READ, evt, attr_id, 1, + phys, 0, 0); + if (sret.error) { + pr_debug("Failed to get event %x attr %lx, error %ld\n", evt, + attr_id, sret.error); + return sbi_err_map_linux_errno(sret.error); + } + + *val =3D reg_evt->attr; + + return 0; +} + +static int sse_event_attr_set_nolock(struct sse_registered_event *reg_evt, + unsigned long attr_id, unsigned long val) +{ + struct sbiret sret; + u32 evt =3D reg_evt->event->evt_id; + unsigned long phys; + + reg_evt->attr =3D val; + phys =3D sse_event_get_attr_phys(reg_evt); + + sret =3D sbi_ecall(SBI_EXT_SSE, SBI_SSE_EVENT_ATTR_WRITE, evt, attr_id, 1, + phys, 0, 0); + if (sret.error) + pr_debug("Failed to set event %x attr %lx, error %ld\n", evt, + attr_id, sret.error); + + return sbi_err_map_linux_errno(sret.error); +} + +static int sse_event_set_target_cpu_nolock(struct sse_event *event, + unsigned int cpu) +{ + unsigned int hart_id =3D cpuid_to_hartid_map(cpu); + struct sse_registered_event *reg_evt =3D event->global; + u32 evt =3D event->evt_id; + bool was_enabled; + int ret; + + if (!sse_event_is_global(evt)) + return -EINVAL; + + was_enabled =3D reg_evt->is_enabled; + if (was_enabled) + sse_event_disable_local(event); + + ret =3D sse_event_attr_set_nolock(reg_evt, SBI_SSE_ATTR_PREFERRED_HART, h= art_id); + if (ret =3D=3D 0) + event->cpu =3D cpu; + + if (was_enabled) + sse_event_enable_local(event); + + return 0; +} + +int sse_event_set_target_cpu(struct sse_event *event, unsigned int cpu) +{ + int ret; + + scoped_guard(mutex, &sse_mutex) { + scoped_guard(cpus_read_lock) { + if (!cpu_online(cpu)) + return -EINVAL; + + ret =3D sse_event_set_target_cpu_nolock(event, cpu); + } + } + + return ret; +} +EXPORT_SYMBOL_GPL(sse_event_set_target_cpu); + +static int sse_event_init_registered(unsigned int cpu, + struct sse_registered_event *reg_evt, + struct sse_event *event) +{ + reg_evt->event =3D event; + + return arch_sse_init_event(®_evt->arch, event->evt_id, cpu); +} + +static void sse_event_free_registered(struct sse_registered_event *reg_evt) +{ + arch_sse_free_event(®_evt->arch); +} + +static int sse_event_alloc_global(struct sse_event *event) +{ + int err; + struct sse_registered_event *reg_evt; + + reg_evt =3D kzalloc(sizeof(*reg_evt), GFP_KERNEL); + if (!reg_evt) + return -ENOMEM; + + event->global =3D reg_evt; + err =3D sse_event_init_registered(smp_processor_id(), reg_evt, event); + if (err) + kfree(reg_evt); + + return err; +} + +static int sse_event_alloc_local(struct sse_event *event) +{ + int err; + unsigned int cpu, err_cpu; + struct sse_registered_event *reg_evt; + struct sse_registered_event __percpu *reg_evts; + + reg_evts =3D alloc_percpu(struct sse_registered_event); + if (!reg_evts) + return -ENOMEM; + + event->local =3D reg_evts; + + for_each_possible_cpu(cpu) { + reg_evt =3D per_cpu_ptr(reg_evts, cpu); + err =3D sse_event_init_registered(cpu, reg_evt, event); + if (err) { + err_cpu =3D cpu; + goto err_free_per_cpu; + } + } + + return 0; + +err_free_per_cpu: + for_each_possible_cpu(cpu) { + if (cpu =3D=3D err_cpu) + break; + reg_evt =3D per_cpu_ptr(reg_evts, cpu); + sse_event_free_registered(reg_evt); + } + + free_percpu(reg_evts); + + return err; +} + +static struct sse_event *sse_event_alloc(u32 evt, u32 priority, + sse_event_handler *handler, void *arg) +{ + int err; + struct sse_event *event; + + event =3D kzalloc(sizeof(*event), GFP_KERNEL); + if (!event) + return ERR_PTR(-ENOMEM); + + event->evt_id =3D evt; + event->priority =3D priority; + event->handler_arg =3D arg; + event->handler =3D handler; + + if (sse_event_is_global(evt)) + err =3D sse_event_alloc_global(event); + else + err =3D sse_event_alloc_local(event); + + if (err) { + kfree(event); + return ERR_PTR(err); + } + + return event; +} + +static int sse_sbi_register_event(struct sse_event *event, + struct sse_registered_event *reg_evt) +{ + int ret; + + ret =3D sse_event_attr_set_nolock(reg_evt, SBI_SSE_ATTR_PRIO, + event->priority); + if (ret) + return ret; + + return arch_sse_register_event(®_evt->arch); +} + +static int sse_event_register_local(struct sse_event *event) +{ + int ret; + struct sse_registered_event *reg_evt; + + reg_evt =3D per_cpu_ptr(event->local, smp_processor_id()); + ret =3D sse_sbi_register_event(event, reg_evt); + if (ret) + pr_debug("Failed to register event %x: err %d\n", event->evt_id, ret); + + return ret; +} + +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; + cpumask_t error; +}; + +static void sse_event_per_cpu_func(void *info) +{ + int ret; + struct sse_per_cpu_evt *cpu_evt =3D info; + + if (cpu_evt->func =3D=3D SBI_SSE_EVENT_REGISTER) + ret =3D sse_event_register_local(cpu_evt->event); + else + ret =3D sse_sbi_event_func(cpu_evt->event, cpu_evt->func); + + if (ret) + cpumask_set_cpu(smp_processor_id(), &cpu_evt->error); +} + +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_id)) { + sse_event_free_registered(event->global); + kfree(event->global); + } else { + for_each_possible_cpu(cpu) { + reg_evt =3D per_cpu_ptr(event->local, cpu); + sse_event_free_registered(reg_evt); + } + free_percpu(event->local); + } + + kfree(event); +} + +static void sse_on_each_cpu(struct sse_event *event, unsigned long func, u= nsigned long revert_func) +{ + struct sse_per_cpu_evt cpu_evt; + + cpu_evt.event =3D event; + cpumask_clear(&cpu_evt.error); + cpu_evt.func =3D func; + on_each_cpu(sse_event_per_cpu_func, &cpu_evt, 1); + /* If there are some error reported by CPUs, revert event state on the ot= her ones */ + if (!cpumask_empty(&cpu_evt.error)) { + cpumask_t revert; + cpumask_andnot(&revert, cpu_online_mask, &cpu_evt.error); + cpu_evt.func =3D revert_func; + on_each_cpu_mask(&revert, sse_event_per_cpu_func, &cpu_evt, 1); + } +} + +int sse_event_enable(struct sse_event *event) +{ + int ret =3D 0; + + scoped_guard(mutex, &sse_mutex) { + scoped_guard(cpus_read_lock) { + if (sse_event_is_global(event->evt_id)) { + ret =3D sse_event_enable_local(event); + } else { + sse_on_each_cpu(event, SBI_SSE_EVENT_ENABLE, SBI_SSE_EVENT_DISABLE); + } + } + } + return ret; +} +EXPORT_SYMBOL_GPL(sse_event_enable); + +static int sse_events_mask(void) +{ + struct sbiret ret; + + ret =3D sbi_ecall(SBI_EXT_SSE, SBI_SSE_EVENT_HART_MASK, 0, 0, 0, 0, 0, 0); + + return sbi_err_map_linux_errno(ret.error); +} + +static int sse_events_unmask(void) +{ + struct sbiret ret; + + ret =3D sbi_ecall(SBI_EXT_SSE, SBI_SSE_EVENT_HART_UNMASK, 0, 0, 0, 0, 0, = 0); + + return sbi_err_map_linux_errno(ret.error); +} + +static void sse_event_disable_nolock(struct sse_event *event) +{ + struct sse_per_cpu_evt cpu_evt; + + if (sse_event_is_global(event->evt_id)) { + sse_event_disable_local(event); + } else { + cpu_evt.event =3D event; + cpu_evt.func =3D SBI_SSE_EVENT_DISABLE; + on_each_cpu(sse_event_per_cpu_func, &cpu_evt, 1); + } +} + +void sse_event_disable(struct sse_event *event) +{ + scoped_guard(mutex, &sse_mutex) { + scoped_guard(cpus_read_lock) { + sse_event_disable_nolock(event); + } + } +} +EXPORT_SYMBOL_GPL(sse_event_disable); + +struct sse_event *sse_event_register(u32 evt, u32 priority, + sse_event_handler *handler, void *arg) +{ + struct sse_event *event; + int ret =3D 0; + + if (!sse_available) + return ERR_PTR(-EOPNOTSUPP); + + guard(mutex)(&sse_mutex); + if (sse_event_get(evt)) + return ERR_PTR(-EEXIST); + + event =3D sse_event_alloc(evt, priority, handler, arg); + if (IS_ERR(event)) + return event; + + scoped_guard(cpus_read_lock) { + if (sse_event_is_global(evt)) { + unsigned long preferred_hart; + + ret =3D sse_event_attr_get_no_lock(event->global, + SBI_SSE_ATTR_PREFERRED_HART, + &preferred_hart); + if (ret) + goto err_event_free; + event->cpu =3D riscv_hartid_to_cpuid(preferred_hart); + + ret =3D sse_sbi_register_event(event, event->global); + if (ret) + goto err_event_free; + + } else { + sse_on_each_cpu(event, SBI_SSE_EVENT_REGISTER, SBI_SSE_EVENT_DISABLE); + } + } + + scoped_guard(spinlock, &events_list_lock) + list_add(&event->list, &events); + + return event; + +err_event_free: + sse_event_free(event); + + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(sse_event_register); + +static void sse_event_unregister_nolock(struct sse_event *event) +{ + struct sse_per_cpu_evt cpu_evt; + + if (sse_event_is_global(event->evt_id)) { + sse_sbi_unregister_event(event); + } else { + cpu_evt.event =3D event; + cpu_evt.func =3D SBI_SSE_EVENT_UNREGISTER; + on_each_cpu(sse_event_per_cpu_func, &cpu_evt, 1); + } +} + +void sse_event_unregister(struct sse_event *event) +{ + scoped_guard(mutex, &sse_mutex) { + scoped_guard(cpus_read_lock) + sse_event_unregister_nolock(event); + + scoped_guard(spinlock, &events_list_lock) + list_del(&event->list); + + sse_event_free(event); + } +} +EXPORT_SYMBOL_GPL(sse_event_unregister); + +static int sse_cpu_online(unsigned int cpu) +{ + struct sse_event *event; + + scoped_guard(spinlock, &events_list_lock) { + list_for_each_entry(event, &events, list) { + if (sse_event_is_global(event->evt_id)) + continue; + + sse_event_register_local(event); + if (sse_get_reg_evt(event)) + sse_event_enable_local(event); + } + } + + /* Ready to handle events. Unmask SSE. */ + return sse_events_unmask(); +} + +static int sse_cpu_teardown(unsigned int cpu) +{ + int ret =3D 0; + unsigned int next_cpu; + struct sse_event *event; + + /* Mask the sse events */ + ret =3D sse_events_mask(); + if (ret) + return ret; + + scoped_guard(spinlock, &events_list_lock) { + list_for_each_entry(event, &events, list) { + if (!sse_event_is_global(event->evt_id)) { + if (event->global->is_enabled) + sse_event_disable_local(event); + + sse_sbi_unregister_event(event); + continue; + } + + if (event->cpu !=3D smp_processor_id()) + continue; + + /* Update destination hart for global event */ + next_cpu =3D cpumask_any_but(cpu_online_mask, cpu); + ret =3D sse_event_set_target_cpu_nolock(event, next_cpu); + } + } + + return ret; +} + +static void sse_reset(void) +{ + struct sse_event *event; + + list_for_each_entry(event, &events, list) { + sse_event_disable_nolock(event); + sse_event_unregister_nolock(event); + } +} + +static int sse_pm_notifier(struct notifier_block *nb, unsigned long action, + void *data) +{ + WARN_ON_ONCE(preemptible()); + + switch (action) { + case CPU_PM_ENTER: + sse_events_mask(); + break; + case CPU_PM_EXIT: + case CPU_PM_ENTER_FAILED: + sse_events_unmask(); + break; + default: + return NOTIFY_DONE; + } + + return NOTIFY_OK; +} + +static struct notifier_block sse_pm_nb =3D { + .notifier_call =3D sse_pm_notifier, +}; + +/* + * Mask all CPUs and unregister all events on panic, reboot or kexec. + */ +static int sse_reboot_notifier(struct notifier_block *nb, unsigned long ac= tion, + void *data) +{ + cpuhp_remove_state(sse_hp_state); + sse_reset(); + + return NOTIFY_OK; +} + +static struct notifier_block sse_reboot_nb =3D { + .notifier_call =3D sse_reboot_notifier, +}; + +static int __init sse_init(void) +{ + int ret; + + if (sbi_probe_extension(SBI_EXT_SSE) <=3D 0) { + pr_err("Missing SBI SSE extension\n"); + return -EOPNOTSUPP; + } + pr_info("SBI SSE extension detected\n"); + + ret =3D cpu_pm_register_notifier(&sse_pm_nb); + if (ret) { + pr_warn("Failed to register CPU PM notifier...\n"); + return ret; + } + + ret =3D register_reboot_notifier(&sse_reboot_nb); + if (ret) { + pr_warn("Failed to register reboot notifier...\n"); + goto remove_cpupm; + } + + ret =3D cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "riscv/sse:online", + sse_cpu_online, sse_cpu_teardown); + if (ret < 0) + goto remove_reboot; + + sse_hp_state =3D ret; + sse_available =3D true; + + return 0; + +remove_reboot: + unregister_reboot_notifier(&sse_reboot_nb); + +remove_cpupm: + cpu_pm_unregister_notifier(&sse_pm_nb); + + return ret; +} +arch_initcall(sse_init); diff --git a/include/linux/riscv_sse.h b/include/linux/riscv_sse.h new file mode 100644 index 000000000000..d7bd0e22a00f --- /dev/null +++ b/include/linux/riscv_sse.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2024 Rivos Inc. + */ + +#ifndef __LINUX_RISCV_SSE_H +#define __LINUX_RISCV_SSE_H + +#include +#include + +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); + +int sse_event_enable_local(struct sse_event *sse_evt); +int sse_event_disable_local(struct sse_event *sse_evt); + +#else +static inline struct sse_event *sse_event_register(u32 event_num, u32 prio= rity, + sse_event_handler *handler, + void *arg) +{ + return ERR_PTR(-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 */ --=20 2.43.0