/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * xen/arch/arm/psci.c * * PSCI host support * * Andre Przywara * Copyright (c) 2013 Linaro Limited. */ #include #include #include #include #include #include #include /* * While a 64-bit OS can make calls with SMC32 calling conventions, for * some calls it is necessary to use SMC64 to pass or return 64-bit values. * For such calls PSCI_0_2_FN_NATIVE(x) will choose the appropriate * (native-width) function ID. */ #ifdef CONFIG_ARM_64 #define PSCI_0_2_FN_NATIVE(name) PSCI_0_2_FN64_##name #else #define PSCI_0_2_FN_NATIVE(name) PSCI_0_2_FN32_##name #endif uint32_t psci_ver; uint32_t smccc_ver; static uint32_t psci_cpu_on_nr; #define PSCI_RET(res) ((int32_t)(res).a0) int call_psci_cpu_on(int cpu) { struct arm_smccc_res res; arm_smccc_smc(psci_cpu_on_nr, cpu_logical_map(cpu), __pa(init_secondary), &res); return PSCI_RET(res); } void call_psci_cpu_off(void) { if ( psci_ver > PSCI_VERSION(0, 1) ) { struct arm_smccc_res res; /* If successfull the PSCI cpu_off call doesn't return */ arm_smccc_smc(PSCI_0_2_FN32_CPU_OFF, &res); panic("PSCI cpu off failed for CPU%d err=%d\n", smp_processor_id(), PSCI_RET(res)); } } void call_psci_system_off(void) { if ( psci_ver > PSCI_VERSION(0, 1) ) arm_smccc_smc(PSCI_0_2_FN32_SYSTEM_OFF, NULL); } void call_psci_system_reset(void) { if ( psci_ver > PSCI_VERSION(0, 1) ) arm_smccc_smc(PSCI_0_2_FN32_SYSTEM_RESET, NULL); } static int __init psci_features(uint32_t psci_func_id) { struct arm_smccc_res res; if ( psci_ver < PSCI_VERSION(1, 0) ) return PSCI_NOT_SUPPORTED; arm_smccc_smc(PSCI_1_0_FN32_PSCI_FEATURES, psci_func_id, &res); return PSCI_RET(res); } static int __init psci_is_smc_method(const struct dt_device_node *psci) { int ret; const char *prop_str; ret = dt_property_read_string(psci, "method", &prop_str); if ( ret ) { printk("/psci node does not provide a method (%d)\n", ret); return -EINVAL; } /* Since Xen runs in HYP all of the time, it does not make sense to * let it call into HYP for PSCI handling, since the handler just * won't be there. So bail out with an error if "smc" is not used. */ if ( strcmp(prop_str, "smc") ) { printk("/psci method must be smc, but is: \"%s\"\n", prop_str); return -EINVAL; } return 0; } static void __init psci_init_smccc(void) { /* PSCI is using at least SMCCC 1.0 calling convention. */ smccc_ver = ARM_SMCCC_VERSION_1_0; if ( psci_features(ARM_SMCCC_VERSION_FID) != PSCI_NOT_SUPPORTED ) { struct arm_smccc_res res; arm_smccc_smc(ARM_SMCCC_VERSION_FID, &res); if ( PSCI_RET(res) != ARM_SMCCC_NOT_SUPPORTED ) smccc_ver = PSCI_RET(res); } if ( smccc_ver >= SMCCC_VERSION(1, 1) ) cpus_set_cap(ARM_SMCCC_1_1); printk(XENLOG_INFO "Using SMC Calling Convention v%u.%u\n", SMCCC_VERSION_MAJOR(smccc_ver), SMCCC_VERSION_MINOR(smccc_ver)); } static int __init psci_init_0_1(void) { int ret; const struct dt_device_node *psci; if ( !acpi_disabled ) { printk("PSCI 0.1 is not supported when using ACPI\n"); return -EINVAL; } psci = dt_find_compatible_node(NULL, NULL, "arm,psci"); if ( !psci ) return -EOPNOTSUPP; ret = psci_is_smc_method(psci); if ( ret ) return -EINVAL; if ( !dt_property_read_u32(psci, "cpu_on", &psci_cpu_on_nr) ) { printk("/psci node is missing the \"cpu_on\" property\n"); return -ENOENT; } psci_ver = PSCI_VERSION(0, 1); return 0; } static int __init psci_init_0_2(void) { static const struct dt_device_match psci_ids[] __initconst = { DT_MATCH_COMPATIBLE("arm,psci-0.2"), DT_MATCH_COMPATIBLE("arm,psci-1.0"), { /* sentinel */ }, }; int ret; struct arm_smccc_res res; if ( acpi_disabled ) { const struct dt_device_node *psci; psci = dt_find_matching_node(NULL, psci_ids); if ( !psci ) return -EOPNOTSUPP; ret = psci_is_smc_method(psci); if ( ret ) return -EINVAL; } else { if ( acpi_psci_hvc_present() ) { printk("PSCI conduit must be SMC, but is HVC\n"); return -EINVAL; } } arm_smccc_smc(PSCI_0_2_FN32_PSCI_VERSION, &res); psci_ver = PSCI_RET(res); /* For the moment, we only support PSCI 0.2 and PSCI 1.x */ if ( psci_ver != PSCI_VERSION(0, 2) && PSCI_VERSION_MAJOR(psci_ver) != 1 ) { printk("Error: Unrecognized PSCI version %u.%u\n", PSCI_VERSION_MAJOR(psci_ver), PSCI_VERSION_MINOR(psci_ver)); return -EOPNOTSUPP; } psci_cpu_on_nr = PSCI_0_2_FN_NATIVE(CPU_ON); return 0; } int __init psci_init(void) { int ret; if ( !acpi_disabled && !acpi_psci_present() ) return -EOPNOTSUPP; ret = psci_init_0_2(); if ( ret ) ret = psci_init_0_1(); if ( ret ) return ret; psci_init_smccc(); printk(XENLOG_INFO "Using PSCI v%u.%u\n", PSCI_VERSION_MAJOR(psci_ver), PSCI_VERSION_MINOR(psci_ver)); return 0; } /* * Local variables: * mode: C * c-file-style: "BSD" * c-basic-offset: 4 * indent-tabs-mode: nil * End: */