2019-05-29 14:17:58 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
2020-01-07 21:04:10 +00:00
|
|
|
/* Copyright (c) 2015,2019 The Linux Foundation. All rights reserved.
|
2015-09-11 21:01:16 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/io.h>
|
|
|
|
#include <linux/errno.h>
|
2016-06-03 23:25:26 +00:00
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/mutex.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/types.h>
|
2015-09-11 21:01:16 +00:00
|
|
|
#include <linux/qcom_scm.h>
|
2016-06-03 23:25:26 +00:00
|
|
|
#include <linux/arm-smccc.h>
|
|
|
|
#include <linux/dma-mapping.h>
|
|
|
|
|
|
|
|
#include "qcom_scm.h"
|
|
|
|
|
2020-01-07 21:04:10 +00:00
|
|
|
#define SCM_SMC_FNID(s, c) ((((s) & 0xFF) << 8) | ((c) & 0xFF))
|
2016-06-03 23:25:26 +00:00
|
|
|
|
2020-01-07 21:04:16 +00:00
|
|
|
/**
|
|
|
|
* struct arm_smccc_args
|
|
|
|
* @args: The array of values used in registers in smc instruction
|
|
|
|
*/
|
|
|
|
struct arm_smccc_args {
|
|
|
|
unsigned long args[8];
|
|
|
|
};
|
|
|
|
|
2016-06-03 23:25:26 +00:00
|
|
|
static u64 qcom_smccc_convention = -1;
|
|
|
|
static DEFINE_MUTEX(qcom_scm_lock);
|
|
|
|
|
|
|
|
#define QCOM_SCM_EBUSY_WAIT_MS 30
|
|
|
|
#define QCOM_SCM_EBUSY_MAX_RETRY 20
|
|
|
|
|
2020-01-07 21:04:13 +00:00
|
|
|
#define SCM_SMC_N_REG_ARGS 4
|
|
|
|
#define SCM_SMC_FIRST_EXT_IDX (SCM_SMC_N_REG_ARGS - 1)
|
|
|
|
#define SCM_SMC_N_EXT_ARGS (MAX_QCOM_SCM_ARGS - SCM_SMC_N_REG_ARGS + 1)
|
2020-01-07 21:04:16 +00:00
|
|
|
#define SCM_SMC_FIRST_REG_IDX 2
|
|
|
|
#define SCM_SMC_LAST_REG_IDX (SCM_SMC_FIRST_REG_IDX + SCM_SMC_N_REG_ARGS - 1)
|
2016-06-03 23:25:26 +00:00
|
|
|
|
2020-01-07 21:04:16 +00:00
|
|
|
static void __scm_smc_do_quirk(const struct arm_smccc_args *smc,
|
|
|
|
struct arm_smccc_res *res)
|
2019-09-20 08:04:27 +00:00
|
|
|
{
|
2020-01-07 21:04:16 +00:00
|
|
|
unsigned long a0 = smc->args[0];
|
2019-09-20 08:04:27 +00:00
|
|
|
struct arm_smccc_quirk quirk = { .id = ARM_SMCCC_QUIRK_QCOM_A6 };
|
|
|
|
|
|
|
|
quirk.state.a6 = 0;
|
|
|
|
|
|
|
|
do {
|
2020-01-07 21:04:16 +00:00
|
|
|
arm_smccc_smc_quirk(a0, smc->args[1], smc->args[2],
|
|
|
|
smc->args[3], smc->args[4], smc->args[5],
|
|
|
|
quirk.state.a6, smc->args[7], res, &quirk);
|
2019-09-20 08:04:27 +00:00
|
|
|
|
|
|
|
if (res->a0 == QCOM_SCM_INTERRUPTED)
|
2020-01-07 21:04:16 +00:00
|
|
|
a0 = res->a0;
|
2019-09-20 08:04:27 +00:00
|
|
|
|
|
|
|
} while (res->a0 == QCOM_SCM_INTERRUPTED);
|
|
|
|
}
|
|
|
|
|
2020-01-07 21:04:16 +00:00
|
|
|
static void __scm_smc_do(const struct arm_smccc_args *smc,
|
|
|
|
struct arm_smccc_res *res, bool atomic)
|
2019-09-20 08:04:27 +00:00
|
|
|
{
|
|
|
|
int retry_count = 0;
|
|
|
|
|
|
|
|
if (atomic) {
|
2020-01-07 21:04:16 +00:00
|
|
|
__scm_smc_do_quirk(smc, res);
|
2019-09-20 08:04:27 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
do {
|
|
|
|
mutex_lock(&qcom_scm_lock);
|
|
|
|
|
2020-01-07 21:04:16 +00:00
|
|
|
__scm_smc_do_quirk(smc, res);
|
2019-09-20 08:04:27 +00:00
|
|
|
|
|
|
|
mutex_unlock(&qcom_scm_lock);
|
|
|
|
|
|
|
|
if (res->a0 == QCOM_SCM_V2_EBUSY) {
|
|
|
|
if (retry_count++ > QCOM_SCM_EBUSY_MAX_RETRY)
|
|
|
|
break;
|
|
|
|
msleep(QCOM_SCM_EBUSY_WAIT_MS);
|
|
|
|
}
|
|
|
|
} while (res->a0 == QCOM_SCM_V2_EBUSY);
|
|
|
|
}
|
|
|
|
|
2020-01-07 21:04:14 +00:00
|
|
|
static int __scm_smc_call(struct device *dev, const struct qcom_scm_desc *desc,
|
2020-01-07 21:04:15 +00:00
|
|
|
struct qcom_scm_res *res, bool atomic)
|
2016-06-03 23:25:26 +00:00
|
|
|
{
|
|
|
|
int arglen = desc->arginfo & 0xf;
|
2019-09-20 08:04:27 +00:00
|
|
|
int i;
|
2016-06-03 23:25:26 +00:00
|
|
|
dma_addr_t args_phys = 0;
|
|
|
|
void *args_virt = NULL;
|
|
|
|
size_t alloc_len;
|
2019-09-20 08:04:27 +00:00
|
|
|
gfp_t flag = atomic ? GFP_ATOMIC : GFP_KERNEL;
|
2020-01-07 21:04:16 +00:00
|
|
|
u32 smccc_call_type = atomic ? ARM_SMCCC_FAST_CALL : ARM_SMCCC_STD_CALL;
|
2020-01-07 21:04:15 +00:00
|
|
|
struct arm_smccc_res smc_res;
|
2020-01-07 21:04:16 +00:00
|
|
|
struct arm_smccc_args smc = {0};
|
|
|
|
|
|
|
|
smc.args[0] = ARM_SMCCC_CALL_VAL(
|
|
|
|
smccc_call_type,
|
|
|
|
qcom_smccc_convention,
|
|
|
|
desc->owner,
|
|
|
|
SCM_SMC_FNID(desc->svc, desc->cmd));
|
|
|
|
smc.args[1] = desc->arginfo;
|
|
|
|
for (i = 0; i < SCM_SMC_N_REG_ARGS; i++)
|
|
|
|
smc.args[i + SCM_SMC_FIRST_REG_IDX] = desc->args[i];
|
2016-06-03 23:25:26 +00:00
|
|
|
|
2020-01-07 21:04:10 +00:00
|
|
|
if (unlikely(arglen > SCM_SMC_N_REG_ARGS)) {
|
|
|
|
alloc_len = SCM_SMC_N_EXT_ARGS * sizeof(u64);
|
2019-09-20 08:04:27 +00:00
|
|
|
args_virt = kzalloc(PAGE_ALIGN(alloc_len), flag);
|
2016-06-03 23:25:26 +00:00
|
|
|
|
|
|
|
if (!args_virt)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
if (qcom_smccc_convention == ARM_SMCCC_SMC_32) {
|
|
|
|
__le32 *args = args_virt;
|
|
|
|
|
2020-01-07 21:04:10 +00:00
|
|
|
for (i = 0; i < SCM_SMC_N_EXT_ARGS; i++)
|
2016-06-03 23:25:26 +00:00
|
|
|
args[i] = cpu_to_le32(desc->args[i +
|
2020-01-07 21:04:10 +00:00
|
|
|
SCM_SMC_FIRST_EXT_IDX]);
|
2016-06-03 23:25:26 +00:00
|
|
|
} else {
|
|
|
|
__le64 *args = args_virt;
|
|
|
|
|
2020-01-07 21:04:10 +00:00
|
|
|
for (i = 0; i < SCM_SMC_N_EXT_ARGS; i++)
|
2016-06-03 23:25:26 +00:00
|
|
|
args[i] = cpu_to_le64(desc->args[i +
|
2020-01-07 21:04:10 +00:00
|
|
|
SCM_SMC_FIRST_EXT_IDX]);
|
2016-06-03 23:25:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
args_phys = dma_map_single(dev, args_virt, alloc_len,
|
|
|
|
DMA_TO_DEVICE);
|
|
|
|
|
|
|
|
if (dma_mapping_error(dev, args_phys)) {
|
|
|
|
kfree(args_virt);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
2020-01-07 21:04:16 +00:00
|
|
|
smc.args[SCM_SMC_LAST_REG_IDX] = args_phys;
|
2016-06-03 23:25:26 +00:00
|
|
|
}
|
|
|
|
|
2020-01-07 21:04:16 +00:00
|
|
|
__scm_smc_do(&smc, &smc_res, atomic);
|
2016-06-03 23:25:26 +00:00
|
|
|
|
|
|
|
if (args_virt) {
|
|
|
|
dma_unmap_single(dev, args_phys, alloc_len, DMA_TO_DEVICE);
|
|
|
|
kfree(args_virt);
|
|
|
|
}
|
|
|
|
|
2020-01-07 21:04:15 +00:00
|
|
|
if (res) {
|
|
|
|
res->result[0] = smc_res.a1;
|
|
|
|
res->result[1] = smc_res.a2;
|
|
|
|
res->result[2] = smc_res.a3;
|
|
|
|
}
|
2016-06-03 23:25:26 +00:00
|
|
|
|
2020-01-07 21:04:15 +00:00
|
|
|
return (long)smc_res.a0 ? qcom_scm_remap_error(smc_res.a0) : 0;
|
2016-06-03 23:25:26 +00:00
|
|
|
}
|
2015-09-11 21:01:16 +00:00
|
|
|
|
2019-09-20 08:04:27 +00:00
|
|
|
/**
|
|
|
|
* qcom_scm_call() - Invoke a syscall in the secure world
|
|
|
|
* @dev: device
|
|
|
|
* @svc_id: service identifier
|
|
|
|
* @cmd_id: command identifier
|
|
|
|
* @desc: Descriptor structure containing arguments and return values
|
|
|
|
*
|
|
|
|
* Sends a command to the SCM and waits for the command to finish processing.
|
|
|
|
* This should *only* be called in pre-emptible context.
|
|
|
|
*/
|
2020-01-07 21:04:25 +00:00
|
|
|
int qcom_scm_call(struct device *dev, const struct qcom_scm_desc *desc,
|
|
|
|
struct qcom_scm_res *res)
|
2019-09-20 08:04:27 +00:00
|
|
|
{
|
|
|
|
might_sleep();
|
2020-01-07 21:04:14 +00:00
|
|
|
return __scm_smc_call(dev, desc, res, false);
|
2019-09-20 08:04:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* qcom_scm_call_atomic() - atomic variation of qcom_scm_call()
|
|
|
|
* @dev: device
|
|
|
|
* @svc_id: service identifier
|
|
|
|
* @cmd_id: command identifier
|
|
|
|
* @desc: Descriptor structure containing arguments and return values
|
|
|
|
* @res: Structure containing results from SMC/HVC call
|
|
|
|
*
|
|
|
|
* Sends a command to the SCM and waits for the command to finish processing.
|
|
|
|
* This can be called in atomic context.
|
|
|
|
*/
|
2020-01-07 21:04:25 +00:00
|
|
|
int qcom_scm_call_atomic(struct device *dev, const struct qcom_scm_desc *desc,
|
|
|
|
struct qcom_scm_res *res)
|
2019-09-20 08:04:27 +00:00
|
|
|
{
|
2020-01-07 21:04:14 +00:00
|
|
|
return __scm_smc_call(dev, desc, res, true);
|
2019-09-20 08:04:27 +00:00
|
|
|
}
|
|
|
|
|
2016-06-03 23:25:26 +00:00
|
|
|
int __qcom_scm_is_call_available(struct device *dev, u32 svc_id, u32 cmd_id)
|
2015-09-11 21:01:16 +00:00
|
|
|
{
|
2016-06-03 23:25:26 +00:00
|
|
|
int ret;
|
2020-01-07 21:04:14 +00:00
|
|
|
struct qcom_scm_desc desc = {
|
|
|
|
.svc = QCOM_SCM_SVC_INFO,
|
|
|
|
.cmd = QCOM_SCM_INFO_IS_CALL_AVAIL,
|
|
|
|
.owner = ARM_SMCCC_OWNER_SIP,
|
|
|
|
};
|
2020-01-07 21:04:15 +00:00
|
|
|
struct qcom_scm_res res;
|
2016-06-03 23:25:26 +00:00
|
|
|
|
|
|
|
desc.arginfo = QCOM_SCM_ARGS(1);
|
2020-01-07 21:04:10 +00:00
|
|
|
desc.args[0] = SCM_SMC_FNID(svc_id, cmd_id) |
|
2016-06-03 23:25:26 +00:00
|
|
|
(ARM_SMCCC_OWNER_SIP << ARM_SMCCC_OWNER_SHIFT);
|
|
|
|
|
2020-01-07 21:04:14 +00:00
|
|
|
ret = qcom_scm_call(dev, &desc, &res);
|
2016-06-03 23:25:26 +00:00
|
|
|
|
2020-01-07 21:04:15 +00:00
|
|
|
return ret ? : res.result[0];
|
2015-09-11 21:01:16 +00:00
|
|
|
}
|
|
|
|
|
2016-06-03 23:25:26 +00:00
|
|
|
void __qcom_scm_init(void)
|
|
|
|
{
|
2020-01-07 21:04:17 +00:00
|
|
|
struct qcom_scm_desc desc = {
|
|
|
|
.svc = QCOM_SCM_SVC_INFO,
|
|
|
|
.cmd = QCOM_SCM_INFO_IS_CALL_AVAIL,
|
|
|
|
.args[0] = SCM_SMC_FNID(QCOM_SCM_SVC_INFO,
|
|
|
|
QCOM_SCM_INFO_IS_CALL_AVAIL) |
|
|
|
|
(ARM_SMCCC_OWNER_SIP << ARM_SMCCC_OWNER_SHIFT),
|
|
|
|
.arginfo = QCOM_SCM_ARGS(1),
|
|
|
|
.owner = ARM_SMCCC_OWNER_SIP,
|
|
|
|
};
|
|
|
|
struct qcom_scm_res res;
|
|
|
|
int ret;
|
2016-06-03 23:25:26 +00:00
|
|
|
|
2020-01-07 21:04:17 +00:00
|
|
|
qcom_smccc_convention = ARM_SMCCC_SMC_64;
|
|
|
|
// Device isn't required as there is only one argument - no device
|
|
|
|
// needed to dma_map_single to secure world
|
|
|
|
ret = qcom_scm_call_atomic(NULL, &desc, &res);
|
|
|
|
if (!ret && res.result[0] == 1)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
qcom_smccc_convention = ARM_SMCCC_SMC_32;
|
|
|
|
ret = qcom_scm_call_atomic(NULL, &desc, &res);
|
|
|
|
if (!ret && res.result[0] == 1)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
qcom_smccc_convention = -1;
|
|
|
|
BUG();
|
|
|
|
out:
|
|
|
|
pr_info("QCOM SCM SMC Convention: %lld\n", qcom_smccc_convention);
|
2015-09-11 21:01:16 +00:00
|
|
|
}
|