Commit 81ea00838c682da06637bcf208549095181df337

Authored by Heinrich Schuchardt
Committed by Alexander Graf
1 parent eb3bc8bb17

efi_loader: PSCI reset and shutdown

When an operating system started via bootefi tries to reset or power off
this is done by calling the EFI runtime ResetSystem(). On most ARMv8 system
the actual reset relies on PSCI. Depending on whether the PSCI firmware
resides the hypervisor (EL2) or in the secure monitor (EL3) either an HVC
or an SMC command has to be issued.

The current implementation always uses SMC. This results in crashes on
systems where the PSCI firmware is implemented in the hypervisor, e.g.
qemu-arm64_defconfig.

The logic to decide which call is needed based on the device tree is
already implemented in the PSCI firmware driver. During the EFI runtime
the device driver model is not available. But we can minimize code
duplication by merging the EFI runtime reset and poweroff code with
the PSCI firmware driver.

As the same HVC/SMC problem is also evident for the ARMv8 do_poweroff
and reset_misc routines let's move them into the same code module.

Signed-off-by: Heinrich Schuchardt <xypron.glpk@gmx.de>
Reviewed-by: Sumit Garg <sumit.garg@linaro.org>
Tested-by: Sumit Garg <sumit.garg@linaro.org>
Signed-off-by: Alexander Graf <agraf@suse.de>

Showing 6 changed files with 96 additions and 67 deletions Side-by-side Diff

arch/arm/cpu/armv7/smccc-call.S
... ... @@ -7,6 +7,8 @@
7 7 #include <asm/opcodes-sec.h>
8 8 #include <asm/opcodes-virt.h>
9 9  
  10 + .section .text.efi_runtime
  11 +
10 12 #define UNWIND(x...)
11 13 /*
12 14 * Wrap c macros in asm macros to delay expansion until after the
arch/arm/cpu/armv8/Kconfig
... ... @@ -96,6 +96,7 @@
96 96 config PSCI_RESET
97 97 bool "Use PSCI for reset and shutdown"
98 98 default y
  99 + select ARM_SMCCC if OF_CONTROL
99 100 depends on !ARCH_EXYNOS7 && !ARCH_BCM283X && \
100 101 !TARGET_LS2080A_SIMU && !TARGET_LS2080AQDS && \
101 102 !TARGET_LS2080ARDB && !TARGET_LS2080A_EMU && \
arch/arm/cpu/armv8/fwcall.c
... ... @@ -7,7 +7,6 @@
7 7  
8 8 #include <asm-offsets.h>
9 9 #include <config.h>
10   -#include <efi_loader.h>
11 10 #include <version.h>
12 11 #include <asm/macro.h>
13 12 #include <asm/psci.h>
... ... @@ -19,7 +18,7 @@
19 18 * x0~x7: input arguments
20 19 * x0~x3: output arguments
21 20 */
22   -static void __efi_runtime hvc_call(struct pt_regs *args)
  21 +static void hvc_call(struct pt_regs *args)
23 22 {
24 23 asm volatile(
25 24 "ldr x0, %0\n"
... ... @@ -53,7 +52,7 @@
53 52 * x0~x3: output arguments
54 53 */
55 54  
56   -void __efi_runtime smc_call(struct pt_regs *args)
  55 +void smc_call(struct pt_regs *args)
57 56 {
58 57 asm volatile(
59 58 "ldr x0, %0\n"
60 59  
... ... @@ -83,9 +82,9 @@
83 82 * use PSCI on U-Boot running below a hypervisor, please detect
84 83 * this and set the flag accordingly.
85 84 */
86   -static const __efi_runtime_data bool use_smc_for_psci = true;
  85 +static const bool use_smc_for_psci = true;
87 86  
88   -void __noreturn __efi_runtime psci_system_reset(void)
  87 +void __noreturn psci_system_reset(void)
89 88 {
90 89 struct pt_regs regs;
91 90  
... ... @@ -100,7 +99,7 @@
100 99 ;
101 100 }
102 101  
103   -void __noreturn __efi_runtime psci_system_off(void)
  102 +void __noreturn psci_system_off(void)
104 103 {
105 104 struct pt_regs regs;
106 105  
... ... @@ -114,45 +113,4 @@
114 113 while (1)
115 114 ;
116 115 }
117   -
118   -#ifdef CONFIG_CMD_POWEROFF
119   -int do_poweroff(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
120   -{
121   - puts("poweroff ...\n");
122   -
123   - udelay(50000); /* wait 50 ms */
124   -
125   - disable_interrupts();
126   -
127   - psci_system_off();
128   -
129   - /*NOTREACHED*/
130   - return 0;
131   -}
132   -#endif
133   -
134   -#ifdef CONFIG_PSCI_RESET
135   -void reset_misc(void)
136   -{
137   - psci_system_reset();
138   -}
139   -
140   -#ifdef CONFIG_EFI_LOADER
141   -void __efi_runtime EFIAPI efi_reset_system(
142   - enum efi_reset_type reset_type,
143   - efi_status_t reset_status,
144   - unsigned long data_size, void *reset_data)
145   -{
146   - if (reset_type == EFI_RESET_COLD ||
147   - reset_type == EFI_RESET_WARM ||
148   - reset_type == EFI_RESET_PLATFORM_SPECIFIC) {
149   - psci_system_reset();
150   - } else if (reset_type == EFI_RESET_SHUTDOWN) {
151   - psci_system_off();
152   - }
153   -
154   - while (1) { }
155   -}
156   -#endif /* CONFIG_EFI_LOADER */
157   -#endif /* CONFIG_PSCI_RESET */
arch/arm/cpu/armv8/smccc-call.S
... ... @@ -6,6 +6,8 @@
6 6 #include <linux/arm-smccc.h>
7 7 #include <generated/asm-offsets.h>
8 8  
  9 + .section .text.efi_runtime
  10 +
9 11 .macro SMCCC instr
10 12 .cfi_startproc
11 13 \instr #0
drivers/firmware/psci.c
... ... @@ -9,31 +9,38 @@
9 9 #include <common.h>
10 10 #include <dm.h>
11 11 #include <dm/lists.h>
  12 +#include <efi_loader.h>
12 13 #include <linux/libfdt.h>
13 14 #include <linux/arm-smccc.h>
14 15 #include <linux/errno.h>
15 16 #include <linux/printk.h>
16 17 #include <linux/psci.h>
17 18  
18   -psci_fn *invoke_psci_fn;
  19 +#define DRIVER_NAME "psci"
19 20  
20   -static unsigned long __invoke_psci_fn_hvc(unsigned long function_id,
21   - unsigned long arg0, unsigned long arg1,
22   - unsigned long arg2)
23   -{
24   - struct arm_smccc_res res;
  21 +#define PSCI_METHOD_HVC 1
  22 +#define PSCI_METHOD_SMC 2
25 23  
26   - arm_smccc_hvc(function_id, arg0, arg1, arg2, 0, 0, 0, 0, &res);
27   - return res.a0;
28   -}
  24 +int __efi_runtime_data psci_method;
29 25  
30   -static unsigned long __invoke_psci_fn_smc(unsigned long function_id,
31   - unsigned long arg0, unsigned long arg1,
32   - unsigned long arg2)
  26 +unsigned long __efi_runtime invoke_psci_fn
  27 + (unsigned long function_id, unsigned long arg0,
  28 + unsigned long arg1, unsigned long arg2)
33 29 {
34 30 struct arm_smccc_res res;
35 31  
36   - arm_smccc_smc(function_id, arg0, arg1, arg2, 0, 0, 0, 0, &res);
  32 + /*
  33 + * In the __efi_runtime we need to avoid the switch statement. In some
  34 + * cases the compiler creates lookup tables to implement switch. These
  35 + * tables are not correctly relocated when SetVirtualAddressMap is
  36 + * called.
  37 + */
  38 + if (psci_method == PSCI_METHOD_SMC)
  39 + arm_smccc_smc(function_id, arg0, arg1, arg2, 0, 0, 0, 0, &res);
  40 + else if (psci_method == PSCI_METHOD_HVC)
  41 + arm_smccc_hvc(function_id, arg0, arg1, arg2, 0, 0, 0, 0, &res);
  42 + else
  43 + res.a0 = PSCI_RET_DISABLED;
37 44 return res.a0;
38 45 }
39 46  
40 47  
... ... @@ -67,9 +74,9 @@
67 74 }
68 75  
69 76 if (!strcmp("hvc", method)) {
70   - invoke_psci_fn = __invoke_psci_fn_hvc;
  77 + psci_method = PSCI_METHOD_HVC;
71 78 } else if (!strcmp("smc", method)) {
72   - invoke_psci_fn = __invoke_psci_fn_smc;
  79 + psci_method = PSCI_METHOD_SMC;
73 80 } else {
74 81 pr_warn("invalid \"method\" property: %s\n", method);
75 82 return -EINVAL;
... ... @@ -78,6 +85,67 @@
78 85 return 0;
79 86 }
80 87  
  88 +/**
  89 + * void do_psci_probe() - probe PSCI firmware driver
  90 + *
  91 + * Ensure that psci_method is initialized.
  92 + */
  93 +static void __maybe_unused do_psci_probe(void)
  94 +{
  95 + struct udevice *dev;
  96 +
  97 + uclass_get_device_by_name(UCLASS_FIRMWARE, DRIVER_NAME, &dev);
  98 +}
  99 +
  100 +#if IS_ENABLED(CONFIG_EFI_LOADER) && IS_ENABLED(CONFIG_PSCI_RESET)
  101 +efi_status_t efi_reset_system_init(void)
  102 +{
  103 + do_psci_probe();
  104 + return EFI_SUCCESS;
  105 +}
  106 +
  107 +void __efi_runtime EFIAPI efi_reset_system(enum efi_reset_type reset_type,
  108 + efi_status_t reset_status,
  109 + unsigned long data_size,
  110 + void *reset_data)
  111 +{
  112 + if (reset_type == EFI_RESET_COLD ||
  113 + reset_type == EFI_RESET_WARM ||
  114 + reset_type == EFI_RESET_PLATFORM_SPECIFIC) {
  115 + invoke_psci_fn(PSCI_0_2_FN_SYSTEM_RESET, 0, 0, 0);
  116 + } else if (reset_type == EFI_RESET_SHUTDOWN) {
  117 + invoke_psci_fn(PSCI_0_2_FN_SYSTEM_OFF, 0, 0, 0);
  118 + }
  119 + while (1)
  120 + ;
  121 +}
  122 +#endif /* IS_ENABLED(CONFIG_EFI_LOADER) && IS_ENABLED(CONFIG_PSCI_RESET) */
  123 +
  124 +#ifdef CONFIG_PSCI_RESET
  125 +void reset_misc(void)
  126 +{
  127 + do_psci_probe();
  128 + invoke_psci_fn(PSCI_0_2_FN_SYSTEM_RESET, 0, 0, 0);
  129 +}
  130 +#endif /* CONFIG_PSCI_RESET */
  131 +
  132 +#ifdef CONFIG_CMD_POWEROFF
  133 +int do_poweroff(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
  134 +{
  135 + do_psci_probe();
  136 +
  137 + puts("poweroff ...\n");
  138 + udelay(50000); /* wait 50 ms */
  139 +
  140 + disable_interrupts();
  141 + invoke_psci_fn(PSCI_0_2_FN_SYSTEM_OFF, 0, 0, 0);
  142 + enable_interrupts();
  143 +
  144 + log_err("Power off not supported on this platform\n");
  145 + return CMD_RET_FAILURE;
  146 +}
  147 +#endif
  148 +
81 149 static const struct udevice_id psci_of_match[] = {
82 150 { .compatible = "arm,psci" },
83 151 { .compatible = "arm,psci-0.2" },
... ... @@ -86,7 +154,7 @@
86 154 };
87 155  
88 156 U_BOOT_DRIVER(psci) = {
89   - .name = "psci",
  157 + .name = DRIVER_NAME,
90 158 .id = UCLASS_FIRMWARE,
91 159 .of_match = psci_of_match,
92 160 .bind = psci_bind,
include/linux/psci.h
... ... @@ -88,10 +88,8 @@
88 88 #define PSCI_RET_DISABLED -8
89 89  
90 90 #ifdef CONFIG_ARM_PSCI_FW
91   -typedef unsigned long (psci_fn)(unsigned long, unsigned long,
92   - unsigned long, unsigned long);
93   -
94   -extern psci_fn *invoke_psci_fn;
  91 +unsigned long invoke_psci_fn(unsigned long a0, unsigned long a1,
  92 + unsigned long a2, unsigned long a3);
95 93 #else
96 94 unsigned long invoke_psci_fn(unsigned long a0, unsigned long a1,
97 95 unsigned long a2, unsigned long a3)