[PATCH v2 RESEND 1/2] arm64: sdei: Use alternative patching to select the conduit in sdei exit

From: D Scott Phillips
Date: Tue Apr 18 2023 - 15:48:11 EST


Patch in the conduit instruction for SDEI instead of branching between the
possible conduits. The sdei driver's conduit initialization is moved
earlier in the initialization flow, into setup_arch(), so that it is ready
in time for alternative patching.

Suggested-by: James Morse <james.morse@xxxxxxx>
Signed-off-by: D Scott Phillips <scott@xxxxxxxxxxxxxxxxxxxxxx>
---
arch/arm64/include/asm/sdei.h | 13 +++----
arch/arm64/kernel/entry.S | 26 ++++++-------
arch/arm64/kernel/sdei.c | 30 ++++++++++++---
arch/arm64/kernel/setup.c | 3 ++
drivers/firmware/arm_sdei.c | 71 +++++++++++++++++++----------------
include/linux/arm_sdei.h | 6 ++-
6 files changed, 88 insertions(+), 61 deletions(-)

diff --git a/arch/arm64/include/asm/sdei.h b/arch/arm64/include/asm/sdei.h
index 4292d9bafb9d..badb39510515 100644
--- a/arch/arm64/include/asm/sdei.h
+++ b/arch/arm64/include/asm/sdei.h
@@ -3,10 +3,6 @@
#ifndef __ASM_SDEI_H
#define __ASM_SDEI_H

-/* Values for sdei_exit_mode */
-#define SDEI_EXIT_HVC 0
-#define SDEI_EXIT_SMC 1
-
#define SDEI_STACK_SIZE IRQ_STACK_SIZE

#ifndef __ASSEMBLY__
@@ -17,8 +13,6 @@

#include <asm/virt.h>

-extern unsigned long sdei_exit_mode;
-
/* Software Delegated Exception entry point from firmware*/
asmlinkage void __sdei_asm_handler(unsigned long event_num, unsigned long arg,
unsigned long pc, unsigned long pstate);
@@ -40,8 +34,11 @@ asmlinkage unsigned long __sdei_handler(struct pt_regs *regs,
unsigned long do_sdei_event(struct pt_regs *regs,
struct sdei_registered_event *arg);

-unsigned long sdei_arch_get_entry_point(int conduit);
-#define sdei_arch_get_entry_point(x) sdei_arch_get_entry_point(x)
+unsigned long sdei_arch_get_entry_point(void);
+#define sdei_arch_get_entry_point sdei_arch_get_entry_point
+
+void __init sdei_patch_conduit(struct alt_instr *alt, __le32 *origptr,
+ __le32 *updptr, int nr_inst);

#endif /* __ASSEMBLY__ */
#endif /* __ASM_SDEI_H */
diff --git a/arch/arm64/kernel/entry.S b/arch/arm64/kernel/entry.S
index ab2a6e33c052..ae7aa02f0df5 100644
--- a/arch/arm64/kernel/entry.S
+++ b/arch/arm64/kernel/entry.S
@@ -914,13 +914,10 @@ NOKPROBE(call_on_irq_stack)
#include <asm/sdei.h>
#include <uapi/linux/arm_sdei.h>

-.macro sdei_handler_exit exit_mode
- /* On success, this call never returns... */
- cmp \exit_mode, #SDEI_EXIT_SMC
- b.ne 99f
- smc #0
- b .
-99: hvc #0
+.macro sdei_handler_exit
+alternative_cb ARM64_ALWAYS_SYSTEM, sdei_patch_conduit
+ nop // Patched to SMC/HVC #0
+alternative_cb_end
b .
.endm

@@ -955,7 +952,6 @@ NOKPROBE(__sdei_asm_entry_trampoline)
* Make the exit call and restore the original ttbr1_el1
*
* x0 & x1: setup for the exit API call
- * x2: exit_mode
* x4: struct sdei_registered_event argument from registration time.
*/
SYM_CODE_START(__sdei_asm_exit_trampoline)
@@ -964,7 +960,7 @@ SYM_CODE_START(__sdei_asm_exit_trampoline)

tramp_unmap_kernel tmp=x4

-1: sdei_handler_exit exit_mode=x2
+1: sdei_handler_exit
SYM_CODE_END(__sdei_asm_exit_trampoline)
NOKPROBE(__sdei_asm_exit_trampoline)
.popsection // .entry.tramp.text
@@ -1070,14 +1066,16 @@ SYM_CODE_START(__sdei_asm_handler)
mov_q x3, SDEI_1_0_FN_SDEI_EVENT_COMPLETE_AND_RESUME
csel x0, x2, x3, ls

- ldr_l x2, sdei_exit_mode
-
-alternative_if_not ARM64_UNMAP_KERNEL_AT_EL0
- sdei_handler_exit exit_mode=x2
+#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
+alternative_if ARM64_UNMAP_KERNEL_AT_EL0
+ b 1f
alternative_else_nop_endif
+#endif
+
+ sdei_handler_exit

#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
- tramp_alias dst=x5, sym=__sdei_asm_exit_trampoline, tmp=x3
+1: tramp_alias dst=x5, sym=__sdei_asm_exit_trampoline, tmp=x3
br x5
#endif
SYM_CODE_END(__sdei_asm_handler)
diff --git a/arch/arm64/kernel/sdei.c b/arch/arm64/kernel/sdei.c
index 830be01af32d..be7f6ea49956 100644
--- a/arch/arm64/kernel/sdei.c
+++ b/arch/arm64/kernel/sdei.c
@@ -20,8 +20,6 @@
#include <asm/sysreg.h>
#include <asm/vmap_stack.h>

-unsigned long sdei_exit_mode;
-
/*
* VMAP'd stacks checking for stack overflow on exception using sp as a scratch
* register, meaning SDEI has to switch to its own stack. We need two stacks as
@@ -162,7 +160,7 @@ static int init_sdei_scs(void)
return err;
}

-unsigned long sdei_arch_get_entry_point(int conduit)
+unsigned long sdei_arch_get_entry_point(void)
{
/*
* SDEI works between adjacent exception levels. If we booted at EL1 we
@@ -181,8 +179,6 @@ unsigned long sdei_arch_get_entry_point(int conduit)
if (init_sdei_scs())
goto out_err_free_stacks;

- sdei_exit_mode = (conduit == SMCCC_CONDUIT_HVC) ? SDEI_EXIT_HVC : SDEI_EXIT_SMC;
-
#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
if (arm64_kernel_unmapped_at_el0()) {
unsigned long offset;
@@ -262,3 +258,27 @@ unsigned long __kprobes do_sdei_event(struct pt_regs *regs,

return vbar + 0x480;
}
+
+/*
+ * Patch in the sdei conduit instruction.
+ */
+void __init sdei_patch_conduit(struct alt_instr *alt, __le32 *origptr,
+ __le32 *updptr, int nr_inst)
+{
+ u32 insn;
+
+ BUG_ON(nr_inst != 1); /* NOP -> HVC/SMC */
+
+ switch (sdei_get_conduit()) {
+ case SMCCC_CONDUIT_HVC:
+ insn = aarch64_insn_get_hvc_value();
+ break;
+ case SMCCC_CONDUIT_SMC:
+ insn = aarch64_insn_get_smc_value();
+ break;
+ default:
+ return;
+ }
+
+ *updptr = cpu_to_le32(insn);
+}
diff --git a/arch/arm64/kernel/setup.c b/arch/arm64/kernel/setup.c
index b8ec7b3ac9cb..6286c4c59074 100644
--- a/arch/arm64/kernel/setup.c
+++ b/arch/arm64/kernel/setup.c
@@ -7,6 +7,7 @@
*/

#include <linux/acpi.h>
+#include <linux/arm_sdei.h>
#include <linux/export.h>
#include <linux/kernel.h>
#include <linux/stddef.h>
@@ -365,6 +366,8 @@ void __init __no_sanitize_address setup_arch(char **cmdline_p)
else
psci_acpi_init();

+ sdei_conduit_init();
+
init_bootcpu_ops();
smp_init_cpus();
smp_build_mpidr_hash();
diff --git a/drivers/firmware/arm_sdei.c b/drivers/firmware/arm_sdei.c
index 1e1a51510e83..0dbb88bcc9a2 100644
--- a/drivers/firmware/arm_sdei.c
+++ b/drivers/firmware/arm_sdei.c
@@ -919,37 +919,12 @@ int sdei_unregister_ghes(struct ghes *ghes)
return err;
}

-static int sdei_get_conduit(struct platform_device *pdev)
+enum arm_smccc_conduit sdei_get_conduit(void)
{
- const char *method;
- struct device_node *np = pdev->dev.of_node;
-
- sdei_firmware_call = NULL;
- if (np) {
- if (of_property_read_string(np, "method", &method)) {
- pr_warn("missing \"method\" property\n");
- return SMCCC_CONDUIT_NONE;
- }
-
- if (!strcmp("hvc", method)) {
- sdei_firmware_call = &sdei_smccc_hvc;
- return SMCCC_CONDUIT_HVC;
- } else if (!strcmp("smc", method)) {
- sdei_firmware_call = &sdei_smccc_smc;
- return SMCCC_CONDUIT_SMC;
- }
-
- pr_warn("invalid \"method\" property: %s\n", method);
- } else if (!acpi_disabled) {
- if (acpi_psci_use_hvc()) {
- sdei_firmware_call = &sdei_smccc_hvc;
- return SMCCC_CONDUIT_HVC;
- } else {
- sdei_firmware_call = &sdei_smccc_smc;
- return SMCCC_CONDUIT_SMC;
- }
- }
-
+ if (sdei_firmware_call == sdei_smccc_hvc)
+ return SMCCC_CONDUIT_HVC;
+ if (sdei_firmware_call == sdei_smccc_smc)
+ return SMCCC_CONDUIT_SMC;
return SMCCC_CONDUIT_NONE;
}

@@ -957,9 +932,7 @@ static int sdei_probe(struct platform_device *pdev)
{
int err;
u64 ver = 0;
- int conduit;

- conduit = sdei_get_conduit(pdev);
if (!sdei_firmware_call)
return 0;

@@ -984,7 +957,7 @@ static int sdei_probe(struct platform_device *pdev)
if (err)
return err;

- sdei_entry_point = sdei_arch_get_entry_point(conduit);
+ sdei_entry_point = sdei_arch_get_entry_point();
if (!sdei_entry_point) {
/* Not supported due to hardware or boot configuration */
sdei_mark_interface_broken();
@@ -1059,6 +1032,38 @@ static bool __init sdei_present_acpi(void)
return true;
}

+void __init sdei_conduit_init(void)
+{
+ const char *method;
+ struct device_node *np;
+
+ if (acpi_disabled) {
+ np = of_find_matching_node_and_match(NULL, sdei_of_match, NULL);
+ if (!np || !of_device_is_available(np))
+ return;
+
+ if (of_property_read_string(np, "method", &method)) {
+ pr_warn("missing \"method\" property\n");
+ return;
+ }
+
+ if (!strcmp("hvc", method))
+ sdei_firmware_call = &sdei_smccc_hvc;
+ else if (!strcmp("smc", method))
+ sdei_firmware_call = &sdei_smccc_smc;
+ else
+ pr_warn("invalid \"method\" property: %s\n", method);
+ } else {
+ if (!sdei_present_acpi())
+ return;
+
+ if (acpi_psci_use_hvc())
+ sdei_firmware_call = &sdei_smccc_hvc;
+ else
+ sdei_firmware_call = &sdei_smccc_smc;
+ }
+}
+
void __init sdei_init(void)
{
struct platform_device *pdev;
diff --git a/include/linux/arm_sdei.h b/include/linux/arm_sdei.h
index 14dc461b0e82..de9aafd87c48 100644
--- a/include/linux/arm_sdei.h
+++ b/include/linux/arm_sdei.h
@@ -13,7 +13,7 @@

/* Arch code should override this to set the entry point from firmware... */
#ifndef sdei_arch_get_entry_point
-#define sdei_arch_get_entry_point(conduit) (0)
+#define sdei_arch_get_entry_point() (0)
#endif

/*
@@ -22,6 +22,8 @@
*/
typedef int (sdei_event_callback)(u32 event, struct pt_regs *regs, void *arg);

+enum arm_smccc_conduit sdei_get_conduit(void);
+
/*
* Register your callback to claim an event. The event must be described
* by firmware.
@@ -47,10 +49,12 @@ int sdei_unregister_ghes(struct ghes *ghes);
int sdei_mask_local_cpu(void);
int sdei_unmask_local_cpu(void);
void __init sdei_init(void);
+void __init sdei_conduit_init(void);
#else
static inline int sdei_mask_local_cpu(void) { return 0; }
static inline int sdei_unmask_local_cpu(void) { return 0; }
static inline void sdei_init(void) { }
+static inline void sdei_conduit_init(void) { }
#endif /* CONFIG_ARM_SDE_INTERFACE */


--
2.39.2