mirror of
https://github.com/torvalds/linux.git
synced 2026-09-23 13:14:02 +02:00
Add support for Peripheral Authentication Service (PAS) driver based on TEE bus with OP-TEE providing the backend PAS service implementation. The TEE PAS service ABI is designed to be extensible with additional API as PTA_QCOM_PAS_CAPABILITIES. This allows to accommodate any future extensions of the PAS service needed while still maintaining backwards compatibility. Reviewed-by: Mukesh Ojha <mukesh.ojha@oss.qualcomm.com> Tested-by: Mukesh Ojha <mukesh.ojha@oss.qualcomm.com> # Lemans Reviewed-by: Harshal Dev <harshal.dev@oss.qualcomm.com> Tested-by: Vignesh Viswanathan <vignesh.viswanathan@oss.qualcomm.com> # IPQ9650 Signed-off-by: Sumit Garg <sumit.garg@oss.qualcomm.com> Link: https://lore.kernel.org/r/20260702115835.167602-4-sumit.garg@kernel.org Signed-off-by: Bjorn Andersson <andersson@kernel.org>
480 lines
12 KiB
C
480 lines
12 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
|
|
*/
|
|
|
|
#include <linux/delay.h>
|
|
#include <linux/of.h>
|
|
#include <linux/firmware/qcom/qcom_pas.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/tee_drv.h>
|
|
#include <linux/uuid.h>
|
|
|
|
#include "qcom_pas.h"
|
|
|
|
/*
|
|
* Peripheral Authentication Service (PAS) supported.
|
|
*
|
|
* [in] params[0].value.a: Unique 32bit remote processor identifier
|
|
*/
|
|
#define TA_QCOM_PAS_IS_SUPPORTED 1
|
|
|
|
/*
|
|
* PAS capabilities.
|
|
*
|
|
* [in] params[0].value.a: Unique 32bit remote processor identifier
|
|
* [out] params[1].value.a: PAS capability flags
|
|
*/
|
|
#define TA_QCOM_PAS_CAPABILITIES 2
|
|
|
|
/*
|
|
* PAS image initialization.
|
|
*
|
|
* [in] params[0].value.a: Unique 32bit remote processor identifier
|
|
* [in] params[1].memref: Loadable firmware metadata
|
|
*/
|
|
#define TA_QCOM_PAS_INIT_IMAGE 3
|
|
|
|
/*
|
|
* PAS memory setup.
|
|
*
|
|
* [in] params[0].value.a: Unique 32bit remote processor identifier
|
|
* [in] params[0].value.b: Relocatable firmware size
|
|
* [in] params[1].value.a: 32bit LSB relocatable firmware memory address
|
|
* [in] params[1].value.b: 32bit MSB relocatable firmware memory address
|
|
*/
|
|
#define TA_QCOM_PAS_MEM_SETUP 4
|
|
|
|
/*
|
|
* PAS get resource table.
|
|
*
|
|
* [in] params[0].value.a: Unique 32bit remote processor identifier
|
|
* [inout] params[1].memref: Resource table config
|
|
*/
|
|
#define TA_QCOM_PAS_GET_RESOURCE_TABLE 5
|
|
|
|
/*
|
|
* PAS image authentication and co-processor reset.
|
|
*
|
|
* [in] params[0].value.a: Unique 32bit remote processor identifier
|
|
* [in] params[0].value.b: Firmware size
|
|
* [in] params[1].value.a: 32bit LSB firmware memory address
|
|
* [in] params[1].value.b: 32bit MSB firmware memory address
|
|
* [in] params[2].memref: Optional fw memory space shared/lent
|
|
*/
|
|
#define TA_QCOM_PAS_AUTH_AND_RESET 6
|
|
|
|
/*
|
|
* PAS co-processor set suspend/resume state.
|
|
*
|
|
* [in] params[0].value.a: Unique 32bit remote processor identifier
|
|
* [in] params[0].value.b: Co-processor state identifier
|
|
*/
|
|
#define TA_QCOM_PAS_SET_REMOTE_STATE 7
|
|
|
|
/*
|
|
* PAS co-processor shutdown.
|
|
*
|
|
* [in] params[0].value.a: Unique 32bit remote processor identifier
|
|
*/
|
|
#define TA_QCOM_PAS_SHUTDOWN 8
|
|
|
|
#define TEE_NUM_PARAMS 4
|
|
|
|
/**
|
|
* struct qcom_pas_tee_private - PAS service private data
|
|
* @dev: PAS service device.
|
|
* @ctx: TEE context handler.
|
|
* @session_id: PAS TA session identifier.
|
|
*/
|
|
struct qcom_pas_tee_private {
|
|
struct device *dev;
|
|
struct tee_context *ctx;
|
|
u32 session_id;
|
|
};
|
|
|
|
static bool qcom_pas_tee_supported(struct device *dev, u32 pas_id)
|
|
{
|
|
struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
|
|
struct tee_ioctl_invoke_arg inv_arg = {
|
|
.func = TA_QCOM_PAS_IS_SUPPORTED,
|
|
.session = data->session_id,
|
|
.num_params = TEE_NUM_PARAMS
|
|
};
|
|
struct tee_param param[4] = {
|
|
[0] = {
|
|
.attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
|
|
.u.value.a = pas_id
|
|
}
|
|
};
|
|
int ret;
|
|
|
|
ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
|
|
if (ret < 0 || inv_arg.ret != 0) {
|
|
dev_err(dev, "PAS not supported, pas_id: %d, ret: %d, err: 0x%x\n",
|
|
pas_id, ret, inv_arg.ret);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int qcom_pas_tee_init_image(struct device *dev, u32 pas_id,
|
|
const void *metadata, size_t size,
|
|
struct qcom_pas_context *ctx)
|
|
{
|
|
struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
|
|
struct tee_ioctl_invoke_arg inv_arg = {
|
|
.func = TA_QCOM_PAS_INIT_IMAGE,
|
|
.session = data->session_id,
|
|
.num_params = TEE_NUM_PARAMS
|
|
};
|
|
struct tee_param param[4] = {
|
|
[0] = {
|
|
.attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
|
|
.u.value.a = pas_id
|
|
},
|
|
[1] = {
|
|
.attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT,
|
|
}
|
|
};
|
|
struct tee_shm *mdata_shm;
|
|
u8 *mdata_buf = NULL;
|
|
int ret;
|
|
|
|
mdata_shm = tee_shm_alloc_kernel_buf(data->ctx, size);
|
|
if (IS_ERR(mdata_shm)) {
|
|
dev_err(dev, "mdata_shm allocation failed\n");
|
|
return PTR_ERR(mdata_shm);
|
|
}
|
|
|
|
mdata_buf = tee_shm_get_va(mdata_shm, 0);
|
|
if (IS_ERR(mdata_buf)) {
|
|
dev_err(dev, "mdata_buf get VA failed\n");
|
|
tee_shm_free(mdata_shm);
|
|
return PTR_ERR(mdata_buf);
|
|
}
|
|
memcpy(mdata_buf, metadata, size);
|
|
|
|
param[1].u.memref.shm = mdata_shm;
|
|
param[1].u.memref.size = size;
|
|
|
|
ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
|
|
if (ret < 0 || inv_arg.ret != 0) {
|
|
dev_err(dev, "PAS init image failed, pas_id: %d, ret: %d, err: 0x%x\n",
|
|
pas_id, ret, inv_arg.ret);
|
|
tee_shm_free(mdata_shm);
|
|
return ret ?: -EINVAL;
|
|
}
|
|
|
|
if (ctx)
|
|
ctx->ptr = (void *)mdata_shm;
|
|
else
|
|
tee_shm_free(mdata_shm);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int qcom_pas_tee_mem_setup(struct device *dev, u32 pas_id,
|
|
phys_addr_t addr, phys_addr_t size)
|
|
{
|
|
struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
|
|
struct tee_ioctl_invoke_arg inv_arg = {
|
|
.func = TA_QCOM_PAS_MEM_SETUP,
|
|
.session = data->session_id,
|
|
.num_params = TEE_NUM_PARAMS
|
|
};
|
|
struct tee_param param[4] = {
|
|
[0] = {
|
|
.attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
|
|
.u.value.a = pas_id,
|
|
.u.value.b = size,
|
|
},
|
|
[1] = {
|
|
.attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
|
|
.u.value.a = lower_32_bits(addr),
|
|
.u.value.b = upper_32_bits(addr),
|
|
}
|
|
};
|
|
int ret;
|
|
|
|
ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
|
|
if (ret < 0 || inv_arg.ret != 0) {
|
|
dev_err(dev, "PAS mem setup failed, pas_id: %d, ret: %d, err: 0x%x\n",
|
|
pas_id, ret, inv_arg.ret);
|
|
return ret ?: -EINVAL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
DEFINE_FREE(shm_free, struct tee_shm *, tee_shm_free(_T))
|
|
|
|
static void *qcom_pas_tee_get_rsc_table(struct device *dev,
|
|
struct qcom_pas_context *ctx,
|
|
void *input_rt, size_t input_rt_size,
|
|
size_t *output_rt_size)
|
|
{
|
|
struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
|
|
struct tee_ioctl_invoke_arg inv_arg = {
|
|
.func = TA_QCOM_PAS_GET_RESOURCE_TABLE,
|
|
.session = data->session_id,
|
|
.num_params = TEE_NUM_PARAMS
|
|
};
|
|
struct tee_param param[4] = {
|
|
[0] = {
|
|
.attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
|
|
.u.value.a = ctx->pas_id,
|
|
},
|
|
[1] = {
|
|
.attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT,
|
|
.u.memref.size = input_rt_size,
|
|
}
|
|
};
|
|
void *rt_buf = NULL;
|
|
int ret;
|
|
|
|
ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
|
|
if (ret < 0 || inv_arg.ret != 0) {
|
|
dev_err(dev, "PAS get RT failed, pas_id: %d, ret: %d, err: 0x%x\n",
|
|
ctx->pas_id, ret, inv_arg.ret);
|
|
return ret ? ERR_PTR(ret) : ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
if (param[1].u.memref.size >= input_rt_size) {
|
|
struct tee_shm *rt_shm __free(shm_free) =
|
|
tee_shm_alloc_kernel_buf(data->ctx,
|
|
param[1].u.memref.size);
|
|
void *rt_shm_va;
|
|
|
|
if (IS_ERR_OR_NULL(rt_shm)) {
|
|
dev_err(dev, "rt_shm allocation failed\n");
|
|
rt_shm = NULL;
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
rt_shm_va = tee_shm_get_va(rt_shm, 0);
|
|
if (IS_ERR(rt_shm_va)) {
|
|
dev_err(dev, "rt_shm get VA failed\n");
|
|
return ERR_CAST(rt_shm_va);
|
|
}
|
|
memcpy(rt_shm_va, input_rt, input_rt_size);
|
|
|
|
param[1].u.memref.shm = rt_shm;
|
|
ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
|
|
if (ret < 0 || inv_arg.ret != 0) {
|
|
dev_err(dev, "PAS get RT failed, pas_id: %d, ret: %d, err: 0x%x\n",
|
|
ctx->pas_id, ret, inv_arg.ret);
|
|
return ret ? ERR_PTR(ret) : ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
if (param[1].u.memref.size) {
|
|
*output_rt_size = param[1].u.memref.size;
|
|
rt_buf = kmemdup(rt_shm_va, *output_rt_size, GFP_KERNEL);
|
|
if (!rt_buf)
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
} else {
|
|
*output_rt_size = 0;
|
|
}
|
|
|
|
return rt_buf;
|
|
}
|
|
|
|
static int __qcom_pas_tee_auth_and_reset(struct device *dev, u32 pas_id,
|
|
phys_addr_t mem_phys, size_t mem_size)
|
|
{
|
|
struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
|
|
struct tee_ioctl_invoke_arg inv_arg = {
|
|
.func = TA_QCOM_PAS_AUTH_AND_RESET,
|
|
.session = data->session_id,
|
|
.num_params = TEE_NUM_PARAMS
|
|
};
|
|
struct tee_param param[4] = {
|
|
[0] = {
|
|
.attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
|
|
.u.value.a = pas_id,
|
|
.u.value.b = mem_size,
|
|
},
|
|
[1] = {
|
|
.attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
|
|
.u.value.a = lower_32_bits(mem_phys),
|
|
.u.value.b = upper_32_bits(mem_phys),
|
|
},
|
|
/* Reserved for fw memory space to be shared or lent */
|
|
[2] = {
|
|
.attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT,
|
|
}
|
|
};
|
|
int ret;
|
|
|
|
ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
|
|
if (ret < 0 || inv_arg.ret != 0) {
|
|
dev_err(dev, "PAS auth reset failed, pas_id: %d, ret: %d, err: 0x%x\n",
|
|
pas_id, ret, inv_arg.ret);
|
|
return ret ?: -EINVAL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int qcom_pas_tee_auth_and_reset(struct device *dev, u32 pas_id)
|
|
{
|
|
return __qcom_pas_tee_auth_and_reset(dev, pas_id, 0, 0);
|
|
}
|
|
|
|
static int qcom_pas_tee_prepare_and_auth_reset(struct device *dev,
|
|
struct qcom_pas_context *ctx)
|
|
{
|
|
return __qcom_pas_tee_auth_and_reset(dev, ctx->pas_id, ctx->mem_phys,
|
|
ctx->mem_size);
|
|
}
|
|
|
|
static int qcom_pas_tee_set_remote_state(struct device *dev, u32 state,
|
|
u32 pas_id)
|
|
{
|
|
struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
|
|
struct tee_ioctl_invoke_arg inv_arg = {
|
|
.func = TA_QCOM_PAS_SET_REMOTE_STATE,
|
|
.session = data->session_id,
|
|
.num_params = TEE_NUM_PARAMS
|
|
};
|
|
struct tee_param param[4] = {
|
|
[0] = {
|
|
.attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
|
|
.u.value.a = pas_id,
|
|
.u.value.b = state,
|
|
}
|
|
};
|
|
int ret;
|
|
|
|
ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
|
|
if (ret < 0 || inv_arg.ret != 0) {
|
|
dev_err(dev, "PAS set remote state failed, pas_id: %d, ret: %d, err: 0x%x\n",
|
|
pas_id, ret, inv_arg.ret);
|
|
return ret ?: -EINVAL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int qcom_pas_tee_shutdown(struct device *dev, u32 pas_id)
|
|
{
|
|
struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
|
|
struct tee_ioctl_invoke_arg inv_arg = {
|
|
.func = TA_QCOM_PAS_SHUTDOWN,
|
|
.session = data->session_id,
|
|
.num_params = TEE_NUM_PARAMS
|
|
};
|
|
struct tee_param param[4] = {
|
|
[0] = {
|
|
.attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
|
|
.u.value.a = pas_id
|
|
}
|
|
};
|
|
int ret;
|
|
|
|
ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
|
|
if (ret < 0 || inv_arg.ret != 0) {
|
|
dev_err(dev, "PAS shutdown failed, pas_id: %d, ret: %d, err: 0x%x\n",
|
|
pas_id, ret, inv_arg.ret);
|
|
return ret ?: -EINVAL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void qcom_pas_tee_metadata_release(struct device *dev,
|
|
struct qcom_pas_context *ctx)
|
|
{
|
|
struct tee_shm *mdata_shm = ctx->ptr;
|
|
|
|
tee_shm_free(mdata_shm);
|
|
ctx->ptr = NULL;
|
|
}
|
|
|
|
static struct qcom_pas_ops qcom_pas_ops_tee = {
|
|
.drv_name = "qcom-pas-tee",
|
|
.supported = qcom_pas_tee_supported,
|
|
.init_image = qcom_pas_tee_init_image,
|
|
.mem_setup = qcom_pas_tee_mem_setup,
|
|
.get_rsc_table = qcom_pas_tee_get_rsc_table,
|
|
.auth_and_reset = qcom_pas_tee_auth_and_reset,
|
|
.prepare_and_auth_reset = qcom_pas_tee_prepare_and_auth_reset,
|
|
.set_remote_state = qcom_pas_tee_set_remote_state,
|
|
.shutdown = qcom_pas_tee_shutdown,
|
|
.metadata_release = qcom_pas_tee_metadata_release,
|
|
};
|
|
|
|
static int optee_ctx_match(struct tee_ioctl_version_data *ver, const void *data)
|
|
{
|
|
return ver->impl_id == TEE_IMPL_ID_OPTEE;
|
|
}
|
|
|
|
static int qcom_pas_tee_probe(struct tee_client_device *pas_dev)
|
|
{
|
|
struct device *dev = &pas_dev->dev;
|
|
struct qcom_pas_tee_private *data;
|
|
struct tee_ioctl_open_session_arg sess_arg = {
|
|
.clnt_login = TEE_IOCTL_LOGIN_REE_KERNEL
|
|
};
|
|
int ret;
|
|
|
|
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
data->ctx = tee_client_open_context(NULL, optee_ctx_match, NULL, NULL);
|
|
if (IS_ERR(data->ctx))
|
|
return -ENODEV;
|
|
|
|
export_uuid(sess_arg.uuid, &pas_dev->id.uuid);
|
|
ret = tee_client_open_session(data->ctx, &sess_arg, NULL);
|
|
if (ret < 0 || sess_arg.ret != 0) {
|
|
dev_err(dev, "tee_client_open_session failed, ret: %d, err: 0x%x\n",
|
|
ret, sess_arg.ret);
|
|
tee_client_close_context(data->ctx);
|
|
return ret ?: -EINVAL;
|
|
}
|
|
|
|
data->session_id = sess_arg.session;
|
|
dev_set_drvdata(dev, data);
|
|
qcom_pas_ops_tee.dev = dev;
|
|
qcom_pas_ops_register(&qcom_pas_ops_tee);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void qcom_pas_tee_remove(struct tee_client_device *pas_dev)
|
|
{
|
|
struct device *dev = &pas_dev->dev;
|
|
struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
|
|
|
|
qcom_pas_ops_unregister();
|
|
tee_client_close_session(data->ctx, data->session_id);
|
|
tee_client_close_context(data->ctx);
|
|
}
|
|
|
|
static const struct tee_client_device_id qcom_pas_tee_id_table[] = {
|
|
{UUID_INIT(0xcff7d191, 0x7ca0, 0x4784,
|
|
0xaf, 0x13, 0x48, 0x22, 0x3b, 0x9a, 0x4f, 0xbe)},
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(tee, qcom_pas_tee_id_table);
|
|
|
|
static struct tee_client_driver optee_pas_tee_driver = {
|
|
.probe = qcom_pas_tee_probe,
|
|
.remove = qcom_pas_tee_remove,
|
|
.id_table = qcom_pas_tee_id_table,
|
|
.driver = {
|
|
.name = "qcom-pas-tee",
|
|
},
|
|
};
|
|
|
|
module_tee_client_driver(optee_pas_tee_driver);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_DESCRIPTION("Qualcomm PAS TEE driver");
|