mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-12-11 05:42:58 +00:00
81ea00838c
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>
162 lines
3.8 KiB
C
162 lines
3.8 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Copyright (C) 2017 Masahiro Yamada <yamada.masahiro@socionext.com>
|
|
*
|
|
* Based on drivers/firmware/psci.c from Linux:
|
|
* Copyright (C) 2015 ARM Limited
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <dm.h>
|
|
#include <dm/lists.h>
|
|
#include <efi_loader.h>
|
|
#include <linux/libfdt.h>
|
|
#include <linux/arm-smccc.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/printk.h>
|
|
#include <linux/psci.h>
|
|
|
|
#define DRIVER_NAME "psci"
|
|
|
|
#define PSCI_METHOD_HVC 1
|
|
#define PSCI_METHOD_SMC 2
|
|
|
|
int __efi_runtime_data psci_method;
|
|
|
|
unsigned long __efi_runtime invoke_psci_fn
|
|
(unsigned long function_id, unsigned long arg0,
|
|
unsigned long arg1, unsigned long arg2)
|
|
{
|
|
struct arm_smccc_res res;
|
|
|
|
/*
|
|
* In the __efi_runtime we need to avoid the switch statement. In some
|
|
* cases the compiler creates lookup tables to implement switch. These
|
|
* tables are not correctly relocated when SetVirtualAddressMap is
|
|
* called.
|
|
*/
|
|
if (psci_method == PSCI_METHOD_SMC)
|
|
arm_smccc_smc(function_id, arg0, arg1, arg2, 0, 0, 0, 0, &res);
|
|
else if (psci_method == PSCI_METHOD_HVC)
|
|
arm_smccc_hvc(function_id, arg0, arg1, arg2, 0, 0, 0, 0, &res);
|
|
else
|
|
res.a0 = PSCI_RET_DISABLED;
|
|
return res.a0;
|
|
}
|
|
|
|
static int psci_bind(struct udevice *dev)
|
|
{
|
|
/* No SYSTEM_RESET support for PSCI 0.1 */
|
|
if (device_is_compatible(dev, "arm,psci-0.2") ||
|
|
device_is_compatible(dev, "arm,psci-1.0")) {
|
|
int ret;
|
|
|
|
/* bind psci-sysreset optionally */
|
|
ret = device_bind_driver(dev, "psci-sysreset", "psci-sysreset",
|
|
NULL);
|
|
if (ret)
|
|
pr_debug("PSCI System Reset was not bound.\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int psci_probe(struct udevice *dev)
|
|
{
|
|
DECLARE_GLOBAL_DATA_PTR;
|
|
const char *method;
|
|
|
|
method = fdt_stringlist_get(gd->fdt_blob, dev_of_offset(dev), "method",
|
|
0, NULL);
|
|
if (!method) {
|
|
pr_warn("missing \"method\" property\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
if (!strcmp("hvc", method)) {
|
|
psci_method = PSCI_METHOD_HVC;
|
|
} else if (!strcmp("smc", method)) {
|
|
psci_method = PSCI_METHOD_SMC;
|
|
} else {
|
|
pr_warn("invalid \"method\" property: %s\n", method);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* void do_psci_probe() - probe PSCI firmware driver
|
|
*
|
|
* Ensure that psci_method is initialized.
|
|
*/
|
|
static void __maybe_unused do_psci_probe(void)
|
|
{
|
|
struct udevice *dev;
|
|
|
|
uclass_get_device_by_name(UCLASS_FIRMWARE, DRIVER_NAME, &dev);
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_EFI_LOADER) && IS_ENABLED(CONFIG_PSCI_RESET)
|
|
efi_status_t efi_reset_system_init(void)
|
|
{
|
|
do_psci_probe();
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
void __efi_runtime EFIAPI efi_reset_system(enum efi_reset_type reset_type,
|
|
efi_status_t reset_status,
|
|
unsigned long data_size,
|
|
void *reset_data)
|
|
{
|
|
if (reset_type == EFI_RESET_COLD ||
|
|
reset_type == EFI_RESET_WARM ||
|
|
reset_type == EFI_RESET_PLATFORM_SPECIFIC) {
|
|
invoke_psci_fn(PSCI_0_2_FN_SYSTEM_RESET, 0, 0, 0);
|
|
} else if (reset_type == EFI_RESET_SHUTDOWN) {
|
|
invoke_psci_fn(PSCI_0_2_FN_SYSTEM_OFF, 0, 0, 0);
|
|
}
|
|
while (1)
|
|
;
|
|
}
|
|
#endif /* IS_ENABLED(CONFIG_EFI_LOADER) && IS_ENABLED(CONFIG_PSCI_RESET) */
|
|
|
|
#ifdef CONFIG_PSCI_RESET
|
|
void reset_misc(void)
|
|
{
|
|
do_psci_probe();
|
|
invoke_psci_fn(PSCI_0_2_FN_SYSTEM_RESET, 0, 0, 0);
|
|
}
|
|
#endif /* CONFIG_PSCI_RESET */
|
|
|
|
#ifdef CONFIG_CMD_POWEROFF
|
|
int do_poweroff(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
|
|
{
|
|
do_psci_probe();
|
|
|
|
puts("poweroff ...\n");
|
|
udelay(50000); /* wait 50 ms */
|
|
|
|
disable_interrupts();
|
|
invoke_psci_fn(PSCI_0_2_FN_SYSTEM_OFF, 0, 0, 0);
|
|
enable_interrupts();
|
|
|
|
log_err("Power off not supported on this platform\n");
|
|
return CMD_RET_FAILURE;
|
|
}
|
|
#endif
|
|
|
|
static const struct udevice_id psci_of_match[] = {
|
|
{ .compatible = "arm,psci" },
|
|
{ .compatible = "arm,psci-0.2" },
|
|
{ .compatible = "arm,psci-1.0" },
|
|
{},
|
|
};
|
|
|
|
U_BOOT_DRIVER(psci) = {
|
|
.name = DRIVER_NAME,
|
|
.id = UCLASS_FIRMWARE,
|
|
.of_match = psci_of_match,
|
|
.bind = psci_bind,
|
|
.probe = psci_probe,
|
|
};
|