diff options
Diffstat (limited to 'drivers/firmware/smccc')
-rw-r--r-- | drivers/firmware/smccc/Kconfig | 25 | ||||
-rw-r--r-- | drivers/firmware/smccc/Makefile | 4 | ||||
-rw-r--r-- | drivers/firmware/smccc/kvm_guest.c | 51 | ||||
-rw-r--r-- | drivers/firmware/smccc/smccc.c | 87 | ||||
-rw-r--r-- | drivers/firmware/smccc/soc_id.c | 95 |
5 files changed, 262 insertions, 0 deletions
diff --git a/drivers/firmware/smccc/Kconfig b/drivers/firmware/smccc/Kconfig new file mode 100644 index 0000000000..15e7466179 --- /dev/null +++ b/drivers/firmware/smccc/Kconfig @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: GPL-2.0-only +config HAVE_ARM_SMCCC + bool + help + Include support for the Secure Monitor Call (SMC) and Hypervisor + Call (HVC) instructions on Armv7 and above architectures. + +config HAVE_ARM_SMCCC_DISCOVERY + bool + depends on ARM_PSCI_FW + default y + help + SMCCC v1.0 lacked discoverability and hence PSCI v1.0 was updated + to add SMCCC discovery mechanism though the PSCI firmware + implementation of PSCI_FEATURES(SMCCC_VERSION) which returns + success on firmware compliant to SMCCC v1.1 and above. + +config ARM_SMCCC_SOC_ID + bool "SoC bus device for the ARM SMCCC SOC_ID" + depends on HAVE_ARM_SMCCC_DISCOVERY + default y + select SOC_BUS + help + Include support for the SoC bus on the ARM SMCCC firmware based + platforms providing some sysfs information about the SoC variant. diff --git a/drivers/firmware/smccc/Makefile b/drivers/firmware/smccc/Makefile new file mode 100644 index 0000000000..40d19144a8 --- /dev/null +++ b/drivers/firmware/smccc/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 +# +obj-$(CONFIG_HAVE_ARM_SMCCC_DISCOVERY) += smccc.o kvm_guest.o +obj-$(CONFIG_ARM_SMCCC_SOC_ID) += soc_id.o diff --git a/drivers/firmware/smccc/kvm_guest.c b/drivers/firmware/smccc/kvm_guest.c new file mode 100644 index 0000000000..89a68e7eea --- /dev/null +++ b/drivers/firmware/smccc/kvm_guest.c @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-2.0 + +#define pr_fmt(fmt) "smccc: KVM: " fmt + +#include <linux/arm-smccc.h> +#include <linux/bitmap.h> +#include <linux/cache.h> +#include <linux/kernel.h> +#include <linux/string.h> + +#include <asm/hypervisor.h> + +static DECLARE_BITMAP(__kvm_arm_hyp_services, ARM_SMCCC_KVM_NUM_FUNCS) __ro_after_init = { }; + +void __init kvm_init_hyp_services(void) +{ + struct arm_smccc_res res; + u32 val[4]; + + if (arm_smccc_1_1_get_conduit() != SMCCC_CONDUIT_HVC) + return; + + arm_smccc_1_1_invoke(ARM_SMCCC_VENDOR_HYP_CALL_UID_FUNC_ID, &res); + if (res.a0 != ARM_SMCCC_VENDOR_HYP_UID_KVM_REG_0 || + res.a1 != ARM_SMCCC_VENDOR_HYP_UID_KVM_REG_1 || + res.a2 != ARM_SMCCC_VENDOR_HYP_UID_KVM_REG_2 || + res.a3 != ARM_SMCCC_VENDOR_HYP_UID_KVM_REG_3) + return; + + memset(&res, 0, sizeof(res)); + arm_smccc_1_1_invoke(ARM_SMCCC_VENDOR_HYP_KVM_FEATURES_FUNC_ID, &res); + + val[0] = lower_32_bits(res.a0); + val[1] = lower_32_bits(res.a1); + val[2] = lower_32_bits(res.a2); + val[3] = lower_32_bits(res.a3); + + bitmap_from_arr32(__kvm_arm_hyp_services, val, ARM_SMCCC_KVM_NUM_FUNCS); + + pr_info("hypervisor services detected (0x%08lx 0x%08lx 0x%08lx 0x%08lx)\n", + res.a3, res.a2, res.a1, res.a0); +} + +bool kvm_arm_hyp_service_available(u32 func_id) +{ + if (func_id >= ARM_SMCCC_KVM_NUM_FUNCS) + return false; + + return test_bit(func_id, __kvm_arm_hyp_services); +} +EXPORT_SYMBOL_GPL(kvm_arm_hyp_service_available); diff --git a/drivers/firmware/smccc/smccc.c b/drivers/firmware/smccc/smccc.c new file mode 100644 index 0000000000..db818f9dcb --- /dev/null +++ b/drivers/firmware/smccc/smccc.c @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2020 Arm Limited + */ + +#define pr_fmt(fmt) "smccc: " fmt + +#include <linux/cache.h> +#include <linux/init.h> +#include <linux/arm-smccc.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <asm/archrandom.h> + +static u32 smccc_version = ARM_SMCCC_VERSION_1_0; +static enum arm_smccc_conduit smccc_conduit = SMCCC_CONDUIT_NONE; + +bool __ro_after_init smccc_trng_available = false; +u64 __ro_after_init smccc_has_sve_hint = false; +s32 __ro_after_init smccc_soc_id_version = SMCCC_RET_NOT_SUPPORTED; +s32 __ro_after_init smccc_soc_id_revision = SMCCC_RET_NOT_SUPPORTED; + +void __init arm_smccc_version_init(u32 version, enum arm_smccc_conduit conduit) +{ + struct arm_smccc_res res; + + smccc_version = version; + smccc_conduit = conduit; + + smccc_trng_available = smccc_probe_trng(); + if (IS_ENABLED(CONFIG_ARM64_SVE) && + smccc_version >= ARM_SMCCC_VERSION_1_3) + smccc_has_sve_hint = true; + + if ((smccc_version >= ARM_SMCCC_VERSION_1_2) && + (smccc_conduit != SMCCC_CONDUIT_NONE)) { + arm_smccc_1_1_invoke(ARM_SMCCC_ARCH_FEATURES_FUNC_ID, + ARM_SMCCC_ARCH_SOC_ID, &res); + if ((s32)res.a0 >= 0) { + arm_smccc_1_1_invoke(ARM_SMCCC_ARCH_SOC_ID, 0, &res); + smccc_soc_id_version = (s32)res.a0; + arm_smccc_1_1_invoke(ARM_SMCCC_ARCH_SOC_ID, 1, &res); + smccc_soc_id_revision = (s32)res.a0; + } + } +} + +enum arm_smccc_conduit arm_smccc_1_1_get_conduit(void) +{ + if (smccc_version < ARM_SMCCC_VERSION_1_1) + return SMCCC_CONDUIT_NONE; + + return smccc_conduit; +} +EXPORT_SYMBOL_GPL(arm_smccc_1_1_get_conduit); + +u32 arm_smccc_get_version(void) +{ + return smccc_version; +} +EXPORT_SYMBOL_GPL(arm_smccc_get_version); + +s32 arm_smccc_get_soc_id_version(void) +{ + return smccc_soc_id_version; +} + +s32 arm_smccc_get_soc_id_revision(void) +{ + return smccc_soc_id_revision; +} + +static int __init smccc_devices_init(void) +{ + struct platform_device *pdev; + + if (smccc_trng_available) { + pdev = platform_device_register_simple("smccc_trng", -1, + NULL, 0); + if (IS_ERR(pdev)) + pr_err("smccc_trng: could not register device: %ld\n", + PTR_ERR(pdev)); + } + + return 0; +} +device_initcall(smccc_devices_init); diff --git a/drivers/firmware/smccc/soc_id.c b/drivers/firmware/smccc/soc_id.c new file mode 100644 index 0000000000..1990263fbb --- /dev/null +++ b/drivers/firmware/smccc/soc_id.c @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2020 Arm Limited + */ + +#define pr_fmt(fmt) "SMCCC: SOC_ID: " fmt + +#include <linux/arm-smccc.h> +#include <linux/bitfield.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/sys_soc.h> + +#define SMCCC_SOC_ID_JEP106_BANK_IDX_MASK GENMASK(30, 24) +/* + * As per the SMC Calling Convention specification v1.2 (ARM DEN 0028C) + * Section 7.4 SMCCC_ARCH_SOC_ID bits[23:16] are JEP-106 identification + * code with parity bit for the SiP. We can drop the parity bit. + */ +#define SMCCC_SOC_ID_JEP106_ID_CODE_MASK GENMASK(22, 16) +#define SMCCC_SOC_ID_IMP_DEF_SOC_ID_MASK GENMASK(15, 0) + +#define JEP106_BANK_CONT_CODE(x) \ + (u8)(FIELD_GET(SMCCC_SOC_ID_JEP106_BANK_IDX_MASK, (x))) +#define JEP106_ID_CODE(x) \ + (u8)(FIELD_GET(SMCCC_SOC_ID_JEP106_ID_CODE_MASK, (x))) +#define IMP_DEF_SOC_ID(x) \ + (u16)(FIELD_GET(SMCCC_SOC_ID_IMP_DEF_SOC_ID_MASK, (x))) + +static struct soc_device *soc_dev; +static struct soc_device_attribute *soc_dev_attr; + +static int __init smccc_soc_init(void) +{ + int soc_id_rev, soc_id_version; + static char soc_id_str[20], soc_id_rev_str[12]; + static char soc_id_jep106_id_str[12]; + + if (arm_smccc_get_version() < ARM_SMCCC_VERSION_1_2) + return 0; + + soc_id_version = arm_smccc_get_soc_id_version(); + if (soc_id_version == SMCCC_RET_NOT_SUPPORTED) { + pr_info("ARCH_SOC_ID not implemented, skipping ....\n"); + return 0; + } + + if (soc_id_version < 0) { + pr_err("Invalid SoC Version: %x\n", soc_id_version); + return -EINVAL; + } + + soc_id_rev = arm_smccc_get_soc_id_revision(); + if (soc_id_rev < 0) { + pr_err("Invalid SoC Revision: %x\n", soc_id_rev); + return -EINVAL; + } + + soc_dev_attr = kzalloc(sizeof(*soc_dev_attr), GFP_KERNEL); + if (!soc_dev_attr) + return -ENOMEM; + + sprintf(soc_id_rev_str, "0x%08x", soc_id_rev); + sprintf(soc_id_jep106_id_str, "jep106:%02x%02x", + JEP106_BANK_CONT_CODE(soc_id_version), + JEP106_ID_CODE(soc_id_version)); + sprintf(soc_id_str, "%s:%04x", soc_id_jep106_id_str, + IMP_DEF_SOC_ID(soc_id_version)); + + soc_dev_attr->soc_id = soc_id_str; + soc_dev_attr->revision = soc_id_rev_str; + soc_dev_attr->family = soc_id_jep106_id_str; + + soc_dev = soc_device_register(soc_dev_attr); + if (IS_ERR(soc_dev)) { + kfree(soc_dev_attr); + return PTR_ERR(soc_dev); + } + + pr_info("ID = %s Revision = %s\n", soc_dev_attr->soc_id, + soc_dev_attr->revision); + + return 0; +} +module_init(smccc_soc_init); + +static void __exit smccc_soc_exit(void) +{ + if (soc_dev) + soc_device_unregister(soc_dev); + kfree(soc_dev_attr); +} +module_exit(smccc_soc_exit); |