uboot/drivers/firmware/psci.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * Copyright (C) 2017 Masahiro Yamada <yamada.masahiro@socionext.com>
   4 *
   5 * Based on drivers/firmware/psci.c from Linux:
   6 * Copyright (C) 2015 ARM Limited
   7 */
   8
   9#include <common.h>
  10#include <command.h>
  11#include <dm.h>
  12#include <irq_func.h>
  13#include <log.h>
  14#include <dm/lists.h>
  15#include <efi_loader.h>
  16#include <sysreset.h>
  17#include <linux/delay.h>
  18#include <linux/libfdt.h>
  19#include <linux/arm-smccc.h>
  20#include <linux/errno.h>
  21#include <linux/printk.h>
  22#include <linux/psci.h>
  23#include <asm/system.h>
  24
  25#define DRIVER_NAME "psci"
  26
  27#define PSCI_METHOD_HVC 1
  28#define PSCI_METHOD_SMC 2
  29
  30/*
  31 * While a 64-bit OS can make calls with SMC32 calling conventions, for some
  32 * calls it is necessary to use SMC64 to pass or return 64-bit values.
  33 * For such calls PSCI_FN_NATIVE(version, name) will choose the appropriate
  34 * (native-width) function ID.
  35 */
  36#if defined(CONFIG_ARM64)
  37#define PSCI_FN_NATIVE(version, name)   PSCI_##version##_FN64_##name
  38#else
  39#define PSCI_FN_NATIVE(version, name)   PSCI_##version##_FN_##name
  40#endif
  41
  42#if CONFIG_IS_ENABLED(EFI_LOADER)
  43int __efi_runtime_data psci_method;
  44#else
  45int psci_method __section(".data");
  46#endif
  47
  48unsigned long __efi_runtime invoke_psci_fn
  49                (unsigned long function_id, unsigned long arg0,
  50                 unsigned long arg1, unsigned long arg2)
  51{
  52        struct arm_smccc_res res;
  53
  54        /*
  55         * In the __efi_runtime we need to avoid the switch statement. In some
  56         * cases the compiler creates lookup tables to implement switch. These
  57         * tables are not correctly relocated when SetVirtualAddressMap is
  58         * called.
  59         */
  60        if (psci_method == PSCI_METHOD_SMC)
  61                arm_smccc_smc(function_id, arg0, arg1, arg2, 0, 0, 0, 0, &res);
  62        else if (psci_method == PSCI_METHOD_HVC)
  63                arm_smccc_hvc(function_id, arg0, arg1, arg2, 0, 0, 0, 0, &res);
  64        else
  65                res.a0 = PSCI_RET_DISABLED;
  66        return res.a0;
  67}
  68
  69static int request_psci_features(u32 psci_func_id)
  70{
  71        return invoke_psci_fn(PSCI_1_0_FN_PSCI_FEATURES,
  72                              psci_func_id, 0, 0);
  73}
  74
  75static u32 psci_0_2_get_version(void)
  76{
  77        return invoke_psci_fn(PSCI_0_2_FN_PSCI_VERSION, 0, 0, 0);
  78}
  79
  80static bool psci_is_system_reset2_supported(void)
  81{
  82        int ret;
  83        u32 ver;
  84
  85        ver = psci_0_2_get_version();
  86
  87        if (PSCI_VERSION_MAJOR(ver) >= 1) {
  88                ret = request_psci_features(PSCI_FN_NATIVE(1_1,
  89                                                           SYSTEM_RESET2));
  90
  91                if (ret != PSCI_RET_NOT_SUPPORTED)
  92                        return true;
  93        }
  94
  95        return false;
  96}
  97
  98static int psci_bind(struct udevice *dev)
  99{
 100        /* No SYSTEM_RESET support for PSCI 0.1 */
 101        if (device_is_compatible(dev, "arm,psci-0.2") ||
 102            device_is_compatible(dev, "arm,psci-1.0")) {
 103                int ret;
 104
 105                /* bind psci-sysreset optionally */
 106                ret = device_bind_driver(dev, "psci-sysreset", "psci-sysreset",
 107                                         NULL);
 108                if (ret)
 109                        pr_debug("PSCI System Reset was not bound.\n");
 110        }
 111
 112        return 0;
 113}
 114
 115static int psci_probe(struct udevice *dev)
 116{
 117        const char *method;
 118
 119#if defined(CONFIG_ARM64)
 120        if (current_el() == 3)
 121                return -EINVAL;
 122#endif
 123
 124        method = ofnode_read_string(dev_ofnode(dev), "method");
 125        if (!method) {
 126                pr_warn("missing \"method\" property\n");
 127                return -ENXIO;
 128        }
 129
 130        if (!strcmp("hvc", method)) {
 131                psci_method = PSCI_METHOD_HVC;
 132        } else if (!strcmp("smc", method)) {
 133                psci_method = PSCI_METHOD_SMC;
 134        } else {
 135                pr_warn("invalid \"method\" property: %s\n", method);
 136                return -EINVAL;
 137        }
 138
 139        return 0;
 140}
 141
 142/**
 143 * void do_psci_probe() - probe PSCI firmware driver
 144 *
 145 * Ensure that psci_method is initialized.
 146 */
 147static void __maybe_unused do_psci_probe(void)
 148{
 149        struct udevice *dev;
 150
 151        uclass_get_device_by_name(UCLASS_FIRMWARE, DRIVER_NAME, &dev);
 152}
 153
 154#if IS_ENABLED(CONFIG_EFI_LOADER) && IS_ENABLED(CONFIG_PSCI_RESET)
 155efi_status_t efi_reset_system_init(void)
 156{
 157        do_psci_probe();
 158        return EFI_SUCCESS;
 159}
 160
 161void __efi_runtime EFIAPI efi_reset_system(enum efi_reset_type reset_type,
 162                                           efi_status_t reset_status,
 163                                           unsigned long data_size,
 164                                           void *reset_data)
 165{
 166        if (reset_type == EFI_RESET_COLD ||
 167            reset_type == EFI_RESET_WARM ||
 168            reset_type == EFI_RESET_PLATFORM_SPECIFIC) {
 169                invoke_psci_fn(PSCI_0_2_FN_SYSTEM_RESET, 0, 0, 0);
 170        } else if (reset_type == EFI_RESET_SHUTDOWN) {
 171                invoke_psci_fn(PSCI_0_2_FN_SYSTEM_OFF, 0, 0, 0);
 172        }
 173        while (1)
 174                ;
 175}
 176#endif /* IS_ENABLED(CONFIG_EFI_LOADER) && IS_ENABLED(CONFIG_PSCI_RESET) */
 177
 178#ifdef CONFIG_PSCI_RESET
 179void reset_misc(void)
 180{
 181        do_psci_probe();
 182        invoke_psci_fn(PSCI_0_2_FN_SYSTEM_RESET, 0, 0, 0);
 183}
 184#endif /* CONFIG_PSCI_RESET */
 185
 186void psci_sys_reset(u32 type)
 187{
 188        bool reset2_supported;
 189
 190        do_psci_probe();
 191
 192        reset2_supported = psci_is_system_reset2_supported();
 193
 194        if (type == SYSRESET_WARM && reset2_supported) {
 195                /*
 196                 * reset_type[31] = 0 (architectural)
 197                 * reset_type[30:0] = 0 (SYSTEM_WARM_RESET)
 198                 * cookie = 0 (ignored by the implementation)
 199                 */
 200                invoke_psci_fn(PSCI_FN_NATIVE(1_1, SYSTEM_RESET2), 0, 0, 0);
 201        } else {
 202                invoke_psci_fn(PSCI_0_2_FN_SYSTEM_RESET, 0, 0, 0);
 203        }
 204}
 205
 206void psci_sys_poweroff(void)
 207{
 208        do_psci_probe();
 209
 210        invoke_psci_fn(PSCI_0_2_FN_SYSTEM_OFF, 0, 0, 0);
 211}
 212
 213#if IS_ENABLED(CONFIG_CMD_POWEROFF) && !IS_ENABLED(CONFIG_SYSRESET_CMD_POWEROFF)
 214int do_poweroff(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
 215{
 216        do_psci_probe();
 217
 218        puts("poweroff ...\n");
 219        udelay(50000); /* wait 50 ms */
 220
 221        disable_interrupts();
 222        invoke_psci_fn(PSCI_0_2_FN_SYSTEM_OFF, 0, 0, 0);
 223        enable_interrupts();
 224
 225        log_err("Power off not supported on this platform\n");
 226        return CMD_RET_FAILURE;
 227}
 228#endif
 229
 230static const struct udevice_id psci_of_match[] = {
 231        { .compatible = "arm,psci" },
 232        { .compatible = "arm,psci-0.2" },
 233        { .compatible = "arm,psci-1.0" },
 234        {},
 235};
 236
 237U_BOOT_DRIVER(psci) = {
 238        .name = DRIVER_NAME,
 239        .id = UCLASS_FIRMWARE,
 240        .of_match = psci_of_match,
 241        .bind = psci_bind,
 242        .probe = psci_probe,
 243};
 244