summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/firmware/Kconfig8
-rw-r--r--drivers/firmware/psci.c81
-rw-r--r--include/linux/arm-smccc.h16
-rw-r--r--include/linux/psci.h14
4 files changed, 118 insertions, 1 deletions
diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
index ef958b3a7a4..f10d1aaf4b6 100644
--- a/drivers/firmware/Kconfig
+++ b/drivers/firmware/Kconfig
@@ -37,4 +37,12 @@ config ZYNQMP_FIRMWARE
Say yes to enable ZynqMP firmware interface driver.
If in doubt, say N.
+config ARM_SMCCC_FEATURES
+ bool "Arm SMCCC features discovery"
+ depends on ARM_PSCI_FW
+ help
+ Discover Arm SMCCC features for which a U-Boot driver is defined. When enabled,
+ the PSCI driver is always probed and binds dirvers registered to the Arm SMCCC
+ services if any and reported as supported by the SMCCC firmware.
+
source "drivers/firmware/scmi/Kconfig"
diff --git a/drivers/firmware/psci.c b/drivers/firmware/psci.c
index f845ba67f8f..ef3e9836461 100644
--- a/drivers/firmware/psci.c
+++ b/drivers/firmware/psci.c
@@ -11,9 +11,11 @@
#include <dm.h>
#include <efi_loader.h>
#include <irq_func.h>
+#include <linker_lists.h>
#include <log.h>
#include <sysreset.h>
#include <asm/system.h>
+#include <dm/device-internal.h>
#include <dm/lists.h>
#include <linux/arm-smccc.h>
#include <linux/delay.h>
@@ -95,6 +97,76 @@ static bool psci_is_system_reset2_supported(void)
return false;
}
+static void smccc_invoke_hvc(unsigned long a0, unsigned long a1,
+ unsigned long a2, unsigned long a3,
+ unsigned long a4, unsigned long a5,
+ unsigned long a6, unsigned long a7,
+ struct arm_smccc_res *res)
+{
+ arm_smccc_hvc(a0, a1, a2, a3, a4, a5, a6, a7, res);
+}
+
+static void smccc_invoke_smc(unsigned long a0, unsigned long a1,
+ unsigned long a2, unsigned long a3,
+ unsigned long a4, unsigned long a5,
+ unsigned long a6, unsigned long a7,
+ struct arm_smccc_res *res)
+{
+ arm_smccc_smc(a0, a1, a2, a3, a4, a5, a6, a7, res);
+}
+
+static int bind_smccc_features(struct udevice *dev, int psci_method)
+{
+ struct psci_plat_data *pdata = dev_get_plat(dev);
+ struct arm_smccc_feature *feature;
+ size_t feature_cnt, n;
+
+ if (!IS_ENABLED(CONFIG_ARM_SMCCC_FEATURES))
+ return 0;
+
+ /*
+ * SMCCC features discovery invoke SMCCC standard function ID
+ * ARM_SMCCC_ARCH_FEATURES but this sequence requires that this
+ * standard ARM_SMCCC_ARCH_FEATURES function ID itself is supported.
+ * It is queried here with invoking PSCI_FEATURES known available
+ * from PSCI 1.0.
+ */
+ if (!device_is_compatible(dev, "arm,psci-1.0") ||
+ PSCI_VERSION_MAJOR(psci_0_2_get_version()) == 0)
+ return 0;
+
+ if (request_psci_features(ARM_SMCCC_ARCH_FEATURES) ==
+ PSCI_RET_NOT_SUPPORTED)
+ return 0;
+
+ if (psci_method == PSCI_METHOD_HVC)
+ pdata->invoke_fn = smccc_invoke_hvc;
+ else
+ pdata->invoke_fn = smccc_invoke_smc;
+
+ feature_cnt = ll_entry_count(struct arm_smccc_feature, arm_smccc_feature);
+ feature = ll_entry_start(struct arm_smccc_feature, arm_smccc_feature);
+
+ for (n = 0; n < feature_cnt; n++, feature++) {
+ const char *drv_name = feature->driver_name;
+ struct udevice *dev2;
+ int ret;
+
+ if (!feature->is_supported || !feature->is_supported(pdata->invoke_fn))
+ continue;
+
+ ret = device_bind_driver(dev, drv_name, drv_name, &dev2);
+ if (ret) {
+ pr_warn("%s was not bound: %d, ignore\n", drv_name, ret);
+ continue;
+ }
+
+ dev_set_parent_plat(dev2, dev_get_plat(dev));
+ }
+
+ return 0;
+}
+
static int psci_bind(struct udevice *dev)
{
/* No SYSTEM_RESET support for PSCI 0.1 */
@@ -109,6 +181,10 @@ static int psci_bind(struct udevice *dev)
pr_debug("PSCI System Reset was not bound.\n");
}
+ /* From PSCI v1.0 onward we can discover services through ARM_SMCCC_FEATURE */
+ if (IS_ENABLED(CONFIG_ARM_SMCCC_FEATURES) && device_is_compatible(dev, "arm,psci-1.0"))
+ dev_or_flags(dev, DM_FLAG_PROBE_AFTER_BIND);
+
return 0;
}
@@ -136,7 +212,7 @@ static int psci_probe(struct udevice *dev)
return -EINVAL;
}
- return 0;
+ return bind_smccc_features(dev, psci_method);
}
/**
@@ -240,4 +316,7 @@ U_BOOT_DRIVER(psci) = {
.of_match = psci_of_match,
.bind = psci_bind,
.probe = psci_probe,
+#ifdef CONFIG_ARM_SMCCC_FEATURES
+ .plat_auto = sizeof(struct psci_plat_data),
+#endif
};
diff --git a/include/linux/arm-smccc.h b/include/linux/arm-smccc.h
index 94a20c97937..e1d09884a1c 100644
--- a/include/linux/arm-smccc.h
+++ b/include/linux/arm-smccc.h
@@ -84,6 +84,22 @@ struct arm_smccc_quirk {
};
/**
+ * struct arm_smccc_feature - Driver registration data for discoverable feature
+ * @driver_name: name of the driver relate to the SMCCC feature
+ * @is_supported: callback to test if SMCCC feature is supported
+ */
+struct arm_smccc_feature {
+ const char *driver_name;
+ bool (*is_supported)(void (*invoke_fn)(unsigned long a0, unsigned long a1, unsigned long a2,
+ unsigned long a3, unsigned long a4, unsigned long a5,
+ unsigned long a6, unsigned long a7,
+ struct arm_smccc_res *res));
+};
+
+#define ARM_SMCCC_FEATURE_DRIVER(__name) \
+ ll_entry_declare(struct arm_smccc_feature, __name, arm_smccc_feature)
+
+/**
* __arm_smccc_smc() - make SMC calls
* @a0-a7: arguments passed in registers 0 to 7
* @res: result values from registers 0 to 3
diff --git a/include/linux/psci.h b/include/linux/psci.h
index c78c1079a82..03e41863432 100644
--- a/include/linux/psci.h
+++ b/include/linux/psci.h
@@ -11,6 +11,8 @@
#ifndef _UAPI_LINUX_PSCI_H
#define _UAPI_LINUX_PSCI_H
+#include <linux/arm-smccc.h>
+
/*
* PSCI v0.1 interface
*
@@ -115,6 +117,18 @@
#define PSCI_RET_DISABLED -8
#define PSCI_RET_INVALID_ADDRESS -9
+/**
+ * struct psci_plat_data - PSCI driver platform data
+ * @method: Selected invocation conduit
+ */
+struct psci_plat_data {
+ void (*invoke_fn)(unsigned long arg0, unsigned long arg1,
+ unsigned long arg2, unsigned long arg3,
+ unsigned long arg4, unsigned long arg5,
+ unsigned long arg6, unsigned long arg7,
+ struct arm_smccc_res *res);
+};
+
#ifdef CONFIG_ARM_PSCI_FW
unsigned long invoke_psci_fn(unsigned long a0, unsigned long a1,
unsigned long a2, unsigned long a3);