xref: /openbmc/linux/drivers/firmware/qcom_scm-smc.c (revision f3c3091b98d5d52df40aaf27f11530701d02ac56)
19a434ceeSElliot Berman // SPDX-License-Identifier: GPL-2.0-only
29a434ceeSElliot Berman /* Copyright (c) 2015,2019 The Linux Foundation. All rights reserved.
39a434ceeSElliot Berman  */
49a434ceeSElliot Berman 
59a434ceeSElliot Berman #include <linux/io.h>
69a434ceeSElliot Berman #include <linux/errno.h>
79a434ceeSElliot Berman #include <linux/delay.h>
89a434ceeSElliot Berman #include <linux/mutex.h>
99a434ceeSElliot Berman #include <linux/slab.h>
109a434ceeSElliot Berman #include <linux/types.h>
113bf90ecaSElliot Berman #include <linux/firmware/qcom/qcom_scm.h>
129a434ceeSElliot Berman #include <linux/arm-smccc.h>
139a434ceeSElliot Berman #include <linux/dma-mapping.h>
149a434ceeSElliot Berman 
159a434ceeSElliot Berman #include "qcom_scm.h"
169a434ceeSElliot Berman 
179a434ceeSElliot Berman /**
189a434ceeSElliot Berman  * struct arm_smccc_args
199a434ceeSElliot Berman  * @args:	The array of values used in registers in smc instruction
209a434ceeSElliot Berman  */
219a434ceeSElliot Berman struct arm_smccc_args {
229a434ceeSElliot Berman 	unsigned long args[8];
239a434ceeSElliot Berman };
249a434ceeSElliot Berman 
259a434ceeSElliot Berman static DEFINE_MUTEX(qcom_scm_lock);
269a434ceeSElliot Berman 
279a434ceeSElliot Berman #define QCOM_SCM_EBUSY_WAIT_MS 30
289a434ceeSElliot Berman #define QCOM_SCM_EBUSY_MAX_RETRY 20
299a434ceeSElliot Berman 
309a434ceeSElliot Berman #define SCM_SMC_N_REG_ARGS	4
319a434ceeSElliot Berman #define SCM_SMC_FIRST_EXT_IDX	(SCM_SMC_N_REG_ARGS - 1)
329a434ceeSElliot Berman #define SCM_SMC_N_EXT_ARGS	(MAX_QCOM_SCM_ARGS - SCM_SMC_N_REG_ARGS + 1)
339a434ceeSElliot Berman #define SCM_SMC_FIRST_REG_IDX	2
349a434ceeSElliot Berman #define SCM_SMC_LAST_REG_IDX	(SCM_SMC_FIRST_REG_IDX + SCM_SMC_N_REG_ARGS - 1)
359a434ceeSElliot Berman 
__scm_smc_do_quirk(const struct arm_smccc_args * smc,struct arm_smccc_res * res)369a434ceeSElliot Berman static void __scm_smc_do_quirk(const struct arm_smccc_args *smc,
379a434ceeSElliot Berman 			       struct arm_smccc_res *res)
389a434ceeSElliot Berman {
399a434ceeSElliot Berman 	unsigned long a0 = smc->args[0];
409a434ceeSElliot Berman 	struct arm_smccc_quirk quirk = { .id = ARM_SMCCC_QUIRK_QCOM_A6 };
419a434ceeSElliot Berman 
429a434ceeSElliot Berman 	quirk.state.a6 = 0;
439a434ceeSElliot Berman 
449a434ceeSElliot Berman 	do {
459a434ceeSElliot Berman 		arm_smccc_smc_quirk(a0, smc->args[1], smc->args[2],
469a434ceeSElliot Berman 				    smc->args[3], smc->args[4], smc->args[5],
479a434ceeSElliot Berman 				    quirk.state.a6, smc->args[7], res, &quirk);
489a434ceeSElliot Berman 
499a434ceeSElliot Berman 		if (res->a0 == QCOM_SCM_INTERRUPTED)
509a434ceeSElliot Berman 			a0 = res->a0;
519a434ceeSElliot Berman 
529a434ceeSElliot Berman 	} while (res->a0 == QCOM_SCM_INTERRUPTED);
539a434ceeSElliot Berman }
549a434ceeSElliot Berman 
fill_wq_resume_args(struct arm_smccc_args * resume,u32 smc_call_ctx)556bf32599SGuru Das Srinagesh static void fill_wq_resume_args(struct arm_smccc_args *resume, u32 smc_call_ctx)
566bf32599SGuru Das Srinagesh {
576bf32599SGuru Das Srinagesh 	memset(resume->args, 0, sizeof(resume->args[0]) * ARRAY_SIZE(resume->args));
586bf32599SGuru Das Srinagesh 
596bf32599SGuru Das Srinagesh 	resume->args[0] = ARM_SMCCC_CALL_VAL(ARM_SMCCC_STD_CALL,
606bf32599SGuru Das Srinagesh 					ARM_SMCCC_SMC_64, ARM_SMCCC_OWNER_SIP,
616bf32599SGuru Das Srinagesh 					SCM_SMC_FNID(QCOM_SCM_SVC_WAITQ, QCOM_SCM_WAITQ_RESUME));
626bf32599SGuru Das Srinagesh 
636bf32599SGuru Das Srinagesh 	resume->args[1] = QCOM_SCM_ARGS(1);
646bf32599SGuru Das Srinagesh 
656bf32599SGuru Das Srinagesh 	resume->args[2] = smc_call_ctx;
666bf32599SGuru Das Srinagesh }
676bf32599SGuru Das Srinagesh 
scm_get_wq_ctx(u32 * wq_ctx,u32 * flags,u32 * more_pending)686bf32599SGuru Das Srinagesh int scm_get_wq_ctx(u32 *wq_ctx, u32 *flags, u32 *more_pending)
696bf32599SGuru Das Srinagesh {
706bf32599SGuru Das Srinagesh 	int ret;
716bf32599SGuru Das Srinagesh 	struct arm_smccc_res get_wq_res;
726bf32599SGuru Das Srinagesh 	struct arm_smccc_args get_wq_ctx = {0};
736bf32599SGuru Das Srinagesh 
74*cdf7efe4SMurali Nalajala 	get_wq_ctx.args[0] = ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL,
756bf32599SGuru Das Srinagesh 				ARM_SMCCC_SMC_64, ARM_SMCCC_OWNER_SIP,
766bf32599SGuru Das Srinagesh 				SCM_SMC_FNID(QCOM_SCM_SVC_WAITQ, QCOM_SCM_WAITQ_GET_WQ_CTX));
776bf32599SGuru Das Srinagesh 
786bf32599SGuru Das Srinagesh 	/* Guaranteed to return only success or error, no WAITQ_* */
796bf32599SGuru Das Srinagesh 	__scm_smc_do_quirk(&get_wq_ctx, &get_wq_res);
806bf32599SGuru Das Srinagesh 	ret = get_wq_res.a0;
816bf32599SGuru Das Srinagesh 	if (ret)
826bf32599SGuru Das Srinagesh 		return ret;
836bf32599SGuru Das Srinagesh 
846bf32599SGuru Das Srinagesh 	*wq_ctx = get_wq_res.a1;
856bf32599SGuru Das Srinagesh 	*flags  = get_wq_res.a2;
866bf32599SGuru Das Srinagesh 	*more_pending = get_wq_res.a3;
876bf32599SGuru Das Srinagesh 
886bf32599SGuru Das Srinagesh 	return 0;
896bf32599SGuru Das Srinagesh }
906bf32599SGuru Das Srinagesh 
__scm_smc_do_quirk_handle_waitq(struct device * dev,struct arm_smccc_args * waitq,struct arm_smccc_res * res)916bf32599SGuru Das Srinagesh static int __scm_smc_do_quirk_handle_waitq(struct device *dev, struct arm_smccc_args *waitq,
926bf32599SGuru Das Srinagesh 					   struct arm_smccc_res *res)
936bf32599SGuru Das Srinagesh {
946bf32599SGuru Das Srinagesh 	int ret;
956bf32599SGuru Das Srinagesh 	u32 wq_ctx, smc_call_ctx;
966bf32599SGuru Das Srinagesh 	struct arm_smccc_args resume;
976bf32599SGuru Das Srinagesh 	struct arm_smccc_args *smc = waitq;
986bf32599SGuru Das Srinagesh 
996bf32599SGuru Das Srinagesh 	do {
1006bf32599SGuru Das Srinagesh 		__scm_smc_do_quirk(smc, res);
1016bf32599SGuru Das Srinagesh 
1026bf32599SGuru Das Srinagesh 		if (res->a0 == QCOM_SCM_WAITQ_SLEEP) {
1036bf32599SGuru Das Srinagesh 			wq_ctx = res->a1;
1046bf32599SGuru Das Srinagesh 			smc_call_ctx = res->a2;
1056bf32599SGuru Das Srinagesh 
1066bf32599SGuru Das Srinagesh 			ret = qcom_scm_wait_for_wq_completion(wq_ctx);
1076bf32599SGuru Das Srinagesh 			if (ret)
1086bf32599SGuru Das Srinagesh 				return ret;
1096bf32599SGuru Das Srinagesh 
1106bf32599SGuru Das Srinagesh 			fill_wq_resume_args(&resume, smc_call_ctx);
1116bf32599SGuru Das Srinagesh 			smc = &resume;
1126bf32599SGuru Das Srinagesh 		}
1136bf32599SGuru Das Srinagesh 	} while (res->a0 == QCOM_SCM_WAITQ_SLEEP);
1146bf32599SGuru Das Srinagesh 
1156bf32599SGuru Das Srinagesh 	return 0;
1166bf32599SGuru Das Srinagesh }
1176bf32599SGuru Das Srinagesh 
__scm_smc_do(struct device * dev,struct arm_smccc_args * smc,struct arm_smccc_res * res,bool atomic)1186bf32599SGuru Das Srinagesh static int __scm_smc_do(struct device *dev, struct arm_smccc_args *smc,
1199a434ceeSElliot Berman 			struct arm_smccc_res *res, bool atomic)
1209a434ceeSElliot Berman {
1216bf32599SGuru Das Srinagesh 	int ret, retry_count = 0;
1229a434ceeSElliot Berman 
1239a434ceeSElliot Berman 	if (atomic) {
1249a434ceeSElliot Berman 		__scm_smc_do_quirk(smc, res);
1256bf32599SGuru Das Srinagesh 		return 0;
1269a434ceeSElliot Berman 	}
1279a434ceeSElliot Berman 
1289a434ceeSElliot Berman 	do {
1299a434ceeSElliot Berman 		mutex_lock(&qcom_scm_lock);
1309a434ceeSElliot Berman 
1316bf32599SGuru Das Srinagesh 		ret = __scm_smc_do_quirk_handle_waitq(dev, smc, res);
1329a434ceeSElliot Berman 
1339a434ceeSElliot Berman 		mutex_unlock(&qcom_scm_lock);
1349a434ceeSElliot Berman 
1356bf32599SGuru Das Srinagesh 		if (ret)
1366bf32599SGuru Das Srinagesh 			return ret;
1376bf32599SGuru Das Srinagesh 
1389a434ceeSElliot Berman 		if (res->a0 == QCOM_SCM_V2_EBUSY) {
1399a434ceeSElliot Berman 			if (retry_count++ > QCOM_SCM_EBUSY_MAX_RETRY)
1409a434ceeSElliot Berman 				break;
1419a434ceeSElliot Berman 			msleep(QCOM_SCM_EBUSY_WAIT_MS);
1429a434ceeSElliot Berman 		}
1439a434ceeSElliot Berman 	}  while (res->a0 == QCOM_SCM_V2_EBUSY);
1446bf32599SGuru Das Srinagesh 
1456bf32599SGuru Das Srinagesh 	return 0;
1469a434ceeSElliot Berman }
1479a434ceeSElliot Berman 
148f6ea568fSStephen Boyd 
__scm_smc_call(struct device * dev,const struct qcom_scm_desc * desc,enum qcom_scm_convention qcom_convention,struct qcom_scm_res * res,bool atomic)149f6ea568fSStephen Boyd int __scm_smc_call(struct device *dev, const struct qcom_scm_desc *desc,
150f6ea568fSStephen Boyd 		   enum qcom_scm_convention qcom_convention,
1519a434ceeSElliot Berman 		   struct qcom_scm_res *res, bool atomic)
1529a434ceeSElliot Berman {
1539a434ceeSElliot Berman 	int arglen = desc->arginfo & 0xf;
1546bf32599SGuru Das Srinagesh 	int i, ret;
1559a434ceeSElliot Berman 	dma_addr_t args_phys = 0;
1569a434ceeSElliot Berman 	void *args_virt = NULL;
1579a434ceeSElliot Berman 	size_t alloc_len;
1589a434ceeSElliot Berman 	gfp_t flag = atomic ? GFP_ATOMIC : GFP_KERNEL;
1599a434ceeSElliot Berman 	u32 smccc_call_type = atomic ? ARM_SMCCC_FAST_CALL : ARM_SMCCC_STD_CALL;
160f6ea568fSStephen Boyd 	u32 qcom_smccc_convention = (qcom_convention == SMC_CONVENTION_ARM_32) ?
1619a434ceeSElliot Berman 				    ARM_SMCCC_SMC_32 : ARM_SMCCC_SMC_64;
1629a434ceeSElliot Berman 	struct arm_smccc_res smc_res;
1639a434ceeSElliot Berman 	struct arm_smccc_args smc = {0};
1649a434ceeSElliot Berman 
1659a434ceeSElliot Berman 	smc.args[0] = ARM_SMCCC_CALL_VAL(
1669a434ceeSElliot Berman 		smccc_call_type,
1679a434ceeSElliot Berman 		qcom_smccc_convention,
1689a434ceeSElliot Berman 		desc->owner,
1699a434ceeSElliot Berman 		SCM_SMC_FNID(desc->svc, desc->cmd));
1709a434ceeSElliot Berman 	smc.args[1] = desc->arginfo;
1719a434ceeSElliot Berman 	for (i = 0; i < SCM_SMC_N_REG_ARGS; i++)
1729a434ceeSElliot Berman 		smc.args[i + SCM_SMC_FIRST_REG_IDX] = desc->args[i];
1739a434ceeSElliot Berman 
1749a434ceeSElliot Berman 	if (unlikely(arglen > SCM_SMC_N_REG_ARGS)) {
1759a434ceeSElliot Berman 		alloc_len = SCM_SMC_N_EXT_ARGS * sizeof(u64);
1769a434ceeSElliot Berman 		args_virt = kzalloc(PAGE_ALIGN(alloc_len), flag);
1779a434ceeSElliot Berman 
1789a434ceeSElliot Berman 		if (!args_virt)
1799a434ceeSElliot Berman 			return -ENOMEM;
1809a434ceeSElliot Berman 
1819a434ceeSElliot Berman 		if (qcom_smccc_convention == ARM_SMCCC_SMC_32) {
1829a434ceeSElliot Berman 			__le32 *args = args_virt;
1839a434ceeSElliot Berman 
1849a434ceeSElliot Berman 			for (i = 0; i < SCM_SMC_N_EXT_ARGS; i++)
1859a434ceeSElliot Berman 				args[i] = cpu_to_le32(desc->args[i +
1869a434ceeSElliot Berman 						      SCM_SMC_FIRST_EXT_IDX]);
1879a434ceeSElliot Berman 		} else {
1889a434ceeSElliot Berman 			__le64 *args = args_virt;
1899a434ceeSElliot Berman 
1909a434ceeSElliot Berman 			for (i = 0; i < SCM_SMC_N_EXT_ARGS; i++)
1919a434ceeSElliot Berman 				args[i] = cpu_to_le64(desc->args[i +
1929a434ceeSElliot Berman 						      SCM_SMC_FIRST_EXT_IDX]);
1939a434ceeSElliot Berman 		}
1949a434ceeSElliot Berman 
1959a434ceeSElliot Berman 		args_phys = dma_map_single(dev, args_virt, alloc_len,
1969a434ceeSElliot Berman 					   DMA_TO_DEVICE);
1979a434ceeSElliot Berman 
1989a434ceeSElliot Berman 		if (dma_mapping_error(dev, args_phys)) {
1999a434ceeSElliot Berman 			kfree(args_virt);
2009a434ceeSElliot Berman 			return -ENOMEM;
2019a434ceeSElliot Berman 		}
2029a434ceeSElliot Berman 
2039a434ceeSElliot Berman 		smc.args[SCM_SMC_LAST_REG_IDX] = args_phys;
2049a434ceeSElliot Berman 	}
2059a434ceeSElliot Berman 
2066bf32599SGuru Das Srinagesh 	/* ret error check follows after args_virt cleanup*/
2076bf32599SGuru Das Srinagesh 	ret = __scm_smc_do(dev, &smc, &smc_res, atomic);
2089a434ceeSElliot Berman 
2099a434ceeSElliot Berman 	if (args_virt) {
2109a434ceeSElliot Berman 		dma_unmap_single(dev, args_phys, alloc_len, DMA_TO_DEVICE);
2119a434ceeSElliot Berman 		kfree(args_virt);
2129a434ceeSElliot Berman 	}
2139a434ceeSElliot Berman 
2146bf32599SGuru Das Srinagesh 	if (ret)
2156bf32599SGuru Das Srinagesh 		return ret;
2166bf32599SGuru Das Srinagesh 
2179a434ceeSElliot Berman 	if (res) {
2189a434ceeSElliot Berman 		res->result[0] = smc_res.a1;
2199a434ceeSElliot Berman 		res->result[1] = smc_res.a2;
2209a434ceeSElliot Berman 		res->result[2] = smc_res.a3;
2219a434ceeSElliot Berman 	}
2229a434ceeSElliot Berman 
2239a434ceeSElliot Berman 	return (long)smc_res.a0 ? qcom_scm_remap_error(smc_res.a0) : 0;
224f6ea568fSStephen Boyd 
2259a434ceeSElliot Berman }
226