u-boot/drivers/firmware/psci.c
Heinrich Schuchardt 81ea00838c 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>
2018-12-02 21:59:37 +01:00

163 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,
};