@@ -5,6 +5,7 @@
obj-$(CONFIG_ARM_SCPI_PROTOCOL) += arm_scpi.o
obj-$(CONFIG_ARM_SCPI_POWER_DOMAIN) += scpi_pm_domain.o
obj-$(CONFIG_ARM_SDE_INTERFACE) += arm_sdei.o
+obj-$(CONFIG_HAVE_ARM_SMCCC) += arm_smccc_conduit.o
obj-$(CONFIG_DMI) += dmi_scan.o
obj-$(CONFIG_DMI_SYSFS) += dmi-sysfs.o
obj-$(CONFIG_EDD) += edd.o
new file mode 100644
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2020, Linaro Limited
+ */
+
+#include <linux/arm-smccc.h>
+#include <linux/device.h>
+#include <linux/of.h>
+
+static enum arm_smccc_conduit arm_smccc_1_0_conduit = SMCCC_CONDUIT_NONE;
+
+/* Helpers for nice trace when called outside a device instance */
+#define PRINT_INFO(dev, ...) \
+ do { \
+ if (dev) \
+ dev_info(dev, __VA_ARGS__); \
+ else \
+ pr_info(__VA_ARGS__); \
+ } while (0)
+
+#define PRINT_WARN(dev, ...) \
+ do { \
+ if (dev) \
+ dev_warn(dev, __VA_ARGS__); \
+ else \
+ pr_warn(__VA_ARGS__); \
+ } while (0)
+
+#define PRINT_ERROR(dev, ...) \
+ do { \
+ if (dev) \
+ dev_err(dev, __VA_ARGS__); \
+ else \
+ pr_err(__VA_ARGS__); \
+ } while (0)
+
+static const char *conduit_str(enum arm_smccc_conduit conduit)
+{
+ static const char hvc_str[] = "HVC";
+ static const char smc_str[] = "SMC";
+ static const char unknown[] = "unknown";
+
+ switch (conduit) {
+ case SMCCC_CONDUIT_HVC:
+ return hvc_str;
+ case SMCCC_CONDUIT_SMC:
+ return smc_str;
+ default:
+ return unknown;
+ }
+}
+
+static int set_conduit(struct device *dev, enum arm_smccc_conduit conduit)
+{
+ switch (conduit) {
+ case SMCCC_CONDUIT_HVC:
+ case SMCCC_CONDUIT_SMC:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (arm_smccc_1_0_conduit == SMCCC_CONDUIT_NONE) {
+ arm_smccc_1_0_conduit = conduit;
+ return 0;
+ }
+
+ if (conduit == arm_smccc_1_0_conduit)
+ return 0;
+
+ PRINT_ERROR(dev, "inconsistent conduits %u (%s) vs %u (%s)\n",
+ conduit, conduit_str(conduit),
+ arm_smccc_1_0_conduit, conduit_str(arm_smccc_1_0_conduit));
+
+ return -EINVAL;
+}
+
+static enum arm_smccc_conduit method_to_conduit(const char *method)
+{
+ if (!strcmp("hvc", method))
+ return SMCCC_CONDUIT_HVC;
+ else if (!strcmp("smc", method))
+ return SMCCC_CONDUIT_SMC;
+ else
+ return SMCCC_CONDUIT_NONE;
+}
+
+static int set_conduit_from_node(struct device *dev, struct device_node *np)
+{
+ const char *method;
+
+ PRINT_INFO(dev, "probing for conduit method from DT.\n");
+
+ if (!np)
+ return -EINVAL;
+
+ if (!of_property_read_string(np, "method", &method)) {
+ enum arm_smccc_conduit dev_conduit = method_to_conduit(method);
+
+ if (dev_conduit == SMCCC_CONDUIT_NONE) {
+ PRINT_WARN(dev, "invalid \"method\" property \"%s\"\n",
+ method);
+ return -EINVAL;
+ }
+
+ return set_conduit(dev, dev_conduit);
+ }
+
+ if (arm_smccc_1_0_conduit != SMCCC_CONDUIT_NONE)
+ return 0;
+
+ PRINT_WARN(dev, "missing \"method\" property\n");
+ return -ENXIO;
+}
+
+int devm_arm_smccc_1_0_set_conduit(struct device *dev)
+{
+ if (!dev || !dev->of_node)
+ return -EINVAL;
+
+ return set_conduit_from_node(dev, dev->of_node);
+}
+EXPORT_SYMBOL_GPL(devm_arm_smccc_1_0_set_conduit);
+
+int of_arm_smccc_1_0_set_conduit(struct device_node *np)
+{
+ if (!np)
+ return -EINVAL;
+
+ return set_conduit_from_node(NULL, np);
+}
+EXPORT_SYMBOL_GPL(of_arm_smccc_1_0_set_conduit);
+
+int arm_smccc_1_0_set_conduit(enum arm_smccc_conduit conduit)
+{
+ if (set_conduit(NULL, conduit))
+ return -EINVAL;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(arm_smccc_1_0_set_conduit);
+
+enum arm_smccc_conduit arm_smccc_1_0_get_conduit(void)
+{
+ return arm_smccc_1_0_conduit;
+}
+EXPORT_SYMBOL_GPL(arm_smccc_1_0_get_conduit);
@@ -377,5 +377,111 @@ asmlinkage void __arm_smccc_hvc(unsigned long a0, unsigned long a1,
ARM_SMCCC_OWNER_STANDARD_HYP, \
0x21)
+/* SMCCC v1.0 compliant invocation helpers */
+
+/*
+ * Like arm_smccc_1_0* but always returns SMCCC_RET_NOT_SUPPORTED.
+ * Used when the SMCCC conduit is not defined. The empty asm statement
+ * avoids compiler warnings about unused variables.
+ */
+#define __fail_smccc_1_0(...) \
+ do { \
+ __declare_args(7, __VA_ARGS__); \
+ asm ("" __constraints(7)); \
+ ___res->a0 = SMCCC_RET_NOT_SUPPORTED; \
+ } while (0)
+
+/*
+ * arm_smccc_1_0_invoke() - make an SMCCC v1.0 compliant call
+ *
+ * This is a macro taking eight source arguments and an return structure.
+ * It uses the SMCCC conduit registered during driver(s) initialization.
+ *
+ * @a0-a7: arguments passed in registers 0 to 7
+ * @res: result values from registers 0 to 3
+ *
+ * This macro will make either an HVC call or an SMC call depending on the
+ * specified SMCCC conduit. If no valid conduit is available then -1
+ * (SMCCC_RET_NOT_SUPPORTED) is returned in @res.a0.
+ *
+ * The return value provides the conduit that was used.
+ */
+#define arm_smccc_1_0_invoke(...) ({ \
+ enum arm_smccc_conduit conduit = arm_smccc_1_0_get_conduit(); \
+ switch (conduit) { \
+ case SMCCC_CONDUIT_HVC: \
+ arm_smccc_hvc(__VA_ARGS__); \
+ break; \
+ case SMCCC_CONDUIT_SMC: \
+ arm_smccc_smc(__VA_ARGS__); \
+ break; \
+ default: \
+ __fail_smccc_1_0(__VA_ARGS__); \
+ conduit = SMCCC_CONDUIT_NONE; \
+ } \
+ conduit; \
+ })
+
+struct device;
+struct device_node;
+
+#ifdef CONFIG_HAVE_ARM_SMCCC
+/**
+ * arm_smccc_1_0_get_conduit() - Return registered SMCCC conduit
+ */
+enum arm_smccc_conduit arm_smccc_1_0_get_conduit(void);
+
+/**
+ * arm_smccc_1_0_set_conduit - Register SMCCC invocation conduit
+ * @conduit: conduit to register
+ *
+ * Return 0 on success and -EINVAL on failure.
+ */
+int arm_smccc_1_0_set_conduit(enum arm_smccc_conduit);
+
+/**
+ * devm_arm_smccc_1_0_set_conduit() - Set SMCCC v1.0 conduit if found in device
+ * @dev: Device instance
+ *
+ * Set the SMCCC invocation conduit based on device node if it has a "methhod"
+ * property that defines the SMCCC conduit to be used. If it has not, check a
+ * conduit is already registered.
+ *
+ * Return 0 on success, -ENXIO if no conduit found, -EINVAL otherwise.
+ */
+int devm_arm_smccc_1_0_set_conduit(struct device *dev);
+
+/**
+ * of_arm_smccc_1_0_set_conduit() - Set SMCCC v1.0 conduit if found in FDT node
+ * @np: Node instance
+ *
+ * Set the SMCCC invocation conduit based on device node if it has a "methhod"
+ * property that defines the SMCCC conduit to be used. If it has not, check a
+ * conduit is already registered.
+ *
+ * Return 0 on success, -ENXIO if no conduit found, -EINVAL otherwise.
+ */
+int of_arm_smccc_1_0_set_conduit(struct device_node *np);
+#else
+static inline enum arm_smccc_conduit arm_smccc_1_0_get_conduit(void)
+{
+ return SMCCC_CONDUIT_NONE;
+}
+
+static inline int arm_smccc_1_0_set_conduit(enum arm_smccc_conduit);
+{
+ return -EINVAL;
+}
+
+static inline int devm_arm_smccc_1_0_set_conduit(struct device *dev);
+{
+ return -EINVAL;
+}
+
+static inline int of_arm_smccc_1_0_set_conduit(struct device_node *np);
+{
+ return -EINVAL;
+}
+#endif /* CONFIG_HAVE_ARM_SMCCC */
#endif /*__ASSEMBLY__*/
#endif /*__LINUX_ARM_SMCCC_H*/
Introduce invocation helper functions for driver that interact with firmware supporting Arm SMCCC v1.0 specification based on existing arm_smccc_1_1_*() helpers. The new functions suit device where secure world supports SMCCC v1.0 but not v1.1. This change allows devices to ensure consistency of the conduit used among drivers defining a conduit method as at runtime all devices are expected to use the very same SMCCC invocation conduit. Signed-off-by: Etienne Carriere <etienne.carriere@linaro.org> --- drivers/firmware/Makefile | 1 + drivers/firmware/arm_smccc_conduit.c | 147 +++++++++++++++++++++++++++ include/linux/arm-smccc.h | 106 +++++++++++++++++++ 3 files changed, 254 insertions(+) create mode 100644 drivers/firmware/arm_smccc_conduit.c