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

Message ID 20230418194746.620266-1-scott@os.amperecomputing.com
State New
Headers
Series [v2,RESEND,1/2] arm64: sdei: Use alternative patching to select the conduit in sdei exit |

Commit Message

D Scott Phillips April 18, 2023, 7:47 p.m. UTC
  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@arm.com>
Signed-off-by: D Scott Phillips <scott@os.amperecomputing.com>
---
 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(-)
  

Patch

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 */